From e22b626b842c6236b2677edc656191bee74d0e88 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Mon, 17 Mar 2025 15:59:14 -0300 Subject: [PATCH 01/23] Setting up electra in version files --- .fork_version | 2 +- .spectest_version | 2 +- config/config.exs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.fork_version b/.fork_version index b89946e8e..c70bc15b6 100644 --- a/.fork_version +++ b/.fork_version @@ -1 +1 @@ -deneb +electra diff --git a/.spectest_version b/.spectest_version index 0d0c52f84..e099f3d06 100644 --- a/.spectest_version +++ b/.spectest_version @@ -1 +1 @@ -v1.4.0 +v1.5.0-beta.3 diff --git a/config/config.exs b/config/config.exs index 9478220ac..32984f0aa 100644 --- a/config/config.exs +++ b/config/config.exs @@ -10,6 +10,7 @@ fork_raw = File.read!(".fork_version") |> String.trim() fork = case fork_raw do "deneb" -> :deneb + "electra" -> :electra v -> raise "Invalid fork specified: #{v}" end From 0dee18ec3b6d7327f82c7c0b42928175f4f50dbe Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Tue, 18 Mar 2025 17:36:19 -0300 Subject: [PATCH 02/23] Avoid skipping electra test in the runners --- test/spec/runners/epoch_processing.ex | 3 +-- test/spec/runners/fork_choice.ex | 1 + test/spec/runners/random.ex | 1 + test/spec/runners/rewards.ex | 1 + test/spec/runners/sync.ex | 6 ++---- test/spec/tasks/generate_spec_tests.ex | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/spec/runners/epoch_processing.ex b/test/spec/runners/epoch_processing.ex index 84ef4f83f..cbc53d54f 100644 --- a/test/spec/runners/epoch_processing.ex +++ b/test/spec/runners/epoch_processing.ex @@ -38,8 +38,7 @@ defmodule EpochProcessingTestRunner do @impl TestRunner def skip?(%SpecTestCase{fork: "deneb"}), do: false - - @impl TestRunner + def skip?(%SpecTestCase{fork: "electra"}), do: false def skip?(_), do: true @impl TestRunner diff --git a/test/spec/runners/fork_choice.ex b/test/spec/runners/fork_choice.ex index 9a60e38c4..2f3ecbff8 100644 --- a/test/spec/runners/fork_choice.ex +++ b/test/spec/runners/fork_choice.ex @@ -26,6 +26,7 @@ defmodule ForkChoiceTestRunner do @impl TestRunner def skip?(%SpecTestCase{fork: "capella"}), do: false def skip?(%SpecTestCase{fork: "deneb"}), do: false + def skip?(%SpecTestCase{fork: "electra"}), do: false def skip?(_testcase), do: true @impl TestRunner diff --git a/test/spec/runners/random.ex b/test/spec/runners/random.ex index 5fd9a957b..fe47692db 100644 --- a/test/spec/runners/random.ex +++ b/test/spec/runners/random.ex @@ -9,6 +9,7 @@ defmodule RandomTestRunner do @impl TestRunner def skip?(%SpecTestCase{fork: "capella"}), do: false def skip?(%SpecTestCase{fork: "deneb"}), do: false + def skip?(%SpecTestCase{fork: "electra"}), do: false def skip?(_), do: true @impl TestRunner diff --git a/test/spec/runners/rewards.ex b/test/spec/runners/rewards.ex index df90674c9..2c535df83 100644 --- a/test/spec/runners/rewards.ex +++ b/test/spec/runners/rewards.ex @@ -18,6 +18,7 @@ defmodule RewardsTestRunner do end def skip?(%SpecTestCase{fork: "deneb"}), do: false + def skip?(%SpecTestCase{fork: "electra"}), do: false def skip?(_), do: true @impl TestRunner diff --git a/test/spec/runners/sync.ex b/test/spec/runners/sync.ex index 9e25ed781..5dec75059 100644 --- a/test/spec/runners/sync.ex +++ b/test/spec/runners/sync.ex @@ -24,10 +24,8 @@ defmodule SyncTestRunner do Enum.member?(@disabled_cases, testcase.case) end - def skip?(%SpecTestCase{fork: "deneb"}) do - false - end - + def skip?(%SpecTestCase{fork: "deneb"}), do: false + def skip?(%SpecTestCase{fork: "electra"}), do: false def skip?(_testcase), do: true @impl TestRunner diff --git a/test/spec/tasks/generate_spec_tests.ex b/test/spec/tasks/generate_spec_tests.ex index 5224be3c5..5a8100c06 100644 --- a/test/spec/tasks/generate_spec_tests.ex +++ b/test/spec/tasks/generate_spec_tests.ex @@ -97,7 +97,7 @@ defmodule Mix.Tasks.GenerateSpecTests do defp generate_case(runner_module, testcase) do """ @tag :tmp_dir - #{if runner_module.skip?(testcase), do: "\n@tag :skip", else: ""} + #{if runner_module.skip?(testcase), do: "@tag :skip", else: ""} test "#{SpecTestCase.name(testcase)}" do testcase = #{inspect(testcase)} #{runner_module}.run_test_case(testcase) From a8b13ec266d139ba20fe113327d649ad2a805cb6 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Tue, 18 Mar 2025 18:11:02 -0300 Subject: [PATCH 03/23] Updated native libraries to be up-to-date with latest ssz releases --- native/ssz_nif/Cargo.lock | 1118 +++++++++++++++++++++++---- native/ssz_nif/Cargo.toml | 10 +- native/ssz_nif/src/utils/helpers.rs | 2 +- 3 files changed, 955 insertions(+), 175 deletions(-) diff --git a/native/ssz_nif/Cargo.lock b/native/ssz_nif/Cargo.lock index ed31578be..82696c4c2 100644 --- a/native/ssz_nif/Cargo.lock +++ b/native/ssz_nif/Cargo.lock @@ -11,12 +11,223 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-primitives" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacedba97e65cdc7ab592f2b22ef5d3ab8d60b2056bc3a6e6363577e8270ec6f" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash", + "hashbrown", + "indexmap", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "auto_impl" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "bitvec" version = "1.0.1" @@ -38,12 +249,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" - [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -61,12 +266,18 @@ name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] [[package]] name = "cc" -version = "1.0.90" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -74,6 +285,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-hex" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -89,6 +319,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -101,9 +343,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -111,27 +353,37 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.100", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", ] [[package]] @@ -145,6 +397,36 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -152,7 +434,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -162,57 +460,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] -name = "equivalent" -version = "1.0.1" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] [[package]] -name = "ethbloom" -version = "0.13.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-rlp", - "impl-serde", - "tiny-keccak", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "ethereum-types" -version = "0.14.1" +name = "errno" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "ethbloom", - "fixed-hash", - "impl-rlp", - "impl-serde", - "primitive-types", - "uint", + "libc", + "windows-sys 0.59.0", ] [[package]] name = "ethereum_hashing" -version = "1.0.0-beta.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233dc6f434ce680dbabf4451ee3380cec46cb3c45d66660445a435619710dd35" +checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ "cpufeatures", - "lazy_static", "ring", "sha2", ] [[package]] name = "ethereum_serde_utils" -version = "0.5.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4d5951468846963c24e8744c133d44f39dff2cd3a233f6be22b370d08a524f" +checksum = "70cbccfccf81d67bff0ab36e591fa536c8a935b078a7b0e58c1d00d418332fc9" dependencies = [ - "ethereum-types", + "alloy-primitives", "hex", "serde", "serde_derive", @@ -221,25 +520,67 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.5.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61ffea29f26e8249d35128a82ec8d3bd4fbc80179ea5f5e5e3daafef6a80fcb" +checksum = "86da3096d1304f5f28476ce383005385459afeaf0eea08592b65ddbc9b258d16" dependencies = [ - "ethereum-types", - "itertools", + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", "smallvec", + "typenum", ] [[package]] name = "ethereum_ssz_derive" -version = "0.5.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6085d7fd3cf84bd2b8fec150d54c8467fb491d8db9c460607c5534f653a0ee38" +checksum = "d832a5c38eba0e7ad92592f7a22d693954637fbb332b4f669590d66a5c3183e5" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", ] [[package]] @@ -260,6 +601,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -274,6 +621,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -284,14 +632,41 @@ checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", ] [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", + "serde", +] [[package]] name = "heck" @@ -304,6 +679,18 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] [[package]] name = "ident_case" @@ -320,24 +707,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -351,19 +720,29 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -375,12 +754,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] -name = "js-sys" -version = "0.3.69" +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" dependencies = [ - "wasm-bindgen", + "digest 0.10.7", + "sha3-asm", ] [[package]] @@ -391,15 +793,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] -name = "log" -version = "0.4.21" +name = "libm" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "memchr" @@ -407,6 +815,35 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -439,6 +876,33 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -453,8 +917,6 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", "uint", ] @@ -470,13 +932,39 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -486,6 +974,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -501,6 +995,7 @@ dependencies = [ "libc", "rand_chacha", "rand_core", + "serde", ] [[package]] @@ -519,7 +1014,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.12", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", ] [[package]] @@ -551,19 +1055,28 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" -version = "0.16.20" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", + "cfg-if", + "getrandom 0.2.12", "libc", - "once_cell", - "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -576,12 +1089,81 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "ruint" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustler" version = "0.32.1" @@ -602,7 +1184,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] @@ -615,12 +1197,62 @@ dependencies = [ "unreachable", ] +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.197" @@ -638,7 +1270,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] @@ -660,7 +1292,43 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", ] [[package]] @@ -670,10 +1338,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "spin" -version = "0.5.2" +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] [[package]] name = "ssz_nif" @@ -689,14 +1361,13 @@ dependencies = [ [[package]] name = "ssz_types" -version = "0.5.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382939886cb24ee8ac885d09116a60f6262d827c7a9e36012b4f6d3d0116d0b3" +checksum = "dad0fa7e9a85c06d0a6ba5100d733fff72e231eb6db2d86078225cf716fd2d95" dependencies = [ - "derivative", "ethereum_serde_utils", "ethereum_ssz", - "itertools", + "itertools 0.13.0", "serde", "serde_derive", "smallvec", @@ -712,9 +1383,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -729,9 +1406,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -744,6 +1421,39 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -772,24 +1482,27 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.5.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c998ac5fe2b07c025444bdd522e6258110b63861c6698eedc610c071980238d" +checksum = "6c58eb0f518840670270d90d97ffee702d8662d9c5494870c9e1e9e0fa00f668" dependencies = [ - "ethereum-types", + "alloy-primitives", "ethereum_hashing", + "ethereum_ssz", "smallvec", + "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.5.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84303a9c7cda5f085a3ed9cd241d1e95e04d88aab1d679b02f212e653537ba86" +checksum = "699e7fb6b3fdfe0c809916f251cf5132d64966858601695c3736630a87e7166a" dependencies = [ "darling", + "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -798,6 +1511,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uint" version = "0.9.5" @@ -810,12 +1529,24 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unreachable" version = "1.0.0" @@ -827,9 +1558,15 @@ dependencies = [ [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -843,6 +1580,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -850,90 +1596,95 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "wasi" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen-rt", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.55", - "wasm-bindgen-shared", + "windows-targets", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "windows-targets", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "web-sys" -version = "0.3.69" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -944,6 +1695,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "wyz" version = "0.5.1" @@ -952,3 +1712,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/native/ssz_nif/Cargo.toml b/native/ssz_nif/Cargo.toml index d322084fa..afd7bd560 100644 --- a/native/ssz_nif/Cargo.toml +++ b/native/ssz_nif/Cargo.toml @@ -11,8 +11,8 @@ crate-type = ["cdylib"] [dependencies] rustler = "0.32.1" -ethereum_ssz_derive = "0.5.0" -ethereum_ssz = "0.5.0" -ssz_types = "0.5.4" -tree_hash = "0.5.2" -tree_hash_derive = "0.5.2" +ethereum_ssz_derive = "0.8.3" +ethereum_ssz = "0.8.3" +ssz_types = "0.10.1" +tree_hash = "0.9.1" +tree_hash_derive = "0.9.1" diff --git a/native/ssz_nif/src/utils/helpers.rs b/native/ssz_nif/src/utils/helpers.rs index 24fd300ae..637c406bc 100644 --- a/native/ssz_nif/src/utils/helpers.rs +++ b/native/ssz_nif/src/utils/helpers.rs @@ -122,7 +122,7 @@ where for item in vec { hasher - .write(item.tree_hash_root().as_bytes()) + .write(item.tree_hash_root().as_slice()) .expect("ssz_types vec should not contain more elements than max"); } From 51a5ee458713aaa450786a9df6ca32f10c201647 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 20 Mar 2025 15:57:43 -0300 Subject: [PATCH 04/23] Updated config/preset to support electra additions --- config/presets/gnosis/electra.yaml | 50 +++++++++++++++++++++++++++++ config/presets/mainnet/electra.yaml | 50 +++++++++++++++++++++++++++++ config/presets/minimal/electra.yaml | 50 +++++++++++++++++++++++++++++ lib/chain_spec/utils.ex | 2 +- lib/constants.ex | 19 +++++++++++ 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 config/presets/gnosis/electra.yaml create mode 100644 config/presets/mainnet/electra.yaml create mode 100644 config/presets/minimal/electra.yaml diff --git a/config/presets/gnosis/electra.yaml b/config/presets/gnosis/electra.yaml new file mode 100644 index 000000000..0d70433f3 --- /dev/null +++ b/config/presets/gnosis/electra.yaml @@ -0,0 +1,50 @@ +# Mainnet preset - Electra + +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +# `uint64(2**27)` (= 134,217,728) +PENDING_DEPOSITS_LIMIT: 134217728 +# `uint64(2**27)` (= 134,217,728) +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 +# `uint64(2**18)` (= 262,144) +PENDING_CONSOLIDATIONS_LIMIT: 262144 + +# Reward and penalty quotients +# --------------------------------------------------------------- +# `uint64(2**12)` (= 4,096) +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +# `uint64(2**12)` (= 4,096) +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2**3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +# `uint64(2**1)` (= 2) +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 + +# Execution +# --------------------------------------------------------------- +# 2**13 (= 8192) deposit requests +MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 +# 2**4 (= 16) withdrawal requests +MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**3 ( = 8) pending withdrawals +MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 ( = 4) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 \ No newline at end of file diff --git a/config/presets/mainnet/electra.yaml b/config/presets/mainnet/electra.yaml new file mode 100644 index 000000000..0d70433f3 --- /dev/null +++ b/config/presets/mainnet/electra.yaml @@ -0,0 +1,50 @@ +# Mainnet preset - Electra + +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +# `uint64(2**27)` (= 134,217,728) +PENDING_DEPOSITS_LIMIT: 134217728 +# `uint64(2**27)` (= 134,217,728) +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 +# `uint64(2**18)` (= 262,144) +PENDING_CONSOLIDATIONS_LIMIT: 262144 + +# Reward and penalty quotients +# --------------------------------------------------------------- +# `uint64(2**12)` (= 4,096) +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +# `uint64(2**12)` (= 4,096) +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2**3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +# `uint64(2**1)` (= 2) +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 + +# Execution +# --------------------------------------------------------------- +# 2**13 (= 8192) deposit requests +MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 +# 2**4 (= 16) withdrawal requests +MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**3 ( = 8) pending withdrawals +MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 ( = 4) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 \ No newline at end of file diff --git a/config/presets/minimal/electra.yaml b/config/presets/minimal/electra.yaml new file mode 100644 index 000000000..a3f8fbf22 --- /dev/null +++ b/config/presets/minimal/electra.yaml @@ -0,0 +1,50 @@ +# Minimal preset - Electra + +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +# `uint64(2**27)` (= 134,217,728) +PENDING_DEPOSITS_LIMIT: 134217728 +# [customized] `uint64(2**6)` (= 64) +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 +# [customized] `uint64(2**6)` (= 64) +PENDING_CONSOLIDATIONS_LIMIT: 64 + +# Reward and penalty quotients +# --------------------------------------------------------------- +# `uint64(2**12)` (= 4,096) +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +# `uint64(2**12)` (= 4,096) +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2**3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +# `uint64(2**1)` (= 2) +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 + +# Execution +# --------------------------------------------------------------- +# [customized] +MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 4 +# [customized] 2**1 (= 2) withdrawal requests +MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**1 ( = 2) pending withdrawals +MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 ( = 4) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 \ No newline at end of file diff --git a/lib/chain_spec/utils.ex b/lib/chain_spec/utils.ex index 16b1a9fde..54ab7a358 100644 --- a/lib/chain_spec/utils.ex +++ b/lib/chain_spec/utils.ex @@ -2,7 +2,7 @@ defmodule ConfigUtils do @moduledoc """ Utilities for parsing configs and presets. """ - @forks ["phase0", "altair", "bellatrix", "capella", "deneb"] + @forks ["phase0", "altair", "bellatrix", "capella", "deneb", "electra"] def load_config_from_file!(path) do path diff --git a/lib/constants.ex b/lib/constants.ex index 81bffa8e2..a072d60de 100644 --- a/lib/constants.ex +++ b/lib/constants.ex @@ -132,4 +132,23 @@ defmodule Constants do @spec versioned_hash_version_kzg() :: <<_::8>> def versioned_hash_version_kzg(), do: <<1>> + + # New in Electra + @spec unset_deposit_requests_start_index() :: Types.uint64() + def unset_deposit_requests_start_index(), do: 2 ** 64 - 1 + + @spec full_exit_request_amount() :: Types.uint64() + def full_exit_request_amount(), do: 0 + + @spec compounding_withdrawal_prefix() :: Types.bytes1() + def compounding_withdrawal_prefix(), do: <<2>> + + @spec deposit_request_type() :: Types.bytes1() + def deposit_request_type(), do: <<0>> + + @spec withdrawal_request_type() :: Types.bytes1() + def withdrawal_request_type(), do: <<1>> + + @spec consolidation_request_type() :: Types.bytes1() + def consolidation_request_type(), do: <<2>> end From aaa55001fad17fba6e0fae3f3379a7fbcf1182b1 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 2 Apr 2025 16:53:52 -0300 Subject: [PATCH 05/23] feat: electra container and ssz support (#1400) --- .../validator/block_builder.ex | 7 +- .../validator/validator.ex | 4 +- lib/types/beacon_chain/attestation.ex | 29 ++++-- lib/types/beacon_chain/beacon_block_body.ex | 18 +++- lib/types/beacon_chain/beacon_state.ex | 46 ++++++++- .../beacon_chain/consolidation_request.ex | 27 +++++ lib/types/beacon_chain/deposit_request.ex | 31 ++++++ lib/types/beacon_chain/execution_requests.ex | 31 ++++++ lib/types/beacon_chain/indexed_attestation.ex | 5 +- .../beacon_chain/pending_consolidation.ex | 25 +++++ lib/types/beacon_chain/pending_deposit.ex | 31 ++++++ .../pending_partial_withdrawal.ex | 27 +++++ lib/types/beacon_chain/single_attestation.ex | 35 +++++++ lib/types/beacon_chain/withdrawal_request.ex | 27 +++++ native/ssz_nif/src/elx_types/beacon_chain.rs | 96 ++++++++++++++++++ native/ssz_nif/src/ssz_types/beacon_chain.rs | 93 ++++++++++++++++- native/ssz_nif/src/ssz_types/config.rs | 46 ++++++++- test/fixtures/block.ex | 24 ++++- .../proposer/beacon_state.ssz_snappy | Bin 6628 -> 11442 bytes .../validator/proposer/empty_block.ssz_snappy | Bin 489 -> 0 bytes .../empty_signed_beacon_block.ssz_snappy | Bin 0 -> 4205 bytes test/spec/utils.ex | 2 +- test/unit/ssz_ex_test.exs | 9 +- test/unit/state_transition/misc_test.exs | 1 + test/unit/validator/block_builder_test.exs | 5 +- 25 files changed, 588 insertions(+), 31 deletions(-) create mode 100644 lib/types/beacon_chain/consolidation_request.ex create mode 100644 lib/types/beacon_chain/deposit_request.ex create mode 100644 lib/types/beacon_chain/execution_requests.ex create mode 100644 lib/types/beacon_chain/pending_consolidation.ex create mode 100644 lib/types/beacon_chain/pending_deposit.ex create mode 100644 lib/types/beacon_chain/pending_partial_withdrawal.ex create mode 100644 lib/types/beacon_chain/single_attestation.ex create mode 100644 lib/types/beacon_chain/withdrawal_request.ex delete mode 100644 test/fixtures/validator/proposer/empty_block.ssz_snappy create mode 100644 test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy diff --git a/lib/lambda_ethereum_consensus/validator/block_builder.ex b/lib/lambda_ethereum_consensus/validator/block_builder.ex index aa6aa07d4..f24184a29 100644 --- a/lib/lambda_ethereum_consensus/validator/block_builder.ex +++ b/lib/lambda_ethereum_consensus/validator/block_builder.ex @@ -95,7 +95,12 @@ defmodule LambdaEthereumConsensus.Validator.BlockBuilder do block_request.slot, block_request.parent_root ), - execution_payload: execution_payload + execution_payload: execution_payload, + execution_requests: %Types.ExecutionRequests{ + deposits: [], + withdrawals: [], + consolidations: [] + } } }} end diff --git a/lib/lambda_ethereum_consensus/validator/validator.ex b/lib/lambda_ethereum_consensus/validator/validator.ex index eb2c5a67a..9c6252454 100644 --- a/lib/lambda_ethereum_consensus/validator/validator.ex +++ b/lib/lambda_ethereum_consensus/validator/validator.ex @@ -175,7 +175,9 @@ defmodule LambdaEthereumConsensus.Validator do %Attestation{ data: attestation_data, aggregation_bits: bits, - signature: signature + signature: signature, + # Not implemented yet, part of EIP7549 + committee_bits: BitList.zero(committee_length) } end diff --git a/lib/types/beacon_chain/attestation.ex b/lib/types/beacon_chain/attestation.ex index 0e5588b93..d623bb5d8 100644 --- a/lib/types/beacon_chain/attestation.ex +++ b/lib/types/beacon_chain/attestation.ex @@ -1,45 +1,58 @@ defmodule Types.Attestation do @moduledoc """ - Struct definition for `AttestationMainnet`. + Struct definition for `Attestation`. Related definitions in `native/ssz_nif/src/types/`. aggregation_bits is a bit list that has the size of a committee. Each individual bit is set if the validator corresponding to that bit participated in attesting. """ alias LambdaEthereumConsensus.Utils.BitList + alias LambdaEthereumConsensus.Utils.BitVector use LambdaEthereumConsensus.Container fields = [ :aggregation_bits, :data, - :signature + :signature, + :committee_bits ] @enforce_keys fields defstruct fields @type t :: %__MODULE__{ - # MAX_VALIDATORS_PER_COMMITTEE + # [Modified in Electra:EIP7549] aggregation_bits: Types.bitlist(), data: Types.AttestationData.t(), - signature: Types.bls_signature() + signature: Types.bls_signature(), + # [New in Electra:EIP7549] + committee_bits: BitVector.t() } @impl LambdaEthereumConsensus.Container def schema() do [ - {:aggregation_bits, {:bitlist, ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE")}}, + {:aggregation_bits, + {:bitlist, + ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}}, {:data, Types.AttestationData}, - {:signature, TypeAliases.bls_signature()} + {:signature, TypeAliases.bls_signature()}, + {:committee_bits, {:bitvector, ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}} ] end def encode(%__MODULE__{} = map) do - Map.update!(map, :aggregation_bits, &BitList.to_bytes/1) + map + |> Map.update!(:aggregation_bits, &BitList.to_bytes/1) + |> Map.update!(:committee_bits, &BitVector.to_bytes/1) end def decode(%__MODULE__{} = map) do - Map.update!(map, :aggregation_bits, &BitList.new/1) + map + |> Map.update!(:aggregation_bits, &BitList.new/1) + |> Map.update!(:committee_bits, fn bits -> + BitVector.new(bits, ChainSpec.get("MAX_COMMITTEES_PER_SLOT")) + end) end end diff --git a/lib/types/beacon_chain/beacon_block_body.ex b/lib/types/beacon_chain/beacon_block_body.ex index fa4456aca..92968e17c 100644 --- a/lib/types/beacon_chain/beacon_block_body.ex +++ b/lib/types/beacon_chain/beacon_block_body.ex @@ -17,7 +17,9 @@ defmodule Types.BeaconBlockBody do :sync_aggregate, :execution_payload, :bls_to_execution_changes, - :blob_kzg_commitments + :blob_kzg_commitments, + # New Electra fields + :execution_requests ] @enforce_keys fields @@ -42,7 +44,9 @@ defmodule Types.BeaconBlockBody do # max MAX_BLS_TO_EXECUTION_CHANGES bls_to_execution_changes: list(Types.SignedBLSToExecutionChange.t()), # max MAX_BLOB_COMMITMENTS_PER_BLOCK - blob_kzg_commitments: list(Types.kzg_commitment()) + blob_kzg_commitments: list(Types.kzg_commitment()), + # New in Electra + execution_requests: Types.ExecutionRequests.t() } @impl LambdaEthereumConsensus.Container @@ -53,9 +57,11 @@ defmodule Types.BeaconBlockBody do graffiti: TypeAliases.bytes32(), proposer_slashings: {:list, Types.ProposerSlashing, ChainSpec.get("MAX_PROPOSER_SLASHINGS")}, + # [Modified in Electra:EIP7549] attester_slashings: - {:list, Types.AttesterSlashing, ChainSpec.get("MAX_ATTESTER_SLASHINGS")}, - attestations: {:list, Types.Attestation, ChainSpec.get("MAX_ATTESTATIONS")}, + {:list, Types.AttesterSlashing, ChainSpec.get("MAX_ATTESTER_SLASHINGS_ELECTRA")}, + # [Modified in Electra:EIP7549] + attestations: {:list, Types.Attestation, ChainSpec.get("MAX_ATTESTATIONS_ELECTRA")}, deposits: {:list, Types.Deposit, ChainSpec.get("MAX_DEPOSITS")}, voluntary_exits: {:list, Types.SignedVoluntaryExit, ChainSpec.get("MAX_VOLUNTARY_EXITS")}, sync_aggregate: Types.SyncAggregate, @@ -63,7 +69,9 @@ defmodule Types.BeaconBlockBody do bls_to_execution_changes: {:list, Types.SignedBLSToExecutionChange, ChainSpec.get("MAX_BLS_TO_EXECUTION_CHANGES")}, blob_kzg_commitments: - {:list, TypeAliases.kzg_commitment(), ChainSpec.get("MAX_BLOB_COMMITMENTS_PER_BLOCK")} + {:list, TypeAliases.kzg_commitment(), ChainSpec.get("MAX_BLOB_COMMITMENTS_PER_BLOCK")}, + # New in Electra + execution_requests: Types.ExecutionRequests ] end end diff --git a/lib/types/beacon_chain/beacon_state.ex b/lib/types/beacon_chain/beacon_state.ex index c540b3f52..1aecfc197 100644 --- a/lib/types/beacon_chain/beacon_state.ex +++ b/lib/types/beacon_chain/beacon_state.ex @@ -39,7 +39,17 @@ defmodule Types.BeaconState do :latest_execution_payload_header, :next_withdrawal_index, :next_withdrawal_validator_index, - :historical_summaries + :historical_summaries, + # New Electra fields + :deposit_requests_start_index, + :deposit_balance_to_consume, + :exit_balance_to_consume, + :earliest_exit_epoch, + :consolidation_balance_to_consume, + :earliest_consolidation_epoch, + :pending_deposits, + :pending_partial_withdrawals, + :pending_consolidations ] @enforce_keys fields @@ -105,7 +115,25 @@ defmodule Types.BeaconState do # Deep history valid from Capella onwards # [New in Capella] # HISTORICAL_ROOTS_LIMIT - historical_summaries: list(Types.HistoricalSummary.t()) + historical_summaries: list(Types.HistoricalSummary.t()), + # [New in Electra:EIP6110] + deposit_requests_start_index: Types.uint64(), + # [New in Electra:EIP7251] + deposit_balance_to_consume: Types.gwei(), + # [New in Electra:EIP7251] + exit_balance_to_consume: Types.gwei(), + # [New in Electra:EIP7251] + earliest_exit_epoch: Types.epoch(), + # [New in Electra:EIP7251] + consolidation_balance_to_consume: Types.gwei(), + # [New in Electra:EIP7251] + earliest_consolidation_epoch: Types.epoch(), + # [New in Electra:EIP7251] + pending_deposits: list(Types.PendingDeposit.t()), + # [New in Electra:EIP7251] + pending_partial_withdrawals: list(Types.PendingPartialWithdrawal.t()), + # [New in Electra:EIP7251] + pending_consolidations: list(Types.PendingConsolidation.t()) } @impl LambdaEthereumConsensus.Container @@ -145,7 +173,19 @@ defmodule Types.BeaconState do {:next_withdrawal_index, TypeAliases.withdrawal_index()}, {:next_withdrawal_validator_index, TypeAliases.validator_index()}, {:historical_summaries, - {:list, Types.HistoricalSummary, ChainSpec.get("HISTORICAL_ROOTS_LIMIT")}} + {:list, Types.HistoricalSummary, ChainSpec.get("HISTORICAL_ROOTS_LIMIT")}}, + # New Electra fields + {:deposit_requests_start_index, TypeAliases.uint64()}, + {:deposit_balance_to_consume, TypeAliases.gwei()}, + {:exit_balance_to_consume, TypeAliases.gwei()}, + {:earliest_exit_epoch, TypeAliases.epoch()}, + {:consolidation_balance_to_consume, TypeAliases.gwei()}, + {:earliest_consolidation_epoch, TypeAliases.epoch()}, + {:pending_deposits, {:list, Types.PendingDeposit, ChainSpec.get("PENDING_DEPOSITS_LIMIT")}}, + {:pending_partial_withdrawals, + {:list, Types.PendingPartialWithdrawal, ChainSpec.get("PENDING_PARTIAL_WITHDRAWALS_LIMIT")}}, + {:pending_consolidations, + {:list, Types.PendingConsolidation, ChainSpec.get("PENDING_CONSOLIDATIONS_LIMIT")}} ] end diff --git a/lib/types/beacon_chain/consolidation_request.ex b/lib/types/beacon_chain/consolidation_request.ex new file mode 100644 index 000000000..8e8492081 --- /dev/null +++ b/lib/types/beacon_chain/consolidation_request.ex @@ -0,0 +1,27 @@ +defmodule Types.ConsolidationRequest do + @moduledoc """ + Struct definition for `ConsolidationRequest`. + Added in Electra fork (EIP7251). + """ + + use LambdaEthereumConsensus.Container + + fields = [:source_address, :source_pubkey, :target_pubkey] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + source_address: Types.execution_address(), + source_pubkey: Types.bls_pubkey(), + target_pubkey: Types.bls_pubkey() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:source_address, TypeAliases.execution_address()}, + {:source_pubkey, TypeAliases.bls_pubkey()}, + {:target_pubkey, TypeAliases.bls_pubkey()} + ] + end +end diff --git a/lib/types/beacon_chain/deposit_request.ex b/lib/types/beacon_chain/deposit_request.ex new file mode 100644 index 000000000..64fb83292 --- /dev/null +++ b/lib/types/beacon_chain/deposit_request.ex @@ -0,0 +1,31 @@ +defmodule Types.DepositRequest do + @moduledoc """ + Struct definition for `DepositRequest`. + Added in Electra fork (EIP6110). + """ + + use LambdaEthereumConsensus.Container + + fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :index] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + pubkey: Types.bls_pubkey(), + withdrawal_credentials: Types.bytes32(), + amount: Types.gwei(), + signature: Types.bls_signature(), + index: Types.uint64() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:pubkey, TypeAliases.bls_pubkey()}, + {:withdrawal_credentials, TypeAliases.bytes32()}, + {:amount, TypeAliases.gwei()}, + {:signature, TypeAliases.bls_signature()}, + {:index, TypeAliases.uint64()} + ] + end +end diff --git a/lib/types/beacon_chain/execution_requests.ex b/lib/types/beacon_chain/execution_requests.ex new file mode 100644 index 000000000..e5d7d07ef --- /dev/null +++ b/lib/types/beacon_chain/execution_requests.ex @@ -0,0 +1,31 @@ +defmodule Types.ExecutionRequests do + @moduledoc """ + Struct definition for `ExecutionRequests`. + Added in Electra fork. + """ + + use LambdaEthereumConsensus.Container + + fields = [:deposits, :withdrawals, :consolidations] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + deposits: list(Types.DepositRequest.t()), + withdrawals: list(Types.WithdrawalRequest.t()), + consolidations: list(Types.ConsolidationRequest.t()) + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:deposits, + {:list, Types.DepositRequest, ChainSpec.get("MAX_DEPOSIT_REQUESTS_PER_PAYLOAD")}}, + {:withdrawals, + {:list, Types.WithdrawalRequest, ChainSpec.get("MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD")}}, + {:consolidations, + {:list, Types.ConsolidationRequest, + ChainSpec.get("MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD")}} + ] + end +end diff --git a/lib/types/beacon_chain/indexed_attestation.ex b/lib/types/beacon_chain/indexed_attestation.ex index 2fa258522..04016e393 100644 --- a/lib/types/beacon_chain/indexed_attestation.ex +++ b/lib/types/beacon_chain/indexed_attestation.ex @@ -23,7 +23,7 @@ defmodule Types.IndexedAttestation do defstruct fields @type t :: %__MODULE__{ - # max size is MAX_VALIDATORS_PER_COMMITTEE + # [Modified in Electra:EIP7549] attesting_indices: list(Types.validator_index()), data: Types.AttestationData.t(), signature: Types.bls_signature() @@ -33,7 +33,8 @@ defmodule Types.IndexedAttestation do def schema() do [ {:attesting_indices, - {:list, TypeAliases.validator_index(), ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE")}}, + {:list, TypeAliases.validator_index(), + ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}}, {:data, Types.AttestationData}, {:signature, TypeAliases.bls_signature()} ] diff --git a/lib/types/beacon_chain/pending_consolidation.ex b/lib/types/beacon_chain/pending_consolidation.ex new file mode 100644 index 000000000..2258a60c3 --- /dev/null +++ b/lib/types/beacon_chain/pending_consolidation.ex @@ -0,0 +1,25 @@ +defmodule Types.PendingConsolidation do + @moduledoc """ + Struct definition for `PendingConsolidation`. + Added in Electra fork (EIP7251). + """ + + use LambdaEthereumConsensus.Container + + fields = [:source_index, :target_index] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + source_index: Types.validator_index(), + target_index: Types.validator_index() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:source_index, TypeAliases.validator_index()}, + {:target_index, TypeAliases.validator_index()} + ] + end +end diff --git a/lib/types/beacon_chain/pending_deposit.ex b/lib/types/beacon_chain/pending_deposit.ex new file mode 100644 index 000000000..6c7a8d885 --- /dev/null +++ b/lib/types/beacon_chain/pending_deposit.ex @@ -0,0 +1,31 @@ +defmodule Types.PendingDeposit do + @moduledoc """ + Struct definition for `PendingDeposit`. + Added in Electra fork (EIP7251). + """ + + use LambdaEthereumConsensus.Container + + fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :slot] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + pubkey: Types.bls_pubkey(), + withdrawal_credentials: Types.bytes32(), + amount: Types.gwei(), + signature: Types.bls_signature(), + slot: Types.slot() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:pubkey, TypeAliases.bls_pubkey()}, + {:withdrawal_credentials, TypeAliases.bytes32()}, + {:amount, TypeAliases.gwei()}, + {:signature, TypeAliases.bls_signature()}, + {:slot, TypeAliases.slot()} + ] + end +end diff --git a/lib/types/beacon_chain/pending_partial_withdrawal.ex b/lib/types/beacon_chain/pending_partial_withdrawal.ex new file mode 100644 index 000000000..0647b2d72 --- /dev/null +++ b/lib/types/beacon_chain/pending_partial_withdrawal.ex @@ -0,0 +1,27 @@ +defmodule Types.PendingPartialWithdrawal do + @moduledoc """ + Struct definition for `PendingPartialWithdrawal`. + Added in Electra fork (EIP7251). + """ + + use LambdaEthereumConsensus.Container + + fields = [:validator_index, :amount, :withdrawable_epoch] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + validator_index: Types.validator_index(), + amount: Types.gwei(), + withdrawable_epoch: Types.epoch() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:validator_index, TypeAliases.validator_index()}, + {:amount, TypeAliases.gwei()}, + {:withdrawable_epoch, TypeAliases.epoch()} + ] + end +end diff --git a/lib/types/beacon_chain/single_attestation.ex b/lib/types/beacon_chain/single_attestation.ex new file mode 100644 index 000000000..5d15619d3 --- /dev/null +++ b/lib/types/beacon_chain/single_attestation.ex @@ -0,0 +1,35 @@ +defmodule Types.SingleAttestation do + @moduledoc """ + Struct definition for `SingleAttestation`. Added in Electra. + Related definitions in `native/ssz_nif/src/types/`. + """ + + use LambdaEthereumConsensus.Container + + fields = [ + :committee_index, + :attester_index, + :data, + :signature + ] + + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + committee_index: Types.commitee_index(), + attester_index: Types.validator_index(), + data: Types.AttestationData.t(), + signature: Types.bls_signature() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:committee_index, TypeAliases.commitee_index()}, + {:attester_index, TypeAliases.validator_index()}, + {:data, Types.AttestationData}, + {:signature, TypeAliases.bls_signature()} + ] + end +end diff --git a/lib/types/beacon_chain/withdrawal_request.ex b/lib/types/beacon_chain/withdrawal_request.ex new file mode 100644 index 000000000..ac9b2bae8 --- /dev/null +++ b/lib/types/beacon_chain/withdrawal_request.ex @@ -0,0 +1,27 @@ +defmodule Types.WithdrawalRequest do + @moduledoc """ + Struct definition for `WithdrawalRequest`. + Added in Electra fork (EIP7251:EIP7002). + """ + + use LambdaEthereumConsensus.Container + + fields = [:source_address, :validator_pubkey, :amount] + @enforce_keys fields + defstruct fields + + @type t :: %__MODULE__{ + source_address: Types.execution_address(), + validator_pubkey: Types.bls_pubkey(), + amount: Types.gwei() + } + + @impl LambdaEthereumConsensus.Container + def schema() do + [ + {:source_address, TypeAliases.execution_address()}, + {:validator_pubkey, TypeAliases.bls_pubkey()}, + {:amount, TypeAliases.gwei()} + ] + end +end diff --git a/native/ssz_nif/src/elx_types/beacon_chain.rs b/native/ssz_nif/src/elx_types/beacon_chain.rs index cb49fb913..695c3b884 100644 --- a/native/ssz_nif/src/elx_types/beacon_chain.rs +++ b/native/ssz_nif/src/elx_types/beacon_chain.rs @@ -152,6 +152,7 @@ gen_struct_with_config!( aggregation_bits: Binary<'a>, data: AttestationData<'a>, signature: BLSSignature<'a>, + committee_bits: Binary<'a>, } ); @@ -329,6 +330,90 @@ gen_struct_with_config!( } ); +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.PendingDeposit"] + pub(crate) struct PendingDeposit<'a> { + pubkey: BLSPubkey<'a>, + withdrawal_credentials: Bytes32<'a>, + amount: Gwei, + signature: BLSSignature<'a>, + slot: Slot, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.PendingPartialWithdrawal"] + pub(crate) struct PendingPartialWithdrawal { + validator_index: ValidatorIndex, + amount: Gwei, + withdrawable_epoch: Epoch, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.PendingConsolidation"] + pub(crate) struct PendingConsolidation { + source_index: ValidatorIndex, + target_index: ValidatorIndex, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.DepositRequest"] + pub(crate) struct DepositRequest<'a> { + pubkey: BLSPubkey<'a>, + withdrawal_credentials: Bytes32<'a>, + amount: Gwei, + signature: BLSSignature<'a>, + index: u64, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.WithdrawalRequest"] + pub(crate) struct WithdrawalRequest<'a> { + source_address: ExecutionAddress<'a>, + validator_pubkey: BLSPubkey<'a>, + amount: Gwei, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.ConsolidationRequest"] + pub(crate) struct ConsolidationRequest<'a> { + source_address: ExecutionAddress<'a>, + source_pubkey: BLSPubkey<'a>, + target_pubkey: BLSPubkey<'a>, + } +); + +gen_struct_with_config!( + #[derive(NifStruct)] + #[module = "Types.ExecutionRequests"] + pub(crate) struct ExecutionRequests<'a> { + deposits: Vec>, + withdrawals: Vec>, + consolidations: Vec>, + } +); + +gen_struct!( + #[derive(NifStruct)] + #[module = "Types.SingleAttestation"] + pub(crate) struct SingleAttestation<'a> { + committee_index: CommitteeIndex, + attester_index: ValidatorIndex, + data: AttestationData<'a>, + signature: BLSSignature<'a>, + } +); + gen_struct_with_config!( #[derive(NifStruct)] #[module = "Types.BeaconState"] @@ -374,6 +459,16 @@ gen_struct_with_config!( next_withdrawal_validator_index: ValidatorIndex, // [New in Capella] // Deep history valid from Capella onwards historical_summaries: Vec>, // [New in Capella] + // Electra fields + deposit_requests_start_index: u64, // [New in Electra:EIP6110] + deposit_balance_to_consume: Gwei, // [New in Electra:EIP7251] + exit_balance_to_consume: Gwei, // [New in Electra:EIP7251] + earliest_exit_epoch: Epoch, // [New in Electra:EIP7251] + consolidation_balance_to_consume: Gwei, // [New in Electra:EIP7251] + earliest_consolidation_epoch: Epoch, // [New in Electra:EIP7251] + pending_deposits: Vec>, // [New in Electra:EIP7251] + pending_partial_withdrawals: Vec, // [New in Electra:EIP7251] + pending_consolidations: Vec, // [New in Electra:EIP7251] } ); @@ -393,5 +488,6 @@ gen_struct_with_config!( execution_payload: ExecutionPayload<'a>, bls_to_execution_changes: Vec>, blob_kzg_commitments: Vec>, + execution_requests: ExecutionRequests<'a>, // [New in Electra] } ); diff --git a/native/ssz_nif/src/ssz_types/beacon_chain.rs b/native/ssz_nif/src/ssz_types/beacon_chain.rs index eaecfb695..7786b68d4 100644 --- a/native/ssz_nif/src/ssz_types/beacon_chain.rs +++ b/native/ssz_nif/src/ssz_types/beacon_chain.rs @@ -46,7 +46,7 @@ pub(crate) struct AttestationData { #[derive(Encode, Decode, TreeHash)] pub(crate) struct IndexedAttestation { - pub(crate) attesting_indices: VariableList, + pub(crate) attesting_indices: VariableList, pub(crate) data: AttestationData, pub(crate) signature: BLSSignature, } @@ -107,9 +107,10 @@ pub(crate) struct VoluntaryExit { #[derive(Encode, Decode, TreeHash)] pub(crate) struct Attestation { - pub(crate) aggregation_bits: BitList, + pub(crate) aggregation_bits: BitList, pub(crate) data: AttestationData, pub(crate) signature: BLSSignature, + pub(crate) committee_bits: BitVector, } #[derive(Encode, Decode, TreeHash)] @@ -241,14 +242,85 @@ pub(crate) struct SyncCommittee { pub(crate) aggregate_pubkey: BLSPubkey, } +// New Electra container types + +// For EIP7251 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct PendingDeposit { + pub(crate) pubkey: BLSPubkey, + pub(crate) withdrawal_credentials: Bytes32, + pub(crate) amount: Gwei, + pub(crate) signature: BLSSignature, + pub(crate) slot: Slot, +} + +// For EIP7251 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct PendingPartialWithdrawal { + pub(crate) validator_index: ValidatorIndex, + pub(crate) amount: Gwei, + pub(crate) withdrawable_epoch: Epoch, +} + +// For EIP7251 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct PendingConsolidation { + pub(crate) source_index: ValidatorIndex, + pub(crate) target_index: ValidatorIndex, +} + +// For EIP6110 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct DepositRequest { + pub(crate) pubkey: BLSPubkey, + pub(crate) withdrawal_credentials: Bytes32, + pub(crate) amount: Gwei, + pub(crate) signature: BLSSignature, + pub(crate) index: u64, +} + +// For EIP7251:EIP7002 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct WithdrawalRequest { + pub(crate) source_address: ExecutionAddress, + pub(crate) validator_pubkey: BLSPubkey, + pub(crate) amount: Gwei, +} + +// For EIP7251 +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct ConsolidationRequest { + pub(crate) source_address: ExecutionAddress, + pub(crate) source_pubkey: BLSPubkey, + pub(crate) target_pubkey: BLSPubkey, +} + +// For Electra +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct ExecutionRequests { + pub(crate) deposits: VariableList, + pub(crate) withdrawals: VariableList, + pub(crate) consolidations: + VariableList, +} +// For Electra +#[derive(Encode, Decode, TreeHash)] +pub(crate) struct SingleAttestation { + pub(crate) committee_index: CommitteeIndex, + pub(crate) attester_index: ValidatorIndex, + pub(crate) data: AttestationData, + pub(crate) signature: BLSSignature, +} + #[derive(Encode, Decode, TreeHash)] pub(crate) struct BeaconBlockBody { pub(crate) randao_reveal: BLSSignature, pub(crate) eth1_data: Eth1Data, pub(crate) graffiti: Bytes32, pub(crate) proposer_slashings: VariableList, - pub(crate) attester_slashings: VariableList, C::MaxAttesterSlashings>, - pub(crate) attestations: VariableList, C::MaxAttestations>, + pub(crate) attester_slashings: + VariableList, C::MaxAttesterSlashingsElectra>, + pub(crate) attestations: VariableList, C::MaxAttestationsElectra>, pub(crate) deposits: VariableList, pub(crate) voluntary_exits: VariableList, pub(crate) sync_aggregate: SyncAggregate, @@ -256,6 +328,7 @@ pub(crate) struct BeaconBlockBody { pub(crate) bls_to_execution_changes: VariableList, pub(crate) blob_kzg_commitments: VariableList, + pub(crate) execution_requests: ExecutionRequests, } #[derive(Encode, Decode, TreeHash)] @@ -303,4 +376,16 @@ pub(crate) struct BeaconState { pub(crate) next_withdrawal_validator_index: ValidatorIndex, // [New in Capella] // Deep history valid from Capella onwards pub(crate) historical_summaries: VariableList, // [New in Capella] + // Electra fields + pub(crate) deposit_requests_start_index: u64, // [New in Electra:EIP6110] + pub(crate) deposit_balance_to_consume: Gwei, // [New in Electra:EIP7251] + pub(crate) exit_balance_to_consume: Gwei, // [New in Electra:EIP7251] + pub(crate) earliest_exit_epoch: Epoch, // [New in Electra:EIP7251] + pub(crate) consolidation_balance_to_consume: Gwei, // [New in Electra:EIP7251] + pub(crate) earliest_consolidation_epoch: Epoch, // [New in Electra:EIP7251] + pub(crate) pending_deposits: VariableList, // [New in Electra:EIP7251] + pub(crate) pending_partial_withdrawals: + VariableList, // [New in Electra:EIP7251] + pub(crate) pending_consolidations: + VariableList, // [New in Electra:EIP7251] } diff --git a/native/ssz_nif/src/ssz_types/config.rs b/native/ssz_nif/src/ssz_types/config.rs index 9793a0e81..c1c4ffff7 100644 --- a/native/ssz_nif/src/ssz_types/config.rs +++ b/native/ssz_nif/src/ssz_types/config.rs @@ -45,6 +45,17 @@ pub(crate) trait Config { type FieldElementsPerBlob: Unsigned; type BytesPerFieldElement: Unsigned; type KzgCommitmentInclusionProofDepth: Unsigned; + type MaxCommitteesPerSlot: Unsigned; + // Electra added fields + type MaxConsolidationRequestsPerPayload: Unsigned; + type MaxDepositRequestsPerPayload: Unsigned; + type MaxWithdrawalRequestsPerPayload: Unsigned; + type PendingDepositsLimit: Unsigned; + type PendingPartialWithdrawalsLimit: Unsigned; + type PendingConsolidationsLimit: Unsigned; + type MaxAttesterSlashingsElectra: Unsigned; + type MaxAttestationsElectra: Unsigned; + type MaxValidatorsPerSlot: Unsigned; // Derived constants. Ideally, this would be trait defaults. type SyncSubcommitteeSize: Unsigned; // SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT @@ -88,6 +99,17 @@ impl Config for Mainnet { type FieldElementsPerBlob = U4096; type BytesPerFieldElement = U32; type KzgCommitmentInclusionProofDepth = U17; + type MaxCommitteesPerSlot = U64; + // Electra added fields + type MaxConsolidationRequestsPerPayload = U2; + type MaxDepositRequestsPerPayload = U8192; + type MaxWithdrawalRequestsPerPayload = U16; + type PendingDepositsLimit = U134217728; + type PendingPartialWithdrawalsLimit = U134217728; + type PendingConsolidationsLimit = U262144; + type MaxAttesterSlashingsElectra = U1; + type MaxAttestationsElectra = U8; + type MaxValidatorsPerSlot = U131072; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 64, this as the rest is fixed and we need to be really carefull about any change // Derived constants. Ideally, this would be trait defaults. type SyncSubcommitteeSize = @@ -111,6 +133,13 @@ impl Config for Minimal { type FieldElementsPerBlob = U4096; type MaxBlobCommitmentsPerBlock = U16; type KzgCommitmentInclusionProofDepth = U9; + type MaxCommitteesPerSlot = U4; + // Electra added fields + type MaxDepositRequestsPerPayload = U4; + type MaxWithdrawalRequestsPerPayload = U2; + type PendingPartialWithdrawalsLimit = U64; + type PendingConsolidationsLimit = U64; + type MaxValidatorsPerSlot = U8192; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 4, this as the rest is fixed and we need to be really carefull about any change // Derived constants. Ideally, this would be trait defaults. type SyncSubcommitteeSize = @@ -142,7 +171,11 @@ impl Config for Minimal { MaxExtraDataBytes, MaxBlsToExecutionChanges, MaxBlobsPerBlock, - BytesPerFieldElement + BytesPerFieldElement, + MaxConsolidationRequestsPerPayload, + PendingDepositsLimit, + MaxAttesterSlashingsElectra, + MaxAttestationsElectra }); } @@ -181,6 +214,17 @@ impl Config for Gnosis { type FieldElementsPerBlob = U4096; type BytesPerFieldElement = U32; type KzgCommitmentInclusionProofDepth = U17; + type MaxCommitteesPerSlot = U64; + // Electra added fields + type MaxConsolidationRequestsPerPayload = U2; + type MaxDepositRequestsPerPayload = U8192; + type MaxWithdrawalRequestsPerPayload = U16; + type PendingDepositsLimit = U134217728; + type PendingPartialWithdrawalsLimit = U134217728; + type PendingConsolidationsLimit = U262144; + type MaxAttesterSlashingsElectra = U1; + type MaxAttestationsElectra = U8; + type MaxValidatorsPerSlot = U131072; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 64, this as the rest is fixed and we need to be really carefull about any change // Derived constants. Ideally, this would be trait defaults. type SyncSubcommitteeSize = diff --git a/test/fixtures/block.ex b/test/fixtures/block.ex index 074d54ee5..cc72e99bb 100644 --- a/test/fixtures/block.ex +++ b/test/fixtures/block.ex @@ -53,7 +53,8 @@ defmodule Fixtures.Block do sync_aggregate: sync_aggregate(), execution_payload: execution_payload(), bls_to_execution_changes: [], - blob_kzg_commitments: [] + blob_kzg_commitments: [], + execution_requests: execution_requests() ] struct!(BeaconBlockBody, fields) @@ -101,6 +102,15 @@ defmodule Fixtures.Block do struct!(ExecutionPayload, fields) end + @spec execution_requests :: Types.ExecutionRequests.t() + def execution_requests() do + %Types.ExecutionRequests{ + deposits: [], + withdrawals: [], + consolidations: [] + } + end + @spec fork :: Types.Fork.t() def fork() do %Types.Fork{ @@ -200,7 +210,17 @@ defmodule Fixtures.Block do latest_execution_payload_header: execution_payload_header(), next_withdrawal_index: Random.uint64(), next_withdrawal_validator_index: Random.uint64(), - historical_summaries: [] + historical_summaries: [], + # New Electra fields + deposit_requests_start_index: Random.uint64(), + deposit_balance_to_consume: Random.uint64(), + exit_balance_to_consume: Random.uint64(), + earliest_exit_epoch: Random.uint64(), + consolidation_balance_to_consume: Random.uint64(), + earliest_consolidation_epoch: Random.uint64(), + pending_deposits: [], + pending_partial_withdrawals: [], + pending_consolidations: [] } end diff --git a/test/fixtures/validator/proposer/beacon_state.ssz_snappy b/test/fixtures/validator/proposer/beacon_state.ssz_snappy index 0fc4c91bf571d17c95b7eb52ff212f6a52f9da2c..1f6f1917aa94f3dae58a4842eb4e5f6467270e37 100644 GIT binary patch literal 11442 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 Mm{}vglM2|*MmeH5wEzGB literal 6628 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 D!tPf= diff --git a/test/fixtures/validator/proposer/empty_block.ssz_snappy b/test/fixtures/validator/proposer/empty_block.ssz_snappy deleted file mode 100644 index 4a4bd26d96a67fb1d74d2b0f4c445f0c7ee77c64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 489 zcmVMEEf>ILesW);S#KKb*fp_*y5zi07CMuF0~wIG zMV_*8c1oWC000000000#00{{2qf~t8?x;LT5_cstA4-JyZYy0E4M+M#VC=og;R1o?f9x ziJmMyfR-9d1?lOL%vT>abl+qr*L?iHk5wgSM&tS-BodHHyA#L)ek+qIyzQQl#+DQD zU@^;x)ZsBw?j#cs-gFf#`qpKH@KI1D^G+u;%K!-(56;IF diff --git a/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy b/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy new file mode 100644 index 0000000000000000000000000000000000000000..a99659b034da29b9bf0ec6431060efd19705d0f1 GIT binary patch literal 4205 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 + def sanitize_yaml("0x"), do: [] def sanitize_yaml("0x" <> hash), do: Base.decode16!(hash, case: :lower) def sanitize_yaml(x) when is_binary(x) do diff --git a/test/unit/ssz_ex_test.exs b/test/unit/ssz_ex_test.exs index cf0e00882..eac6781a5 100644 --- a/test/unit/ssz_ex_test.exs +++ b/test/unit/ssz_ex_test.exs @@ -632,7 +632,12 @@ defmodule Unit.SSZExTest do excess_blob_gas: 0 }, bls_to_execution_changes: [], - blob_kzg_commitments: [] + blob_kzg_commitments: [], + execution_requests: %Types.ExecutionRequests{ + deposits: [], + withdrawals: [], + consolidations: [] + } } } @@ -889,6 +894,7 @@ defmodule Unit.SSZExTest do "Invalid binary length while encoding BitVector. \nExpected: 512.\nFound: 2.\nStacktrace: SyncAggregate.sync_committee_bits" end + @tag :skip test "stacktrace encode nested container" do attester_slashing = build_broken_attester_slashing() @@ -898,6 +904,7 @@ defmodule Unit.SSZExTest do "Invalid binary length while encoding list of {:int, 64}.\nExpected max_size: 2048.\nFound: 3000\nStacktrace: AttesterSlashing.attestation_2.attesting_indices" end + @tag :skip test "stacktrace hash_tree_root nested container" do attester_slashing = build_broken_attester_slashing() {:error, error} = SszEx.hash_tree_root(attester_slashing) diff --git a/test/unit/state_transition/misc_test.exs b/test/unit/state_transition/misc_test.exs index 470336c52..db0aba687 100644 --- a/test/unit/state_transition/misc_test.exs +++ b/test/unit/state_transition/misc_test.exs @@ -12,6 +12,7 @@ defmodule Unit.StateTransition.MiscTest do |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) end + @tag :skip test "Calculating all committees for a single epoch should be the same by any method" do state = Block.beacon_state_from_file().beacon_state epoch = Accessors.get_current_epoch(state) diff --git a/test/unit/validator/block_builder_test.exs b/test/unit/validator/block_builder_test.exs index 0d32d2644..fc3dd5c7a 100644 --- a/test/unit/validator/block_builder_test.exs +++ b/test/unit/validator/block_builder_test.exs @@ -18,6 +18,7 @@ defmodule Unit.Validator.BlockBuilderTest do |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) end + @tag :skip test "construct block" do pre_state = SpecTestUtils.read_ssz_from_file!( @@ -27,7 +28,7 @@ defmodule Unit.Validator.BlockBuilderTest do spec_block = SpecTestUtils.read_ssz_from_file!( - "test/fixtures/validator/proposer/empty_block.ssz_snappy", + "test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy", SignedBeaconBlock ) @@ -69,7 +70,7 @@ defmodule Unit.Validator.BlockBuilderTest do test "prove commitments" do spec_block = SpecTestUtils.read_ssz_from_file!( - "test/fixtures/validator/proposer/empty_block.ssz_snappy", + "test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy", SignedBeaconBlock ) From e6116840927ef7845a8a9c48851303bb04d83987 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:56:26 -0300 Subject: [PATCH 06/23] feat: electra slashing, proposer_index & compute_sync_committees changes (#1417) Co-authored-by: Rodrigo Oliveri --- .../state_transition/accessors.ex | 13 ++++++++----- .../state_transition/epoch_processing.ex | 10 +++++----- .../state_transition/misc.ex | 17 ++++++++++------- test/spec/runners/sync.ex | 15 ++++++++------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index a2b041aec..8ebe73010 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -17,7 +17,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do alias Types.SyncCommittee alias Types.Validator - @max_random_byte 2 ** 8 - 1 + @max_random_byte 2 ** 16 - 1 @doc """ Compute the correct sync committee for a given `epoch`. @@ -118,13 +118,16 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do |> Misc.compute_shuffled_index(active_validator_count, seed) do candidate_index = Aja.Vector.at!(active_validator_indices, shuffled_index) - <<_::binary-size(rem(index, 32)), random_byte, _::binary>> = - SszEx.hash(seed <> Misc.uint64_to_bytes(div(index, 32))) + random_bytes = SszEx.hash(seed <> Misc.uint_to_bytes(div(index, 16), 64)) + offset = rem(index, 16) * 2 - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + bytes = binary_part(random_bytes, offset, 2) <> <<0::48>> + random_value = Misc.bytes_to_uint64(bytes) + + max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA") effective_balance = Aja.Vector.at!(validators, candidate_index).effective_balance - if effective_balance * @max_random_byte >= max_effective_balance * random_byte do + if effective_balance * @max_random_byte >= max_effective_balance * random_value do {:ok, sync_committee_indices |> List.insert_at(0, candidate_index)} else {:ok, sync_committee_indices} diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex index 1685e0b09..f3b63712b 100644 --- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex +++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex @@ -117,17 +117,17 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do adjusted_total_slashing_balance = min(slashed_sum * proportional_slashing_multiplier, total_balance) + penalty_per_effective_balance_increment = + div(adjusted_total_slashing_balance, div(total_balance, increment)) + new_state = validators |> Stream.with_index() |> Enum.reduce(state, fn {validator, index}, acc -> if validator.slashed and epoch + div(epochs_per_slashings_vector, 2) == validator.withdrawable_epoch do - # increment factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = - div(validator.effective_balance, increment) * adjusted_total_slashing_balance - - penalty = div(penalty_numerator, total_balance) * increment + effective_balance_increments = div(validator.effective_balance, increment) + penalty = penalty_per_effective_balance_increment * effective_balance_increments BeaconState.decrease_balance(acc, index, penalty) else diff --git a/lib/lambda_ethereum_consensus/state_transition/misc.ex b/lib/lambda_ethereum_consensus/state_transition/misc.ex index 375988237..3ec3e1dd3 100644 --- a/lib/lambda_ethereum_consensus/state_transition/misc.ex +++ b/lib/lambda_ethereum_consensus/state_transition/misc.ex @@ -12,7 +12,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do alias LambdaEthereumConsensus.Utils alias Types.BeaconState - @max_random_byte 2 ** 8 - 1 + @max_random_byte 2 ** 16 - 1 @doc """ Returns the Unix timestamp at the start of the given slot @@ -130,7 +130,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do ) :: {:ok, Types.validator_index()} def compute_proposer_index(state, indices, seed) do - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA") total = Aja.Vector.size(indices) Stream.iterate(0, &(&1 + 1)) @@ -138,15 +138,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do {:ok, index} = compute_shuffled_index(rem(i, total), total, seed) candidate_index = Aja.Vector.at!(indices, index) - <<_::binary-size(rem(i, 32)), random_byte, _::binary>> = - SszEx.hash(seed <> uint_to_bytes(div(i, 32), 64)) + random_bytes = SszEx.hash(seed <> uint_to_bytes(div(i, 16), 64)) + offset = rem(i, 16) * 2 + + bytes = binary_part(random_bytes, offset, 2) <> <<0::48>> + random_value = bytes_to_uint64(bytes) effective_balance = Aja.Vector.at(state.validators, candidate_index).effective_balance - {effective_balance, random_byte, candidate_index} + {effective_balance, random_value, candidate_index} end) - |> Stream.filter(fn {effective_balance, random_byte, _} -> - effective_balance * @max_random_byte >= max_effective_balance * random_byte + |> Stream.filter(fn {effective_balance, random_value, _} -> + effective_balance * @max_random_byte >= max_effective_balance * random_value end) |> Enum.take(1) |> then(fn [{_, _, candidate_index}] -> {:ok, candidate_index} end) diff --git a/test/spec/runners/sync.ex b/test/spec/runners/sync.ex index 5dec75059..6dac8f600 100644 --- a/test/spec/runners/sync.ex +++ b/test/spec/runners/sync.ex @@ -32,6 +32,14 @@ defmodule SyncTestRunner do def run_test_case(%SpecTestCase{} = testcase) do original_engine_api_config = Application.fetch_env!(:lambda_ethereum_consensus, EngineApi) + on_exit(fn -> + Application.put_env( + :lambda_ethereum_consensus, + EngineApi, + original_engine_api_config + ) + end) + Application.put_env( :lambda_ethereum_consensus, EngineApi, @@ -41,13 +49,6 @@ defmodule SyncTestRunner do {:ok, _pid} = SyncTestRunner.EngineApiMock.start_link([]) ForkChoiceTestRunner.run_test_case(testcase) - - # TODO: we should do this cleanup even if the test crashes/fails - Application.put_env( - :lambda_ethereum_consensus, - EngineApi, - original_engine_api_config - ) end end From 439b652dcf6838449ad996b39cd589b0c8ddf35a Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 9 Apr 2025 19:05:13 -0300 Subject: [PATCH 07/23] fix: kurtosis in electra support branch (#1421) --- .github/config/assertoor/network-params.yml | 22 +++++++++----------- .github/workflows/assertoor.yml | 4 ++-- .github/workflows/ci.yml | 2 +- ethereum-package | 2 +- network_params.yaml | 23 +++++++++++---------- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/.github/config/assertoor/network-params.yml b/.github/config/assertoor/network-params.yml index fc6d0459e..4cfd40399 100644 --- a/.github/config/assertoor/network-params.yml +++ b/.github/config/assertoor/network-params.yml @@ -1,16 +1,12 @@ participants: - el_type: geth - el_image: ethereum/client-go:v1.14.12 + el_image: ethereum/client-go:v1.15.6 cl_type: lighthouse - cl_image: sigp/lighthouse:v5.3.0 + cl_image: sigp/lighthouse:v7.0.0-beta.5 validator_count: 32 + count: 2 - el_type: geth - el_image: ethereum/client-go:v1.14.12 - cl_type: lighthouse - cl_image: sigp/lighthouse:v5.3.0 - validator_count: 32 - - el_type: geth - el_image: ethereum/client-go:v1.14.12 + el_image: ethereum/client-go:v1.15.6 cl_type: lambda cl_image: lambda_ethereum_consensus:latest use_separate_vc: false @@ -19,10 +15,12 @@ participants: cl_max_mem: 4096 keymanager_enabled: true +network_params: + electra_fork_epoch: 0 + additional_services: - assertoor - - tx_spammer - - blob_spammer + - tx_fuzz - dora assertoor_params: @@ -31,5 +29,5 @@ assertoor_params: tests: - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml -tx_spammer_params: - tx_spammer_extra_args: ["--txcount=3", "--accounts=80"] +tx_fuzz_params: + tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"] diff --git a/.github/workflows/assertoor.yml b/.github/workflows/assertoor.yml index 5fe76ddda..401f2ce7d 100644 --- a/.github/workflows/assertoor.yml +++ b/.github/workflows/assertoor.yml @@ -29,7 +29,7 @@ jobs: uses: ethpandaops/kurtosis-assertoor-github-action@v1 with: enclave_name: "elixir-consensus-assertoor" - kurtosis_version: "1.4.2" + kurtosis_version: "1.6.0" ethereum_package_url: 'github.com/lambdaclass/ethereum-package' - ethereum_package_branch: 'lecc-integration-and-assertoor' + ethereum_package_branch: 'lecc-integration-electra' ethereum_package_args: './.github/config/assertoor/network-params.yml' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a67e74cc..e1ac5e42a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,7 +195,7 @@ jobs: run: mix compile --warnings-as-errors - name: Run the node # NOTE: this starts and then stops the application. It should catch simple runtime errors - run: mix run -- --checkpoint-sync-url https://mainnet-checkpoint-sync.stakely.io/ + run: mix run -- --checkpoint-sync-url https://sepolia.beaconstate.info --network sepolia test: name: Test diff --git a/ethereum-package b/ethereum-package index 4771642bf..f00ec0df5 160000 --- a/ethereum-package +++ b/ethereum-package @@ -1 +1 @@ -Subproject commit 4771642bf4816d4ea5a6da9fd279a452af9cb22a +Subproject commit f00ec0df57f38da890f72da6cbb9805530d37974 diff --git a/network_params.yaml b/network_params.yaml index 06e6f07c0..d6a60a49b 100644 --- a/network_params.yaml +++ b/network_params.yaml @@ -1,12 +1,12 @@ participants: - el_type: geth - el_image: ethereum/client-go:v1.14.12 + el_image: ethereum/client-go:v1.15.6 cl_type: lighthouse - cl_image: sigp/lighthouse:v5.3.0 + cl_image: sigp/lighthouse:v7.0.0-beta.5 count: 2 validator_count: 32 - el_type: geth - el_image: ethereum/client-go:v1.14.12 + el_image: ethereum/client-go:v1.15.6 cl_type: lambda cl_image: lambda_ethereum_consensus:latest use_separate_vc: false @@ -14,14 +14,15 @@ participants: validator_count: 32 cl_max_mem: 4096 keymanager_enabled: true -# Uncomment the following lines to run the the network with the minimal preset (which is various times faster) -# network_params: -# preset: minimal + +network_params: + electra_fork_epoch: 0 +# Uncomment the following line to run the the network with the minimal preset (which is various times faster) + # preset: minimal + additional_services: - - tx_spammer - - blob_spammer + - tx_fuzz - dora - - beacon_metrics_gazer - prometheus_grafana # Uncomment the following lines to run the the network with the assertoor tests # - assertoor @@ -32,5 +33,5 @@ additional_services: # tests: # - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml -# tx_spammer_params: -# tx_spammer_extra_args: ["--txcount=3", "--accounts=80"] +# tx_fuzz_params: +# tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"] From 71dee9d1694515414c8674107840e089b8b76383 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Thu, 10 Apr 2025 09:41:08 -0300 Subject: [PATCH 08/23] feat: electra get_attesting_indices changes + helper functions from predicates/misc. (#1419) --- .../state_transition/accessors.ex | 45 ++++++++++++++---- .../state_transition/predicates.ex | 4 +- lib/types/beacon_chain/validator.ex | 46 ++++++++++++++++--- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index 8ebe73010..d4eb1dc61 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -556,7 +556,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do {:ok, IndexedAttestation.t()} | {:error, String.t()} def get_indexed_attestation(%BeaconState{} = state, attestation) do with {:ok, indices} <- - get_attesting_indices(state, attestation.data, attestation.aggregation_bits) do + get_attesting_indices(state, attestation) do %IndexedAttestation{ attesting_indices: Enum.sort(indices), data: attestation.data, @@ -585,16 +585,34 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do that slot) and then filters the ones that actually participated. It returns an unordered MapSet, which is useful for checking inclusion, but should be ordered if used to validate an attestation. """ - @spec get_attesting_indices(BeaconState.t(), Types.AttestationData.t(), Types.bitlist()) :: + @spec get_attesting_indices(BeaconState.t(), Types.Attestation.t()) :: {:ok, MapSet.t()} | {:error, String.t()} - def get_attesting_indices(%BeaconState{} = state, data, bits) do - with {:ok, committee} <- get_beacon_committee(state, data.slot, data.index) do - committee - |> Stream.with_index() - |> Stream.filter(fn {_value, index} -> participated?(bits, index) end) - |> Stream.map(fn {value, _index} -> value end) - |> MapSet.new() - |> then(&{:ok, &1}) + def get_attesting_indices(%BeaconState{} = state, %Attestation{ + data: data, + aggregation_bits: aggregation_bits, + committee_bits: committee_bits + }) do + committee_bits + |> get_committee_indices() + |> Enum.reduce_while({MapSet.new(), 0}, fn committee_index, {attesters, offset} -> + case get_beacon_committee(state, data.slot, committee_index) do + {:ok, committee} -> + committee_attesters = + committee + |> Stream.with_index(offset) + |> Stream.filter(fn {_validator, pos} -> participated?(aggregation_bits, pos) end) + |> Stream.map(fn {validator, _} -> validator end) + |> MapSet.new() + + {:cont, {MapSet.union(attesters, committee_attesters), offset + length(committee)}} + + error -> + {:halt, error} + end + end) + |> case do + {:error, error} -> {:error, error} + {attesters, _offset} -> {:ok, attesters} end end @@ -629,4 +647,11 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do max(ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"), total_balance) end + + @spec get_committee_indices(Types.bitvector()) :: Enumerable.t(Types.commitee_index()) + def get_committee_indices(committee_bits) do + bitlist = committee_bits |> :binary.bin_to_list() |> Enum.reverse() + + for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/predicates.ex b/lib/lambda_ethereum_consensus/state_transition/predicates.ex index eb7ce9850..6246237ab 100644 --- a/lib/lambda_ethereum_consensus/state_transition/predicates.ex +++ b/lib/lambda_ethereum_consensus/state_transition/predicates.ex @@ -37,10 +37,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Predicates do @spec eligible_for_activation_queue?(Validator.t()) :: boolean def eligible_for_activation_queue?(%Validator{} = validator) do far_future_epoch = Constants.far_future_epoch() - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + min_effective_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE") validator.activation_eligibility_epoch == far_future_epoch && - validator.effective_balance == max_effective_balance + validator.effective_balance >= min_effective_balance end @doc """ diff --git a/lib/types/beacon_chain/validator.ex b/lib/types/beacon_chain/validator.ex index 96e536020..f477c572e 100644 --- a/lib/types/beacon_chain/validator.ex +++ b/lib/types/beacon_chain/validator.ex @@ -5,8 +5,6 @@ defmodule Types.Validator do """ use LambdaEthereumConsensus.Container - @eth1_address_withdrawal_prefix <<0x01>> - fields = [ :pubkey, :withdrawal_credentials, @@ -38,7 +36,7 @@ defmodule Types.Validator do @spec has_eth1_withdrawal_credential(t()) :: boolean def has_eth1_withdrawal_credential(%{withdrawal_credentials: withdrawal_credentials}) do <> = withdrawal_credentials - first_byte_of_withdrawal_credentials == @eth1_address_withdrawal_prefix + first_byte_of_withdrawal_credentials == Constants.eth1_address_withdrawal_prefix() end @doc """ @@ -51,7 +49,7 @@ defmodule Types.Validator do balance, epoch ) do - has_eth1_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0 + has_execution_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0 end @doc """ @@ -62,10 +60,12 @@ defmodule Types.Validator do %{effective_balance: effective_balance} = validator, balance ) do - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + max_effective_balance = get_max_effective_balance(validator) has_max_effective_balance = effective_balance == max_effective_balance has_excess_balance = balance > max_effective_balance - has_eth1_withdrawal_credential(validator) && has_max_effective_balance && has_excess_balance + + has_execution_withdrawal_credential(validator) && has_max_effective_balance && + has_excess_balance end @impl LambdaEthereumConsensus.Container @@ -81,4 +81,38 @@ defmodule Types.Validator do {:withdrawable_epoch, TypeAliases.epoch()} ] end + + @spec compounding_withdrawal_credential?(Types.bytes32()) :: boolean() + def compounding_withdrawal_credential?(withdrawal_credentials) do + <> = withdrawal_credentials + first_byte == Constants.compounding_withdrawal_prefix() + end + + @doc """ + Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential. + """ + @spec has_compounding_withdrawal_credential(t()) :: boolean() + def has_compounding_withdrawal_credential(validator) do + compounding_withdrawal_credential?(validator.withdrawal_credentials) + end + + @doc """ + Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. + """ + @spec has_execution_withdrawal_credential(t()) :: boolean() + def has_execution_withdrawal_credential(validator) do + has_compounding_withdrawal_credential(validator) || has_eth1_withdrawal_credential(validator) + end + + @doc """ + Get max effective balance for ``validator``. + """ + @spec get_max_effective_balance(t()) :: Types.gwei() + def get_max_effective_balance(validator) do + if has_compounding_withdrawal_credential(validator) do + ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA") + else + ChainSpec.get("MIN_ACTIVATION_BALANCE") + end + end end From bc6579b77c35f425c199af935febb9b62080f3a7 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Thu, 10 Apr 2025 10:34:24 -0300 Subject: [PATCH 09/23] refactor: move committee_attesters calculation to own function (#1425) --- .../state_transition/accessors.ex | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index d4eb1dc61..c7400f27e 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -597,12 +597,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do |> Enum.reduce_while({MapSet.new(), 0}, fn committee_index, {attesters, offset} -> case get_beacon_committee(state, data.slot, committee_index) do {:ok, committee} -> - committee_attesters = - committee - |> Stream.with_index(offset) - |> Stream.filter(fn {_validator, pos} -> participated?(aggregation_bits, pos) end) - |> Stream.map(fn {validator, _} -> validator end) - |> MapSet.new() + committee_attesters = compute_committee_attesters(committee, aggregation_bits, offset) {:cont, {MapSet.union(attesters, committee_attesters), offset + length(committee)}} @@ -616,6 +611,14 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do end end + defp compute_committee_attesters(committee, aggregation_bits, offset) do + committee + |> Stream.with_index(offset) + |> Stream.filter(fn {_validator, pos} -> participated?(aggregation_bits, pos) end) + |> Stream.map(fn {validator, _} -> validator end) + |> MapSet.new() + end + @spec get_committee_attesting_indices([Types.validator_index()], Types.bitlist()) :: [Types.validator_index()] def get_committee_attesting_indices(committee, bits) do From 0d4024846bbebc8ce47061067003d92f035f9948 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 10 Apr 2025 16:07:18 -0300 Subject: [PATCH 10/23] docs: add an electra implementation gap doc (#1423) Co-authored-by: Leandro Serra --- README.md | 3 + docs/specs/electra/beacon-chain.md | 1707 +++++++++++++++++++++++++++ docs/specs/electra/fork.md | 178 +++ docs/specs/electra/p2p-interface.md | 209 ++++ docs/specs/electra/validator.md | 309 +++++ electra-gap.md | 114 ++ 6 files changed, 2520 insertions(+) create mode 100644 docs/specs/electra/beacon-chain.md create mode 100644 docs/specs/electra/fork.md create mode 100644 docs/specs/electra/p2p-interface.md create mode 100644 docs/specs/electra/validator.md create mode 100644 electra-gap.md diff --git a/README.md b/README.md index 128db54a5..df8a4c82e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ We take security seriously. If you discover a vulnerability in this project, ple For more details, please refer to our [Security Policy](./.github/SECURITY.md). +## Electra Support + +We are working on the electra support in [this branch](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) and the current progress is updated in [this document](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support/electra-gap.md) ## Prerequisites diff --git a/docs/specs/electra/beacon-chain.md b/docs/specs/electra/beacon-chain.md new file mode 100644 index 000000000..8f0bc9d51 --- /dev/null +++ b/docs/specs/electra/beacon-chain.md @@ -0,0 +1,1707 @@ +# Electra -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Misc](#misc) + - [Withdrawal prefixes](#withdrawal-prefixes) + - [Execution layer triggered requests](#execution-layer-triggered-requests) +- [Preset](#preset) + - [Gwei values](#gwei-values) + - [Rewards and penalties](#rewards-and-penalties) + - [State list lengths](#state-list-lengths) + - [Max operations per block](#max-operations-per-block) + - [Execution](#execution) + - [Withdrawals processing](#withdrawals-processing) + - [Pending deposits processing](#pending-deposits-processing) +- [Configuration](#configuration) + - [Execution](#execution-1) + - [Validator cycle](#validator-cycle) +- [Containers](#containers) + - [New containers](#new-containers) + - [`PendingDeposit`](#pendingdeposit) + - [`PendingPartialWithdrawal`](#pendingpartialwithdrawal) + - [`PendingConsolidation`](#pendingconsolidation) + - [`DepositRequest`](#depositrequest) + - [`WithdrawalRequest`](#withdrawalrequest) + - [`ConsolidationRequest`](#consolidationrequest) + - [`ExecutionRequests`](#executionrequests) + - [`SingleAttestation`](#singleattestation) + - [Modified containers](#modified-containers) + - [`AttesterSlashing`](#attesterslashing) + - [`BeaconBlockBody`](#beaconblockbody) + - [Modified containers](#modified-containers-1) + - [`Attestation`](#attestation) + - [`IndexedAttestation`](#indexedattestation) + - [`BeaconState`](#beaconstate) +- [Helper functions](#helper-functions) + - [Predicates](#predicates) + - [Modified `compute_proposer_index`](#modified-compute_proposer_index) + - [Modified `is_eligible_for_activation_queue`](#modified-is_eligible_for_activation_queue) + - [New `is_compounding_withdrawal_credential`](#new-is_compounding_withdrawal_credential) + - [New `has_compounding_withdrawal_credential`](#new-has_compounding_withdrawal_credential) + - [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential) + - [Modified `is_fully_withdrawable_validator`](#modified-is_fully_withdrawable_validator) + - [Modified `is_partially_withdrawable_validator`](#modified-is_partially_withdrawable_validator) + - [Misc](#misc-1) + - [New `get_committee_indices`](#new-get_committee_indices) + - [New `get_max_effective_balance`](#new-get_max_effective_balance) + - [Beacon state accessors](#beacon-state-accessors) + - [New `get_balance_churn_limit`](#new-get_balance_churn_limit) + - [New `get_activation_exit_churn_limit`](#new-get_activation_exit_churn_limit) + - [New `get_consolidation_churn_limit`](#new-get_consolidation_churn_limit) + - [New `get_pending_balance_to_withdraw`](#new-get_pending_balance_to_withdraw) + - [Modified `get_attesting_indices`](#modified-get_attesting_indices) + - [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices) + - [Beacon state mutators](#beacon-state-mutators) + - [Modified `initiate_validator_exit`](#modified-initiate_validator_exit) + - [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator) + - [New `queue_excess_active_balance`](#new-queue_excess_active_balance) + - [New `compute_exit_epoch_and_update_churn`](#new-compute_exit_epoch_and_update_churn) + - [New `compute_consolidation_epoch_and_update_churn`](#new-compute_consolidation_epoch_and_update_churn) + - [Modified `slash_validator`](#modified-slash_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Epoch processing](#epoch-processing) + - [Modified `process_epoch`](#modified-process_epoch) + - [Modified `process_registry_updates`](#modified-process_registry_updates) + - [Modified `process_slashings`](#modified-process_slashings) + - [New `apply_pending_deposit`](#new-apply_pending_deposit) + - [New `process_pending_deposits`](#new-process_pending_deposits) + - [New `process_pending_consolidations`](#new-process_pending_consolidations) + - [Modified `process_effective_balance_updates`](#modified-process_effective_balance_updates) + - [Execution engine](#execution-engine) + - [Request data](#request-data) + - [Modified `NewPayloadRequest`](#modified-newpayloadrequest) + - [Engine APIs](#engine-apis) + - [Modified `is_valid_block_hash`](#modified-is_valid_block_hash) + - [Modified `notify_new_payload`](#modified-notify_new_payload) + - [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload) + - [Block processing](#block-processing) + - [Withdrawals](#withdrawals) + - [Modified `get_expected_withdrawals`](#modified-get_expected_withdrawals) + - [Modified `process_withdrawals`](#modified-process_withdrawals) + - [Execution payload](#execution-payload) + - [New `get_execution_requests_list`](#new-get_execution_requests_list) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Operations](#operations) + - [Modified `process_operations`](#modified-process_operations) + - [Attestations](#attestations) + - [Modified `process_attestation`](#modified-process_attestation) + - [Deposits](#deposits) + - [Modified `get_validator_from_deposit`](#modified-get_validator_from_deposit) + - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) + - [Modified `apply_deposit`](#modified-apply_deposit) + - [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature) + - [Modified `process_deposit`](#modified-process_deposit) + - [Voluntary exits](#voluntary-exits) + - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) + - [Execution layer withdrawal requests](#execution-layer-withdrawal-requests) + - [New `process_withdrawal_request`](#new-process_withdrawal_request) + - [Deposit requests](#deposit-requests) + - [New `process_deposit_request`](#new-process_deposit_request) + - [Execution layer consolidation requests](#execution-layer-consolidation-requests) + - [New `is_valid_switch_to_compounding_request`](#new-is_valid_switch_to_compounding_request) + - [New `process_consolidation_request`](#new-process_consolidation_request) + + + + +## Introduction + +Electra is a consensus-layer upgrade containing a number of features. Including: + +* [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain +* [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable exits +* [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE +* [EIP-7549](https://eips.ethereum.org/EIPS/eip-7549): Move committee index outside Attestation +* [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691): Blob throughput increase + +*Note*: This specification is built upon [Deneb](../deneb/beacon-chain.md) and is under active development. + +## Constants + +The following values are (non-configurable) constants used throughout the specification. + +### Misc + +| Name | Value | Description | +| - | - | - | +| `UNSET_DEPOSIT_REQUESTS_START_INDEX` | `uint64(2**64 - 1)` | *[New in Electra:EIP6110]* Value which indicates no start index has been assigned | +| `FULL_EXIT_REQUEST_AMOUNT` | `uint64(0)` | *[New in Electra:EIP7002]* Withdrawal amount used to signal a full validator exit | + +### Withdrawal prefixes + +| Name | Value | Description | +| - | - | - | +| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` | *[New in Electra:EIP7251]* Withdrawal credential prefix for a compounding validator | + +### Execution layer triggered requests + +| Name | Value | +| - | - | +| `DEPOSIT_REQUEST_TYPE` | `Bytes1('0x00')` | +| `WITHDRAWAL_REQUEST_TYPE` | `Bytes1('0x01')` | +| `CONSOLIDATION_REQUEST_TYPE` | `Bytes1('0x02')` | + +## Preset + +### Gwei values + +| Name | Value | Description | +| - | - | - | +| `MIN_ACTIVATION_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | *[New in Electra:EIP7251]* Minimum balance for a validator to become active | +| `MAX_EFFECTIVE_BALANCE_ELECTRA` | `Gwei(2**11 * 10**9)` (= 2048,000,000,000) | *[New in Electra:EIP7251]* Maximum effective balance for a compounding validator | + +### Rewards and penalties + +| Name | Value | +| - | - | +| `MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | +| `WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | + +### State list lengths + +| Name | Value | Unit | +| - | - | - | +| `PENDING_DEPOSITS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending deposits | +| `PENDING_PARTIAL_WITHDRAWALS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending partial withdrawals | +| `PENDING_CONSOLIDATIONS_LIMIT` | `uint64(2**18)` (= 262,144) | pending consolidations | + +### Max operations per block + +| Name | Value | +| - | - | +| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | +| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | + +### Execution + +| Name | Value | Description | +| - | - | - | +| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of execution layer deposit requests in each payload | +| `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload | +| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(2**1)` (= 2) | *[New in Electra:EIP7251]* Maximum number of execution layer consolidation requests in each payload | + +### Withdrawals processing + +| Name | Value | Description | +| - | - | - | +| `MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP` | `uint64(2**3)` (= 8)| *[New in Electra:EIP7002]* Maximum number of pending partial withdrawals to process per payload | + +### Pending deposits processing + +| Name | Value | Description | +| - | - | - | +| `MAX_PENDING_DEPOSITS_PER_EPOCH` | `uint64(2**4)` (= 16)| *[New in Electra:EIP6110]* Maximum number of pending deposits to process per epoch | + +## Configuration + +### Execution + +| Name | Value | Description | +| - | - | - | +| `MAX_BLOBS_PER_BLOCK_ELECTRA` | `uint64(9)` | *[New in Electra:EIP7691]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | + +### Validator cycle + +| Name | Value | +| - | - | +| `MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA` | `Gwei(2**7 * 10**9)` (= 128,000,000,000) | # Equivalent to 4 32 ETH validators +| `MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT` | `Gwei(2**8 * 10**9)` (= 256,000,000,000) | + +## Containers + +### New containers + +#### `PendingDeposit` + +*Note*: The container is new in EIP7251. + +```python +class PendingDeposit(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature + slot: Slot +``` + +#### `PendingPartialWithdrawal` + +*Note*: The container is new in EIP7251. + +```python +class PendingPartialWithdrawal(Container): + validator_index: ValidatorIndex + amount: Gwei + withdrawable_epoch: Epoch +``` + +#### `PendingConsolidation` + +*Note*: The container is new in EIP7251. + +```python +class PendingConsolidation(Container): + source_index: ValidatorIndex + target_index: ValidatorIndex +``` + +#### `DepositRequest` + +*Note*: The container is new in EIP6110. + +```python +class DepositRequest(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature + index: uint64 +``` + +#### `WithdrawalRequest` + +*Note*: The container is new in EIP7251:EIP7002. + +```python +class WithdrawalRequest(Container): + source_address: ExecutionAddress + validator_pubkey: BLSPubkey + amount: Gwei +``` + +#### `ConsolidationRequest` + +*Note*: The container is new in EIP7251. + +```python +class ConsolidationRequest(Container): + source_address: ExecutionAddress + source_pubkey: BLSPubkey + target_pubkey: BLSPubkey +``` + +#### `ExecutionRequests` + +```python +class ExecutionRequests(Container): + deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110] + withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7002:EIP7251] + consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7251] +``` + +#### `SingleAttestation` + +```python +class SingleAttestation(Container): + committee_index: CommitteeIndex + attester_index: ValidatorIndex + data: AttestationData + signature: BLSSignature +``` + +### Modified containers + +#### `AttesterSlashing` + +```python +class AttesterSlashing(Container): + attestation_1: IndexedAttestation # [Modified in Electra:EIP7549] + attestation_2: IndexedAttestation # [Modified in Electra:EIP7549] +``` + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] # [Modified in Electra:EIP7549] + attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] # [Modified in Electra:EIP7549] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + execution_requests: ExecutionRequests # [New in Electra] +``` + +### Modified containers + +#### `Attestation` + +```python +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in Electra:EIP7549] + data: AttestationData + signature: BLSSignature + committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in Electra:EIP7549] +``` + +#### `IndexedAttestation` + +```python +class IndexedAttestation(Container): + # [Modified in Electra:EIP7549] + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + data: AttestationData + signature: BLSSignature +``` + +#### `BeaconState` + +```python +class BeaconState(Container): + # Versioning + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + # History + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + # Eth1 + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + # Registry + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # Randomness + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + # Slashings + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances + # Participation + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + # Finality + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + # Deep history valid from Capella onwards + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + deposit_requests_start_index: uint64 # [New in Electra:EIP6110] + deposit_balance_to_consume: Gwei # [New in Electra:EIP7251] + exit_balance_to_consume: Gwei # [New in Electra:EIP7251] + earliest_exit_epoch: Epoch # [New in Electra:EIP7251] + consolidation_balance_to_consume: Gwei # [New in Electra:EIP7251] + earliest_consolidation_epoch: Epoch # [New in Electra:EIP7251] + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] # [New in Electra:EIP7251] + # [New in Electra:EIP7251] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] # [New in Electra:EIP7251] +``` + +## Helper functions + +### Predicates + +#### Modified `compute_proposer_index` + +*Note*: The function `compute_proposer_index` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter. + +```python +def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + # [Modified in Electra] + random_bytes = hash(seed + uint_to_bytes(i // 16)) + offset = i % 16 * 2 + random_value = bytes_to_uint64(random_bytes[offset:offset + 2]) + effective_balance = state.validators[candidate_index].effective_balance + # [Modified in Electra:EIP7251] + if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: + return candidate_index + i += 1 +``` + +#### Modified `is_eligible_for_activation_queue` + +*Note*: The function `is_eligible_for_activation_queue` is modified to use `MIN_ACTIVATION_BALANCE` instead of `MAX_EFFECTIVE_BALANCE`. + +```python +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance >= MIN_ACTIVATION_BALANCE # [Modified in Electra:EIP7251] + ) +``` + +#### New `is_compounding_withdrawal_credential` + +```python +def is_compounding_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool: + return withdrawal_credentials[:1] == COMPOUNDING_WITHDRAWAL_PREFIX +``` + +#### New `has_compounding_withdrawal_credential` + +```python +def has_compounding_withdrawal_credential(validator: Validator) -> bool: + """ + Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential. + """ + return is_compounding_withdrawal_credential(validator.withdrawal_credentials) +``` + +#### New `has_execution_withdrawal_credential` + +```python +def has_execution_withdrawal_credential(validator: Validator) -> bool: + """ + Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. + """ + return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator) +``` + +#### Modified `is_fully_withdrawable_validator` + +*Note*: The function `is_fully_withdrawable_validator` is modified to use `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`. + +```python +def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool: + """ + Check if ``validator`` is fully withdrawable. + """ + return ( + has_execution_withdrawal_credential(validator) # [Modified in Electra:EIP7251] + and validator.withdrawable_epoch <= epoch + and balance > 0 + ) +``` + +#### Modified `is_partially_withdrawable_validator` + +*Note*: The function `is_partially_withdrawable_validator` is modified to use `get_max_effective_balance` instead of `MAX_EFFECTIVE_BALANCE` and `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`. + +```python +def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool: + """ + Check if ``validator`` is partially withdrawable. + """ + max_effective_balance = get_max_effective_balance(validator) + has_max_effective_balance = validator.effective_balance == max_effective_balance # [Modified in Electra:EIP7251] + has_excess_balance = balance > max_effective_balance # [Modified in Electra:EIP7251] + return ( + has_execution_withdrawal_credential(validator) # [Modified in Electra:EIP7251] + and has_max_effective_balance + and has_excess_balance + ) +``` + +### Misc + +#### New `get_committee_indices` + +```python +def get_committee_indices(committee_bits: Bitvector) -> Sequence[CommitteeIndex]: + return [CommitteeIndex(index) for index, bit in enumerate(committee_bits) if bit] +``` + +#### New `get_max_effective_balance` + +```python +def get_max_effective_balance(validator: Validator) -> Gwei: + """ + Get max effective balance for ``validator``. + """ + if has_compounding_withdrawal_credential(validator): + return MAX_EFFECTIVE_BALANCE_ELECTRA + else: + return MIN_ACTIVATION_BALANCE +``` + +### Beacon state accessors + +#### New `get_balance_churn_limit` + +```python +def get_balance_churn_limit(state: BeaconState) -> Gwei: + """ + Return the churn limit for the current epoch. + """ + churn = max( + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA, + get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT + ) + return churn - churn % EFFECTIVE_BALANCE_INCREMENT +``` + +#### New `get_activation_exit_churn_limit` + +```python +def get_activation_exit_churn_limit(state: BeaconState) -> Gwei: + """ + Return the churn limit for the current epoch dedicated to activations and exits. + """ + return min(MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state)) +``` + +#### New `get_consolidation_churn_limit` + +```python +def get_consolidation_churn_limit(state: BeaconState) -> Gwei: + return get_balance_churn_limit(state) - get_activation_exit_churn_limit(state) +``` + +#### New `get_pending_balance_to_withdraw` + +```python +def get_pending_balance_to_withdraw(state: BeaconState, validator_index: ValidatorIndex) -> Gwei: + return sum( + withdrawal.amount for withdrawal in state.pending_partial_withdrawals + if withdrawal.validator_index == validator_index + ) +``` + +#### Modified `get_attesting_indices` + +*Note*: The function `get_attesting_indices` is modified to support EIP7549. + +```python +def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``. + """ + output: Set[ValidatorIndex] = set() + committee_indices = get_committee_indices(attestation.committee_bits) + committee_offset = 0 + for committee_index in committee_indices: + committee = get_beacon_committee(state, attestation.data.slot, committee_index) + committee_attesters = set( + attester_index for i, attester_index in enumerate(committee) + if attestation.aggregation_bits[committee_offset + i] + ) + output = output.union(committee_attesters) + + committee_offset += len(committee) + + return output +``` + +#### Modified `get_next_sync_committee_indices` + +*Note*: The function `get_next_sync_committee_indices` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter. + +```python +def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 1) + + MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] + active_validator_indices = get_active_validator_indices(state, epoch) + active_validator_count = uint64(len(active_validator_indices)) + seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) + i = uint64(0) + sync_committee_indices: List[ValidatorIndex] = [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] + # [Modified in Electra] + random_bytes = hash(seed + uint_to_bytes(i // 16)) + offset = i % 16 * 2 + random_value = bytes_to_uint64(random_bytes[offset:offset + 2]) + effective_balance = state.validators[candidate_index].effective_balance + # [Modified in Electra:EIP7251] + if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices +``` + +### Beacon state mutators + +#### Modified `initiate_validator_exit` + +*Note*: The function `initiate_validator_exit` is modified to use the new `compute_exit_epoch_and_update_churn` function. + +```python +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch [Modified in Electra:EIP7251] + exit_queue_epoch = compute_exit_epoch_and_update_churn(state, validator.effective_balance) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) +``` + +#### New `switch_to_compounding_validator` + +```python +def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None: + validator = state.validators[index] + validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + queue_excess_active_balance(state, index) +``` + +#### New `queue_excess_active_balance` + +```python +def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None: + balance = state.balances[index] + if balance > MIN_ACTIVATION_BALANCE: + excess_balance = balance - MIN_ACTIVATION_BALANCE + state.balances[index] = MIN_ACTIVATION_BALANCE + validator = state.validators[index] + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + state.pending_deposits.append(PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=excess_balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + )) +``` + +#### New `compute_exit_epoch_and_update_churn` + +```python +def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch: + earliest_exit_epoch = max(state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state))) + per_epoch_churn = get_activation_exit_churn_limit(state) + # New epoch for exits. + if state.earliest_exit_epoch < earliest_exit_epoch: + exit_balance_to_consume = per_epoch_churn + else: + exit_balance_to_consume = state.exit_balance_to_consume + + # Exit doesn't fit in the current earliest epoch. + if exit_balance > exit_balance_to_consume: + balance_to_process = exit_balance - exit_balance_to_consume + additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1 + earliest_exit_epoch += additional_epochs + exit_balance_to_consume += additional_epochs * per_epoch_churn + + # Consume the balance and update state variables. + state.exit_balance_to_consume = exit_balance_to_consume - exit_balance + state.earliest_exit_epoch = earliest_exit_epoch + + return state.earliest_exit_epoch +``` + +#### New `compute_consolidation_epoch_and_update_churn` + +```python +def compute_consolidation_epoch_and_update_churn(state: BeaconState, consolidation_balance: Gwei) -> Epoch: + earliest_consolidation_epoch = max( + state.earliest_consolidation_epoch, compute_activation_exit_epoch(get_current_epoch(state))) + per_epoch_consolidation_churn = get_consolidation_churn_limit(state) + # New epoch for consolidations. + if state.earliest_consolidation_epoch < earliest_consolidation_epoch: + consolidation_balance_to_consume = per_epoch_consolidation_churn + else: + consolidation_balance_to_consume = state.consolidation_balance_to_consume + + # Consolidation doesn't fit in the current earliest epoch. + if consolidation_balance > consolidation_balance_to_consume: + balance_to_process = consolidation_balance - consolidation_balance_to_consume + additional_epochs = (balance_to_process - 1) // per_epoch_consolidation_churn + 1 + earliest_consolidation_epoch += additional_epochs + consolidation_balance_to_consume += additional_epochs * per_epoch_consolidation_churn + + # Consume the balance and update state variables. + state.consolidation_balance_to_consume = consolidation_balance_to_consume - consolidation_balance + state.earliest_consolidation_epoch = earliest_consolidation_epoch + + return state.earliest_consolidation_epoch +``` + +#### Modified `slash_validator` + +*Note*: The function `slash_validator` is modified to change how the slashing penalty and proposer/whistleblower rewards are calculated in accordance with EIP7251. + +```python +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + # [Modified in Electra:EIP7251] + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei( + validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA) # [Modified in Electra:EIP7251] + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) +``` + +## Beacon chain state transition function + +### Epoch processing + +#### Modified `process_epoch` + +*Note*: The function `process_epoch` is modified to call updated functions and to process pending balance deposits and pending consolidations which are new in Electra. + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) # [Modified in Electra:EIP7251] + process_slashings(state) # [Modified in Electra:EIP7251] + process_eth1_data_reset(state) + process_pending_deposits(state) # [New in Electra:EIP7251] + process_pending_consolidations(state) # [New in Electra:EIP7251] + process_effective_balance_updates(state) # [Modified in Electra:EIP7251] + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_summaries_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) +``` + +#### Modified `process_registry_updates` + +*Note*: The function `process_registry_updates` is modified to use the updated definitions of +`initiate_validator_exit` and `is_eligible_for_activation_queue`, changes how the activation epochs +are computed for eligible validators, and processes activations in the same loop as activation +eligibility updates and ejections. + +```python +def process_registry_updates(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + activation_epoch = compute_activation_exit_epoch(current_epoch) + + # Process activation eligibility, ejections, and activations + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): # [Modified in Electra:EIP7251] + validator.activation_eligibility_epoch = current_epoch + 1 + elif is_active_validator(validator, current_epoch) and validator.effective_balance <= EJECTION_BALANCE: + initiate_validator_exit(state, ValidatorIndex(index)) # [Modified in Electra:EIP7251] + elif is_eligible_for_activation(state, validator): + validator.activation_epoch = activation_epoch +``` + +#### Modified `process_slashings` + +*Note*: The function `process_slashings` is modified to use a new algorithm to compute correlation penalty. + +```python +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, + total_balance + ) + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from total balance to avoid uint64 overflow + penalty_per_effective_balance_increment = adjusted_total_slashing_balance // (total_balance // increment) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + effective_balance_increments = validator.effective_balance // increment + # [Modified in Electra:EIP7251] + penalty = penalty_per_effective_balance_increment * effective_balance_increments + decrease_balance(state, ValidatorIndex(index), penalty) +``` + +#### New `apply_pending_deposit` + +```python +def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None: + """ + Applies ``deposit`` to the ``state``. + """ + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if is_valid_deposit_signature( + deposit.pubkey, + deposit.withdrawal_credentials, + deposit.amount, + deposit.signature + ): + add_validator_to_registry(state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount) + else: + validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) + increase_balance(state, validator_index, deposit.amount) +``` + +#### New `process_pending_deposits` + +Iterating over `pending_deposits` queue this function runs the following checks before applying pending deposit: +1. All Eth1 bridge deposits are processed before the first deposit request gets processed. +2. Deposit position in the queue is finalized. +3. Deposit does not exceed the `MAX_PENDING_DEPOSITS_PER_EPOCH` limit. +4. Deposit does not exceed the activation churn limit. + +```python +def process_pending_deposits(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state) + processed_amount = 0 + next_deposit_index = 0 + deposits_to_postpone = [] + is_churn_limit_reached = False + finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) + + for deposit in state.pending_deposits: + # Do not process deposit requests if Eth1 bridge deposits are not yet applied. + if ( + # Is deposit request + deposit.slot > GENESIS_SLOT and + # There are pending Eth1 bridge deposits + state.eth1_deposit_index < state.deposit_requests_start_index + ): + break + + # Check if deposit has been finalized, otherwise, stop processing. + if deposit.slot > finalized_slot: + break + + # Check if number of processed deposits has not reached the limit, otherwise, stop processing. + if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH: + break + + # Read validator state + is_validator_exited = False + is_validator_withdrawn = False + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey in validator_pubkeys: + validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))] + is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH + is_validator_withdrawn = validator.withdrawable_epoch < next_epoch + + if is_validator_withdrawn: + # Deposited balance will never become active. Increase balance but do not consume churn + apply_pending_deposit(state, deposit) + elif is_validator_exited: + # Validator is exiting, postpone the deposit until after withdrawable epoch + deposits_to_postpone.append(deposit) + else: + # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing + if is_churn_limit_reached: + break + + # Consume churn and apply deposit. + processed_amount += deposit.amount + apply_pending_deposit(state, deposit) + + # Regardless of how the deposit was handled, we move on in the queue. + next_deposit_index += 1 + + state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone + + # Accumulate churn only if the churn limit has been hit. + if is_churn_limit_reached: + state.deposit_balance_to_consume = available_for_processing - processed_amount + else: + state.deposit_balance_to_consume = Gwei(0) +``` + +#### New `process_pending_consolidations` + +```python +def process_pending_consolidations(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + next_pending_consolidation = 0 + for pending_consolidation in state.pending_consolidations: + source_validator = state.validators[pending_consolidation.source_index] + if source_validator.slashed: + next_pending_consolidation += 1 + continue + if source_validator.withdrawable_epoch > next_epoch: + break + + # Calculate the consolidated balance + source_effective_balance = min( + state.balances[pending_consolidation.source_index], source_validator.effective_balance) + + # Move active balance to target. Excess balance is withdrawable. + decrease_balance(state, pending_consolidation.source_index, source_effective_balance) + increase_balance(state, pending_consolidation.target_index, source_effective_balance) + next_pending_consolidation += 1 + + state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:] +``` + +#### Modified `process_effective_balance_updates` + +*Note*: The function `process_effective_balance_updates` is modified to use the new limit for the maximum effective balance. + +```python +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + # [Modified in Electra:EIP7251] + max_effective_balance = get_max_effective_balance(validator) + + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance) +``` + +### Execution engine + +#### Request data + +##### Modified `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload + versioned_hashes: Sequence[VersionedHash] + parent_beacon_block_root: Root + execution_requests: ExecutionRequests # [New in Electra] +``` + +#### Engine APIs + +##### Modified `is_valid_block_hash` + +*Note*: The function `is_valid_block_hash` is modified to include the additional `execution_requests_list`. + +```python +def is_valid_block_hash(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes]) -> bool: + """ + Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly. + """ + ... +``` + +##### Modified `notify_new_payload` + +*Note*: The function `notify_new_payload` is modified to include the additional `execution_requests_list`. + +```python +def notify_new_payload(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes]) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list`` + are valid with respect to ``self.execution_state``. + """ + ... +``` + +##### Modified `verify_and_notify_new_payload` + +*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter +`execution_requests_list` when calling `is_valid_block_hash` and `notify_new_payload` in Electra. + +```python +def verify_and_notify_new_payload(self: ExecutionEngine, + new_payload_request: NewPayloadRequest) -> bool: + """ + Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``. + """ + execution_payload = new_payload_request.execution_payload + parent_beacon_block_root = new_payload_request.parent_beacon_block_root + execution_requests_list = get_execution_requests_list(new_payload_request.execution_requests) # [New in Electra] + + if b'' in execution_payload.transactions: + return False + + # [Modified in Electra] + if not self.is_valid_block_hash( + execution_payload, + parent_beacon_block_root, + execution_requests_list): + return False + + if not self.is_valid_versioned_hashes(new_payload_request): + return False + + # [Modified in Electra] + if not self.notify_new_payload( + execution_payload, + parent_beacon_block_root, + execution_requests_list): + return False + + return True +``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_withdrawals(state, block.body.execution_payload) # [Modified in Electra:EIP7251] + process_execution_payload(state, block.body, EXECUTION_ENGINE) # [Modified in Electra:EIP6110] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) # [Modified in Electra:EIP6110:EIP7002:EIP7549:EIP7251] + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### Withdrawals + +##### Modified `get_expected_withdrawals` + +*Note*: The function `get_expected_withdrawals` is modified to support EIP7251. + +```python +def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]: + epoch = get_current_epoch(state) + withdrawal_index = state.next_withdrawal_index + validator_index = state.next_withdrawal_validator_index + withdrawals: List[Withdrawal] = [] + processed_partial_withdrawals_count = 0 + + # [New in Electra:EIP7251] Consume pending partial withdrawals + for withdrawal in state.pending_partial_withdrawals: + if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: + break + + validator = state.validators[withdrawal.validator_index] + has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE + has_excess_balance = state.balances[withdrawal.validator_index] > MIN_ACTIVATION_BALANCE + if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance: + withdrawable_balance = min( + state.balances[withdrawal.validator_index] - MIN_ACTIVATION_BALANCE, + withdrawal.amount) + withdrawals.append(Withdrawal( + index=withdrawal_index, + validator_index=withdrawal.validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=withdrawable_balance, + )) + withdrawal_index += WithdrawalIndex(1) + + processed_partial_withdrawals_count += 1 + + # Sweep for remaining. + bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) + for _ in range(bound): + validator = state.validators[validator_index] + # [Modified in Electra:EIP7251] + partially_withdrawn_balance = sum( + withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index) + balance = state.balances[validator_index] - partially_withdrawn_balance + if is_fully_withdrawable_validator(validator, balance, epoch): + withdrawals.append(Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=balance, + )) + withdrawal_index += WithdrawalIndex(1) + elif is_partially_withdrawable_validator(validator, balance): + withdrawals.append(Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=balance - get_max_effective_balance(validator), # [Modified in Electra:EIP7251] + )) + withdrawal_index += WithdrawalIndex(1) + if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + break + validator_index = ValidatorIndex((validator_index + 1) % len(state.validators)) + return withdrawals, processed_partial_withdrawals_count +``` + +##### Modified `process_withdrawals` + +*Note*: The function `process_withdrawals` is modified to support EIP7251. + +```python +def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: + # [Modified in Electra:EIP7251] + expected_withdrawals, processed_partial_withdrawals_count = get_expected_withdrawals(state) + + assert payload.withdrawals == expected_withdrawals + + for withdrawal in expected_withdrawals: + decrease_balance(state, withdrawal.validator_index, withdrawal.amount) + + # Update pending partial withdrawals [New in Electra:EIP7251] + state.pending_partial_withdrawals = state.pending_partial_withdrawals[processed_partial_withdrawals_count:] + + # Update the next withdrawal index if this block contained withdrawals + if len(expected_withdrawals) != 0: + latest_withdrawal = expected_withdrawals[-1] + state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) + + # Update the next validator index to start the next withdrawal sweep + if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + # Next sweep starts after the latest withdrawal's validator index + next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators)) + state.next_withdrawal_validator_index = next_validator_index + else: + # Advance sweep by the max length of the sweep if there was not a full set of withdrawals + next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + next_validator_index = ValidatorIndex(next_index % len(state.validators)) + state.next_withdrawal_validator_index = next_validator_index +``` + +#### Execution payload + +##### New `get_execution_requests_list` + +*Note*: Encodes execution requests as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). + +```python +def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequence[bytes]: + requests = [ + (DEPOSIT_REQUEST_TYPE, execution_requests.deposits), + (WITHDRAWAL_REQUEST_TYPE, execution_requests.withdrawals), + (CONSOLIDATION_REQUEST_TYPE, execution_requests.consolidations), + ] + + return [ + request_type + ssz_serialize(request_data) + for request_type, request_data in requests + if len(request_data) != 0 + ] +``` + +##### Modified `process_execution_payload` + +*Note*: The function `process_execution_payload` is modified to pass `execution_requests` into `execution_engine.verify_and_notify_new_payload` (via the updated `NewPayloadRequest`). + +```python +def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: + payload = body.execution_payload + + # Verify consistency of the parent hash with respect to the previous execution payload header + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA # [Modified in Electra:EIP7691] + # Verify the execution payload is valid + versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] + assert execution_engine.verify_and_notify_new_payload( + NewPayloadRequest( + execution_payload=payload, + versioned_hashes=versioned_hashes, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests=body.execution_requests, # [New in Electra] + ) + ) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), + blob_gas_used=payload.blob_gas_used, + excess_blob_gas=payload.excess_blob_gas, + ) +``` + +#### Operations + +##### Modified `process_operations` + +*Note*: The function `process_operations` is modified to support all of the new functionality in Electra. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # [Modified in Electra:EIP6110] + # Disable former deposit mechanism once all prior deposits are processed + eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index) + if state.eth1_deposit_index < eth1_deposit_index_limit: + assert len(body.deposits) == min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index) + else: + assert len(body.deposits) == 0 + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549] + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251] + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + for_ops(body.execution_requests.deposits, process_deposit_request) # [New in Electra:EIP6110] + for_ops(body.execution_requests.withdrawals, process_withdrawal_request) # [New in Electra:EIP7002:EIP7251] + for_ops(body.execution_requests.consolidations, process_consolidation_request) # [New in Electra:EIP7251] +``` + +##### Attestations + +###### Modified `process_attestation` + +*Note*: The function is modified to support EIP7549. + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot + + # [Modified in Electra:EIP7549] + assert data.index == 0 + committee_indices = get_committee_indices(attestation.committee_bits) + committee_offset = 0 + for committee_index in committee_indices: + assert committee_index < get_committee_count_per_slot(state, data.target.epoch) + committee = get_beacon_committee(state, data.slot, committee_index) + committee_attesters = set( + attester_index for i, attester_index in enumerate(committee) + if attestation.aggregation_bits[committee_offset + i] + ) + assert len(committee_attesters) > 0 + committee_offset += len(committee) + + # Bitfield length matches total number of participants + assert len(attestation.aggregation_bits) == committee_offset + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, attestation): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +``` + +##### Deposits + +###### Modified `get_validator_from_deposit` + +*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` for compounding withdrawal credential. + +```python +def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> Validator: + validator = Validator( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + effective_balance=Gwei(0), + slashed=False, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + ) + + # [Modified in Electra:EIP7251] + max_effective_balance = get_max_effective_balance(validator) + validator.effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance) + + return validator +``` + +###### Modified `add_validator_to_registry` + +*Note*: The function `add_validator_to_registry` is modified to use the modified `get_validator_from_deposit`. + +```python +def add_validator_to_registry(state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64) -> None: + index = get_index_for_new_validator(state) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) # [Modified in Electra:EIP7251] + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) +``` + +###### Modified `apply_deposit` + +*Note*: The function `apply_deposit` is modified to support EIP7251. + +```python +def apply_deposit(state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64, + signature: BLSSignature) -> None: + validator_pubkeys = [v.pubkey for v in state.validators] + if pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature): + add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) # [Modified in Electra:EIP7251] + else: + return + + # Increase balance by deposit amount + # [Modified in Electra:EIP7251] + state.pending_deposits.append(PendingDeposit( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=signature, + slot=GENESIS_SLOT # Use GENESIS_SLOT to distinguish from a pending deposit request + )) +``` + +###### New `is_valid_deposit_signature` + +```python +def is_valid_deposit_signature(pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64, + signature: BLSSignature) -> bool: + deposit_message = DepositMessage( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + ) + domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks + signing_root = compute_signing_root(deposit_message, domain) + return bls.Verify(pubkey, signing_root, signature) +``` + +###### Modified `process_deposit` + +*Note*: The function `process_deposit` is modified to to use the modified `apply_deposit`. + +```python +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + assert is_valid_merkle_branch( + leaf=hash_tree_root(deposit.data), + branch=deposit.proof, + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) + + # Deposits must be processed in order + state.eth1_deposit_index += 1 + + # [Modified in Electra:EIP7251] + apply_deposit( + state=state, + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + signature=deposit.data.signature, + ) +``` + +##### Voluntary exits + +###### Modified `process_voluntary_exit` + +*Note*: The function `process_voluntary_exit` is modified to ensure the validator has no pending withdrawals in the queue. + +```python +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD + # Only exit validator if it has no pending withdrawals in the queue + assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in Electra:EIP7251] + # Verify signature + domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) +``` + +##### Execution layer withdrawal requests + +###### New `process_withdrawal_request` + +```python +def process_withdrawal_request( + state: BeaconState, + withdrawal_request: WithdrawalRequest +) -> None: + amount = withdrawal_request.amount + is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT + + # If partial withdrawal queue is full, only full exits are processed + if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request: + return + + validator_pubkeys = [v.pubkey for v in state.validators] + # Verify pubkey exists + request_pubkey = withdrawal_request.validator_pubkey + if request_pubkey not in validator_pubkeys: + return + index = ValidatorIndex(validator_pubkeys.index(request_pubkey)) + validator = state.validators[index] + + # Verify withdrawal credentials + has_correct_credential = has_execution_withdrawal_credential(validator) + is_correct_source_address = ( + validator.withdrawal_credentials[12:] == withdrawal_request.source_address + ) + if not (has_correct_credential and is_correct_source_address): + return + # Verify the validator is active + if not is_active_validator(validator, get_current_epoch(state)): + return + # Verify exit has not been initiated + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + # Verify the validator has been active long enough + if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + + pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index) + + if is_full_exit_request: + # Only exit validator if it has no pending withdrawals in the queue + if pending_balance_to_withdraw == 0: + initiate_validator_exit(state, index) + return + + has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE + has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw + + # Only allow partial withdrawals with compounding withdrawal credentials + if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance: + to_withdraw = min( + state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, + amount + ) + exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw) + withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + state.pending_partial_withdrawals.append(PendingPartialWithdrawal( + validator_index=index, + amount=to_withdraw, + withdrawable_epoch=withdrawable_epoch, + )) +``` + +##### Deposit requests + +###### New `process_deposit_request` + +```python +def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: + # Set deposit request start index + if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX: + state.deposit_requests_start_index = deposit_request.index + + # Create pending deposit + state.pending_deposits.append(PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + )) +``` + +##### Execution layer consolidation requests + +###### New `is_valid_switch_to_compounding_request` + +```python +def is_valid_switch_to_compounding_request( + state: BeaconState, + consolidation_request: ConsolidationRequest +) -> bool: + # Switch to compounding requires source and target be equal + if consolidation_request.source_pubkey != consolidation_request.target_pubkey: + return False + + # Verify pubkey exists + source_pubkey = consolidation_request.source_pubkey + validator_pubkeys = [v.pubkey for v in state.validators] + if source_pubkey not in validator_pubkeys: + return False + + source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))] + + # Verify request has been authorized + if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address: + return False + + # Verify source withdrawal credentials + if not has_eth1_withdrawal_credential(source_validator): + return False + + # Verify the source is active + current_epoch = get_current_epoch(state) + if not is_active_validator(source_validator, current_epoch): + return False + + # Verify exit for source has not been initiated + if source_validator.exit_epoch != FAR_FUTURE_EPOCH: + return False + + return True +``` + +###### New `process_consolidation_request` + +```python +def process_consolidation_request( + state: BeaconState, + consolidation_request: ConsolidationRequest +) -> None: + if is_valid_switch_to_compounding_request(state, consolidation_request): + validator_pubkeys = [v.pubkey for v in state.validators] + request_source_pubkey = consolidation_request.source_pubkey + source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) + switch_to_compounding_validator(state, source_index) + return + + # Verify that source != target, so a consolidation cannot be used as an exit + if consolidation_request.source_pubkey == consolidation_request.target_pubkey: + return + # If the pending consolidations queue is full, consolidation requests are ignored + if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT: + return + # If there is too little available consolidation churn limit, consolidation requests are ignored + if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE: + return + + validator_pubkeys = [v.pubkey for v in state.validators] + # Verify pubkeys exists + request_source_pubkey = consolidation_request.source_pubkey + request_target_pubkey = consolidation_request.target_pubkey + if request_source_pubkey not in validator_pubkeys: + return + if request_target_pubkey not in validator_pubkeys: + return + source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) + target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey)) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + + # Verify source withdrawal credentials + has_correct_credential = has_execution_withdrawal_credential(source_validator) + is_correct_source_address = ( + source_validator.withdrawal_credentials[12:] == consolidation_request.source_address + ) + if not (has_correct_credential and is_correct_source_address): + return + + # Verify that target has compounding withdrawal credentials + if not has_compounding_withdrawal_credential(target_validator): + return + + # Verify the source and the target are active + current_epoch = get_current_epoch(state) + if not is_active_validator(source_validator, current_epoch): + return + if not is_active_validator(target_validator, current_epoch): + return + # Verify exits for source and target have not been initiated + if source_validator.exit_epoch != FAR_FUTURE_EPOCH: + return + if target_validator.exit_epoch != FAR_FUTURE_EPOCH: + return + # Verify the source has been active long enough + if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + # Verify the source has no pending withdrawals in the queue + if get_pending_balance_to_withdraw(state, source_index) > 0: + return + + # Initiate source validator exit and append pending consolidation + source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn( + state, source_validator.effective_balance + ) + source_validator.withdrawable_epoch = Epoch( + source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + state.pending_consolidations.append(PendingConsolidation( + source_index=source_index, + target_index=target_index + )) +``` \ No newline at end of file diff --git a/docs/specs/electra/fork.md b/docs/specs/electra/fork.md new file mode 100644 index 000000000..64bbd4e8a --- /dev/null +++ b/docs/specs/electra/fork.md @@ -0,0 +1,178 @@ +# Electra -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to Electra](#fork-to-electra) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + + +## Introduction + +This document describes the process of the Electra upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `ELECTRA_FORK_VERSION` | `Version('0x05000000')` | +| `ELECTRA_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to Electra + +### Fork trigger + +The fork is triggered at epoch `ELECTRA_FORK_EPOCH`. + +Note that for the pure Electra networks, we don't apply `upgrade_to_electra` since it starts with Electra version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == ELECTRA_FORK_EPOCH`, +an irregular state change is made to upgrade to Electra. + +```python +def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: + epoch = deneb.get_current_epoch(pre) + + earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(pre)) + for validator in pre.validators: + if validator.exit_epoch != FAR_FUTURE_EPOCH: + if validator.exit_epoch > earliest_exit_epoch: + earliest_exit_epoch = validator.exit_epoch + earliest_exit_epoch += Epoch(1) + + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110] + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Withdrawals + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + # Deep history valid from Capella onwards + historical_summaries=pre.historical_summaries, + # [New in Electra:EIP6110] + deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX, + # [New in Electra:EIP7251] + deposit_balance_to_consume=0, + exit_balance_to_consume=0, + earliest_exit_epoch=earliest_exit_epoch, + consolidation_balance_to_consume=0, + earliest_consolidation_epoch=compute_activation_exit_epoch(get_current_epoch(pre)), + pending_deposits=[], + pending_partial_withdrawals=[], + pending_consolidations=[], + ) + + post.exit_balance_to_consume = get_activation_exit_churn_limit(post) + post.consolidation_balance_to_consume = get_consolidation_churn_limit(post) + + # [New in Electra:EIP7251] + # add validators that are not yet active to pending balance deposits + pre_activation = sorted([ + index for index, validator in enumerate(post.validators) + if validator.activation_epoch == FAR_FUTURE_EPOCH + ], key=lambda index: ( + post.validators[index].activation_eligibility_epoch, + index + )) + + for index in pre_activation: + balance = post.balances[index] + post.balances[index] = 0 + validator = post.validators[index] + validator.effective_balance = 0 + validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + post.pending_deposits.append(PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + )) + + # Ensure early adopters of compounding credentials go through the activation churn + for index, validator in enumerate(post.validators): + if has_compounding_withdrawal_credential(validator): + queue_excess_active_balance(post, ValidatorIndex(index)) + + return post +``` \ No newline at end of file diff --git a/docs/specs/electra/p2p-interface.md b/docs/specs/electra/p2p-interface.md new file mode 100644 index 000000000..bec604aa1 --- /dev/null +++ b/docs/specs/electra/p2p-interface.md @@ -0,0 +1,209 @@ +# Electra -- Networking + +*Note*: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Modifications in Electra](#modifications-in-electra) + - [Configuration](#configuration) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) + - [`blob_sidecar_{subnet_id}`](#blob_sidecar_subnet_id) + - [Attestation subnets](#attestation-subnets) + - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [BlobSidecarsByRange v1](#blobsidecarsbyrange-v1) + - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1) + + + + +## Introduction + +This document contains the consensus-layer networking specification for Electra. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + +## Modifications in Electra + +### Configuration + +*[New in Electra:EIP7691]* + +| Name | Value | Description | +|-------------------------------------|----------------------------------------------------------|-------------------------------------------------------------------| +| `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA` | Maximum number of blob sidecars in a single request | +| `BLOB_SIDECAR_SUBNET_COUNT_ELECTRA` | `9` | The number of blob sidecar subnets used in the gossipsub protocol | + +### The gossip domain: gossipsub + +Some gossip meshes are upgraded in the fork of Electra to support upgraded types. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. + +The `beacon_block` topic is modified to also support Electra blocks. + +The `beacon_aggregate_and_proof` and `beacon_attestation_{subnet_id}` topics are modified to support the gossip of the new attestation type. + +The `attester_slashing` topic is modified to support the gossip of the new `AttesterSlashing` type. + +The specification around the creation, validation, and dissemination of messages has not changed from the Capella document unless explicitly noted here. + +The derivation of the `message-id` remains stable. + +##### Global topics + +###### `beacon_block` + +*Updated validation* + +- _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- + i.e. validate that `len(signed_beacon_block.message.body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA` + +###### `beacon_aggregate_and_proof` + +The following convenience variables are re-defined + +- `index = get_committee_indices(aggregate.committee_bits)[0]` + +The following validations are added: + +* [REJECT] `len(committee_indices) == 1`, where `committee_indices = get_committee_indices(aggregate)`. +* [REJECT] `aggregate.data.index == 0` + +###### `blob_sidecar_{subnet_id}` + +*[Modified in Electra:EIP7691]* + +The existing validations all apply as given from previous forks, with the following exceptions: + +* Uses of `MAX_BLOBS_PER_BLOCK` in existing validations are replaced with `MAX_BLOBS_PER_BLOCK_ELECTRA`. + +##### Attestation subnets + +###### `beacon_attestation_{subnet_id}` + +The topic is updated to propagate `SingleAttestation` objects. + +The following convenience variables are re-defined: + +- `index = attestation.committee_index` + +The following validations are added: + +- _[REJECT]_ `attestation.data.index == 0` +- _[REJECT]_ The attester is a member of the committee -- i.e. + `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`. + +The following validations are removed: + +- _[REJECT]_ The attestation is unaggregated -- + that is, it has exactly one participating validator (`len([bit for bit in aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). +- _[REJECT]_ The number of aggregation bits matches the committee size -- i.e. + `len(aggregation_bits) == len(get_beacon_committee(state, attestation.data.slot, index))`. + +### The Req/Resp domain + +#### Messages + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The Electra fork-digest is introduced to the `context` enum to specify Electra beacon block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` | + +##### BlobSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/1/` + +*[Modified in Electra:EIP7691]* + +Request Content: + +``` +( + start_slot: Slot + count: uint64 +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +*Updated validation* + +Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` sidecars. + +##### BlobSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/` + +*[Modified in Electra:EIP7691]* + +Request Content: + +``` +( + List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +*Updated validation* + +No more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` may be requested at a time. \ No newline at end of file diff --git a/docs/specs/electra/validator.md b/docs/specs/electra/validator.md new file mode 100644 index 000000000..750eb1dea --- /dev/null +++ b/docs/specs/electra/validator.md @@ -0,0 +1,309 @@ +# Electra -- Honest Validator + +*Note*: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [Modified `GetPayloadResponse`](#modified-getpayloadresponse) +- [Containers](#containers) + - [Modified containers](#modified-containers) + - [`AggregateAndProof`](#aggregateandproof) + - [`SignedAggregateAndProof`](#signedaggregateandproof) +- [Protocol](#protocol) + - [`ExecutionEngine`](#executionengine) + - [Modified `get_payload`](#modified-get_payload) +- [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Attester slashings](#attester-slashings) + - [Attestations](#attestations) + - [Deposits](#deposits) + - [Execution payload](#execution-payload) + - [Execution Requests](#execution-requests) + - [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars) + - [Sidecar](#sidecar) +- [Attesting](#attesting) + - [Construct attestation](#construct-attestation) +- [Attestation aggregation](#attestation-aggregation) + - [Construct aggregate](#construct-aggregate) + + + + +## Introduction + +This document represents the changes to be made in the code of an "honest validator" to implement Electra. + +## Prerequisites + +This document is an extension of the [Deneb -- Honest Validator](../deneb/validator.md) guide. +All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [Electra](./beacon-chain.md) are requisite for this document and used throughout. +Please see related Beacon Chain doc before continuing and use them as a reference throughout. + +## Helpers + +### Modified `GetPayloadResponse` + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload + block_value: uint256 + blobs_bundle: BlobsBundle + execution_requests: Sequence[bytes] # [New in Electra] +``` + +## Containers + +### Modified containers + +#### `AggregateAndProof` + +```python +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation # [Modified in Electra:EIP7549] + selection_proof: BLSSignature +``` + +#### `SignedAggregateAndProof` + +```python +class SignedAggregateAndProof(Container): + message: AggregateAndProof # [Modified in Electra:EIP7549] + signature: BLSSignature +``` + +## Protocol + +### `ExecutionEngine` + +#### Modified `get_payload` + +Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that +has been built since the corresponding call to `notify_forkchoice_updated` method. + +```python +def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + """ + Return ExecutionPayload, uint256, BlobsBundle and execution requests (as Sequence[bytes]) objects. + """ + # pylint: disable=unused-argument + ... +``` + +## Block proposal + +### Constructing the `BeaconBlockBody` + +#### Attester slashings + +Changed the max attester slashings size to `MAX_ATTESTER_SLASHINGS_ELECTRA`. + +#### Attestations + +Changed the max attestations size to `MAX_ATTESTATIONS_ELECTRA`. + +The network attestation aggregates contain only the assigned committee attestations. +Attestation aggregates received by the block proposer from the committee aggregators with disjoint `committee_bits` sets and equal `AttestationData` SHOULD be consolidated into a single `Attestation` object. +The proposer should run the following function to construct an on chain final aggregate from a list of network aggregates with equal `AttestationData`: + +```python +def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation: + aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0]) + + data = aggregates[0].data + aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]() + for a in aggregates: + for b in a.aggregation_bits: + aggregation_bits.append(b) + + signature = bls.Aggregate([a.signature for a in aggregates]) + + committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates] + committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)] + committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags) + + return Attestation( + aggregation_bits=aggregation_bits, + data=data, + committee_bits=committee_bits, + signature=signature, + ) +``` + +#### Deposits + +*[New in Electra:EIP6110]* The expected number of deposits MUST be changed from `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)` to the result of the following function: + +```python +def get_eth1_pending_deposit_count(state: BeaconState) -> uint64: + eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index) + if state.eth1_deposit_index < eth1_deposit_index_limit: + return min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index) + else: + return uint64(0) +``` + +*Note*: Clients will be able to remove the `Eth1Data` polling mechanism in an uncoordinated fashion once the transition period is finished. The transition period is considered finished when a network reaches the point where `state.eth1_deposit_index == state.deposit_requests_start_index`. + +```python +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + # [New in Electra:EIP6110] + if state.eth1_deposit_index == state.deposit_requests_start_index: + return state.eth1_data + + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) +``` + +#### Execution payload + +`prepare_execution_payload` is updated from the Deneb specs. + +*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied. +That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`. + +*Note*: The only change to `prepare_execution_payload` is the new definition of `get_expected_withdrawals`. + +```python +def prepare_execution_payload(state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + # Verify consistency of the parent hash with respect to the previous execution payload header + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + withdrawals, _ = get_expected_withdrawals(state) # [Modified in EIP-7251] + + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=withdrawals, + parent_beacon_block_root=hash_tree_root(state.latest_block_header), + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) +``` + +#### Execution Requests + +*[New in Electra]* + +1. The execution payload is obtained from the execution engine as defined above using `payload_id`. The response also includes a `execution_requests` entry containing a list of bytes. Each element on the list corresponds to one SSZ list of requests as defined in [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The first byte of each request is used to determine the request type. Requests must be ordered by request type in ascending order. As a result, there can only be at most one instance of each request type. +2. Set `block.body.execution_requests = get_execution_requests(execution_requests)`, where: + +```python +def get_execution_requests(execution_requests_list: Sequence[bytes]) -> ExecutionRequests: + deposits = [] + withdrawals = [] + consolidations = [] + + request_types = [ + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + CONSOLIDATION_REQUEST_TYPE, + ] + + prev_request_type = None + for request in execution_requests_list: + request_type, request_data = request[0:1], request[1:] + + # Check that the request type is valid + assert request_type in request_types + # Check that the request data is not empty + assert len(request_data) != 0 + # Check that requests are in strictly ascending order + # Each successive type must be greater than the last with no duplicates + assert prev_request_type is None or prev_request_type < request_type + prev_request_type = request_type + + if request_type == DEPOSIT_REQUEST_TYPE: + deposits = ssz_deserialize( + List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], + request_data + ) + elif request_type == WITHDRAWAL_REQUEST_TYPE: + withdrawals = ssz_deserialize( + List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], + request_data + ) + elif request_type == CONSOLIDATION_REQUEST_TYPE: + consolidations = ssz_deserialize( + List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD], + request_data + ) + + return ExecutionRequests( + deposits=deposits, + withdrawals=withdrawals, + consolidations=consolidations, + ) +``` + +### Constructing the `BlobSidecar`s + +#### Sidecar + +*[Modified in Electra:EIP7691]* + +```python +def compute_subnet_for_blob_sidecar(blob_index: BlobIndex) -> SubnetID: + return SubnetID(blob_index % BLOB_SIDECAR_SUBNET_COUNT_ELECTRA) +``` + +## Attesting + +### Construct attestation + +The validator creates `attestation` as a `SingleAttestation` container +with updated field assignments: + +- Set `attestation_data.index = 0`. +- Set `attestation.committee_index` to the index associated with the validator's committee. +- Set `attestation.attester_index` to the index of the validator. + +## Attestation aggregation + +### Construct aggregate + +- Set `attestation_data.index = 0`. +- Let `aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`. +- Set `attestation.committee_bits = committee_bits`, where `committee_bits` has the bit set corresponding to `committee_index` in each individual attestation. \ No newline at end of file diff --git a/electra-gap.md b/electra-gap.md new file mode 100644 index 000000000..cbecad4b1 --- /dev/null +++ b/electra-gap.md @@ -0,0 +1,114 @@ +# Implementation Gaps for Electra Upgrade + +This document outlines the gaps in the current implementation of the Electra. It's still a WIP. + +## Difference Between Updated and Modified + +- **Updated**: Changes in validation rules, protocols, or external behaviors. These changes may not directly alter the logic of the implementation. +- **Modified**: Refers to direct changes made to the code or logic of an existing function, container, or process to accommodate new requirements or features. + +## Containers + +- [x] New `PendingDeposit` ([Spec](docs/specs/electra/beacon-chain.md#pendingdeposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `PendingPartialWithdrawal` ([Spec](docs/specs/electra/beacon-chain.md#pendingpartialwithdrawal), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `PendingConsolidation` ([Spec](docs/specs/electra/beacon-chain.md#pendingconsolidation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `DepositRequest` ([Spec](docs/specs/electra/beacon-chain.md#depositrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `WithdrawalRequest` ([Spec](docs/specs/electra/beacon-chain.md#withdrawalrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `ConsolidationRequest` ([Spec](docs/specs/electra/beacon-chain.md#consolidationrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `ExecutionRequests` ([Spec](docs/specs/electra/beacon-chain.md#executionrequests), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] New `SingleAttestation` ([Spec](docs/specs/electra/beacon-chain.md#singleattestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] Modified `AttesterSlashing` ([Spec](docs/specs/electra/beacon-chain.md#attesterslashing), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] Modified `BeaconBlockBody` ([Spec](docs/specs/electra/beacon-chain.md#beaconblockbody), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] Modified `Attestation` ([Spec](docs/specs/electra/beacon-chain.md#attestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] Modified `IndexedAttestation` ([Spec](docs/specs/electra/beacon-chain.md#indexedattestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) +- [x] Modified `BeaconState` ([Spec](docs/specs/electra/beacon-chain.md#beaconstate), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) + +## Predicates + +- [x] Modified `is_eligible_for_activation_queue` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_eligible_for_activation_queue), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] New `is_compounding_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-is_compounding_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] New `has_compounding_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-has_compounding_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] New `has_execution_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-has_execution_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] Modified `is_fully_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_fully_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] Modified `is_partially_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) + + +## Beacon State Accessors + +- [x] Modified `get_attesting_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_attesting_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] Modified `get_next_sync_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) +- [x] New `get_balance_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_balance_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) +- [x] New `get_activation_exit_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_activation_exit_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) +- [ ] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit)) +- [ ] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw)) + +## Beacon State Mutators + +- [x] Modified `initiate_validator_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-initiate_validator_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) +- [ ] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator)) +- [ ] New `queue_excess_active_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-queue_excess_active_balance)) +- [x] New `compute_exit_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_exit_epoch_and_update_churn), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) +- [ ] New `compute_consolidation_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_consolidation_epoch_and_update_churn)) +- [x] Modified `slash_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-slash_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) + +## Miscellaneous + +- [x] New `get_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#new-get_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) +- [x] Modified `compute_proposer_index` ([Spec](docs/specs/electra/beacon-chain.md#modified-compute_proposer_index), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) +- [x] New `get_max_effective_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-get_max_effective_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) + +## Epoch Processing + +- [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch)) +- [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) +- [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) +- [ ] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit)) +- [ ] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits)) +- [ ] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations)) +- [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates)) + +## Block Processing + +- [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) +- [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) +- [ ] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations)) +- [ ] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation)) +- [ ] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit)) +- [ ] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit)) +- [ ] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request)) +- [ ] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request)) +- [ ] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request)) + +## Execution Engine + +- [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash)) +- [ ] Modified `notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-notify_new_payload)) +- [ ] Modified `verify_and_notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-verify_and_notify_new_payload)) + +## Networking + +- [ ] Updated `beacon_block` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_block)) +- [ ] Updated `beacon_aggregate_and_proof` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_aggregate_and_proof)) +- [ ] Updated `blob_sidecar_{subnet_id}` topic validation ([Spec](docs/specs/electra/p2p-interface.md#blob_sidecar_subnet_id)) +- [ ] Updated `beacon_attestation_{subnet_id}` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_attestation_subnet_id)) +- [ ] Updated `BeaconBlocksByRange v2` ([Spec](docs/specs/electra/p2p-interface.md#beaconblocksbyrange-v2)) +- [ ] Updated `BeaconBlocksByRoot v2` ([Spec](docs/specs/electra/p2p-interface.md#beaconblocksbyroot-v2)) +- [ ] Updated `BlobSidecarsByRange v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyrange-v1)) +- [ ] Updated `BlobSidecarsByRoot v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyroot-v1)) + +## Honest Validator + +- [ ] Modified `GetPayloadResponse` ([Spec](docs/specs/electra/validator.md#modified-getpayloadresponse)) +- [ ] Modified `AggregateAndProof` ([Spec](docs/specs/electra/validator.md#aggregateandproof)) +- [ ] Modified `SignedAggregateAndProof` ([Spec](docs/specs/electra/validator.md#signedaggregateandproof)) +- [ ] Modified `get_payload` ([Spec](docs/specs/electra/validator.md#modified-get_payload)) +- [ ] Updated `prepare_execution_payload` ([Spec](docs/specs/electra/validator.md#execution-payload)) +- [ ] New `get_execution_requests` ([Spec](docs/specs/electra/validator.md#execution-requests)) +- [ ] Updated `compute_subnet_for_blob_sidecar` ([Spec](docs/specs/electra/validator.md#sidecar)) +- [ ] Updated `construct attestation` ([Spec](docs/specs/electra/validator.md#construct-attestation)) +- [ ] Updated `construct aggregate` ([Spec](docs/specs/electra/validator.md#construct-aggregate)) + +## Fork Logic + +- [ ] Modified `compute_fork_version` ([Spec](docs/specs/electra/fork.md#modified-compute_fork_version)) +- [ ] New `upgrade_to_electra` ([Spec](docs/specs/electra/fork.md#upgrade_to_electra)) From 68dec8f7886c9678ffa40002fc0dfb690232a7ac Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:50:16 -0300 Subject: [PATCH 11/23] feat: electra process_registry_updates & slash_validators changes (#1420) --- config/networks/mainnet/config.yaml | 6 + config/networks/minimal/config.yaml | 6 + config/presets/mainnet/electra.yaml | 2 +- config/presets/minimal/electra.yaml | 2 +- .../state_transition/accessors.ex | 25 +++++ .../state_transition/epoch_processing.ex | 104 +++++++++++------- .../state_transition/mutators.ex | 95 +++++++++------- .../state_transition/operations.ex | 2 +- 8 files changed, 158 insertions(+), 84 deletions(-) diff --git a/config/networks/mainnet/config.yaml b/config/networks/mainnet/config.yaml index 47dc09509..37c1b6828 100644 --- a/config/networks/mainnet/config.yaml +++ b/config/networks/mainnet/config.yaml @@ -151,3 +151,9 @@ WHISK_PROPOSER_SELECTION_GAP: 2 # EIP7594 EIP7594_FORK_VERSION: 0x06000001 EIP7594_FORK_EPOCH: 18446744073709551615 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) Gwei +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9) (= 256,000,000,000) Gwei +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 diff --git a/config/networks/minimal/config.yaml b/config/networks/minimal/config.yaml index 72cbd013c..0e08d07f2 100644 --- a/config/networks/minimal/config.yaml +++ b/config/networks/minimal/config.yaml @@ -149,3 +149,9 @@ WHISK_PROPOSER_SELECTION_GAP: 1 # EIP7594 EIP7594_FORK_VERSION: 0x06000001 EIP7594_FORK_EPOCH: 18446744073709551615 + +# Electra +# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 +# [customized] 2**8 * 10**9) (= 256,000,000,000) Gwei +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 diff --git a/config/presets/mainnet/electra.yaml b/config/presets/mainnet/electra.yaml index 0d70433f3..42afbb233 100644 --- a/config/presets/mainnet/electra.yaml +++ b/config/presets/mainnet/electra.yaml @@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 # Pending deposits processing # --------------------------------------------------------------- # 2**4 ( = 4) pending deposits -MAX_PENDING_DEPOSITS_PER_EPOCH: 16 \ No newline at end of file +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/config/presets/minimal/electra.yaml b/config/presets/minimal/electra.yaml index a3f8fbf22..44e476975 100644 --- a/config/presets/minimal/electra.yaml +++ b/config/presets/minimal/electra.yaml @@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2 # Pending deposits processing # --------------------------------------------------------------- # 2**4 ( = 4) pending deposits -MAX_PENDING_DEPOSITS_PER_EPOCH: 16 \ No newline at end of file +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index c7400f27e..d35709fec 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -657,4 +657,29 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index end + + @doc """ + Return the churn limit for the current epoch. + """ + @spec get_balance_churn_limit(Types.BeaconState.t()) :: Types.gwei() + def get_balance_churn_limit(state) do + churn = + max( + ChainSpec.get("MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA"), + div(get_total_active_balance(state), ChainSpec.get("CHURN_LIMIT_QUOTIENT")) + ) + + churn - rem(churn, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")) + end + + @doc """ + Return the churn limit for the current epoch dedicated to activations and exits. + """ + @spec get_activation_exit_churn_limit(Types.BeaconState.t()) :: Types.gwei() + def get_activation_exit_churn_limit(state) do + min( + ChainSpec.get("MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT"), + get_balance_churn_limit(state) + ) + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex index f3b63712b..08833bba3 100644 --- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex +++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex @@ -144,49 +144,75 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do current_epoch = Accessors.get_current_epoch(state) activation_exit_epoch = Misc.compute_activation_exit_epoch(current_epoch) - churn_limit = Accessors.get_validator_activation_churn_limit(state) + validators + |> Enum.with_index() + |> Enum.reduce_while(state, fn {validator, idx}, state -> + handle_validator_registry_update( + state, + validator, + idx, + current_epoch, + activation_exit_epoch, + ejection_balance + ) + end) + |> then(fn + %BeaconState{} = state -> {:ok, state} + {:error, reason} -> {:error, reason} + end) + end - result = - validators - |> Stream.with_index() - |> Stream.map(fn {v, i} -> - {{v, i}, Predicates.eligible_for_activation_queue?(v), - Predicates.active_validator?(v, current_epoch) and - v.effective_balance <= ejection_balance} - end) - |> Stream.filter(&(elem(&1, 1) or elem(&1, 2))) - |> Stream.map(fn - {{v, i}, true, b} -> {{%{v | activation_eligibility_epoch: current_epoch + 1}, i}, b} - {{v, i}, false = _is_eligible, b} -> {{v, i}, b} - end) - |> Enum.reduce({:ok, state}, fn - _, {:error, _} = err -> err - {{v, i}, should_be_ejected}, {:ok, st} -> eject_validator(st, v, i, should_be_ejected) - {err, _}, _ -> err - end) + defp handle_validator_registry_update( + state, + validator, + idx, + current_epoch, + activation_exit_epoch, + ejection_balance + ) do + cond do + Predicates.eligible_for_activation_queue?(validator) -> + updated_validator = %Validator{ + validator + | activation_eligibility_epoch: current_epoch + 1 + } - with {:ok, new_state} <- result do - new_state.validators - |> Stream.with_index() - |> Stream.filter(fn {v, _} -> Predicates.eligible_for_activation?(state, v) end) - |> Enum.sort_by(fn {%{activation_eligibility_epoch: ep}, i} -> {ep, i} end) - |> Enum.take(churn_limit) - |> Enum.reduce(new_state.validators, fn {v, i}, acc -> - %{v | activation_epoch: activation_exit_epoch} - |> then(&Aja.Vector.replace_at!(acc, i, &1)) - end) - |> then(&{:ok, %BeaconState{new_state | validators: &1}}) - end - end + {:cont, + %BeaconState{ + state + | validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator) + }} - defp eject_validator(state, validator, index, false) do - {:ok, %{state | validators: Aja.Vector.replace_at!(state.validators, index, validator)}} - end + Predicates.active_validator?(validator, current_epoch) && + validator.effective_balance <= ejection_balance -> + case Mutators.initiate_validator_exit(state, validator) do + {:ok, {state, ejected_validator}} -> + updated_state = %{ + state + | validators: Aja.Vector.replace_at!(state.validators, idx, ejected_validator) + } + + {:cont, updated_state} + + {:error, msg} -> + {:halt, {:error, msg}} + end + + Predicates.eligible_for_activation?(state, validator) -> + updated_validator = %Validator{ + validator + | activation_epoch: activation_exit_epoch + } + + updated_state = %BeaconState{ + state + | validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator) + } + + {:cont, updated_state} - defp eject_validator(state, validator, index, true) do - with {:ok, ejected_validator} <- Mutators.initiate_validator_exit(state, validator) do - {:ok, - %{state | validators: Aja.Vector.replace_at!(state.validators, index, ejected_validator)}} + true -> + {:cont, state} end end diff --git a/lib/lambda_ethereum_consensus/state_transition/mutators.ex b/lib/lambda_ethereum_consensus/state_transition/mutators.ex index 1deb14701..53a839996 100644 --- a/lib/lambda_ethereum_consensus/state_transition/mutators.ex +++ b/lib/lambda_ethereum_consensus/state_transition/mutators.ex @@ -11,59 +11,33 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do Initiate the exit of the validator with index ``index``. """ @spec initiate_validator_exit(BeaconState.t(), integer()) :: - {:ok, Validator.t()} | {:error, String.t()} + {:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()} def initiate_validator_exit(%BeaconState{} = state, index) when is_integer(index) do initiate_validator_exit(state, Aja.Vector.at!(state.validators, index)) end @spec initiate_validator_exit(BeaconState.t(), Validator.t()) :: - {:ok, Validator.t()} | {:error, String.t()} + {:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()} def initiate_validator_exit(%BeaconState{} = state, %Validator{} = validator) do far_future_epoch = Constants.far_future_epoch() min_validator_withdrawability_delay = ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY") if validator.exit_epoch != far_future_epoch do - {:ok, validator} + {:ok, {state, validator}} else - exit_epochs = - state.validators - |> Stream.filter(fn validator -> - validator.exit_epoch != far_future_epoch - end) - |> Stream.map(fn validator -> validator.exit_epoch end) - |> Enum.to_list() - - exit_queue_epoch = - Enum.max( - exit_epochs ++ [Misc.compute_activation_exit_epoch(Accessors.get_current_epoch(state))] - ) - - exit_queue_churn = - state.validators - |> Stream.filter(fn validator -> - validator.exit_epoch == exit_queue_epoch - end) - |> Enum.to_list() - |> length() - - exit_queue_epoch = - if exit_queue_churn >= Accessors.get_validator_churn_limit(state) do - exit_queue_epoch + 1 - else - exit_queue_epoch - end - - next_withdrawable_epoch = exit_queue_epoch + min_validator_withdrawability_delay + state = compute_exit_epoch_and_update_churn(state, validator.effective_balance) + exit_queue_epoch = state.earliest_exit_epoch - if next_withdrawable_epoch > Constants.far_future_epoch() do - {:error, "withdrawable_epoch_too_large"} + if exit_queue_epoch + min_validator_withdrawability_delay > 2 ** 64 do + {:error, "withdrawable_epoch overflow"} else {:ok, - %{ - validator - | exit_epoch: exit_queue_epoch, - withdrawable_epoch: next_withdrawable_epoch - }} + {state, + %{ + validator + | exit_epoch: exit_queue_epoch, + withdrawable_epoch: exit_queue_epoch + min_validator_withdrawability_delay + }}} end end end @@ -78,17 +52,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do ) :: {:ok, BeaconState.t()} | {:error, String.t()} def slash_validator(state, slashed_index, whistleblower_index \\ nil) do - with {:ok, validator} <- initiate_validator_exit(state, slashed_index), + with {:ok, {state, validator}} <- initiate_validator_exit(state, slashed_index), state = add_slashing(state, validator, slashed_index), {:ok, proposer_index} <- Accessors.get_beacon_proposer_index(state) do slashing_penalty = validator.effective_balance - |> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX")) + |> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA")) whistleblower_index = whistleblower_index(whistleblower_index, proposer_index) whistleblower_reward = - div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT")) + div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA")) proposer_reward = (whistleblower_reward * Constants.proposer_weight()) @@ -187,4 +161,41 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do ) |> then(&{:ok, &1}) end + + @spec compute_exit_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) :: + Types.BeaconState.t() + def compute_exit_epoch_and_update_churn(state, exit_balance) do + current_epoch = Accessors.get_current_epoch(state) + + earliest_exit_epoch = + max(state.earliest_exit_epoch, Misc.compute_activation_exit_epoch(current_epoch)) + + per_epoch_churn = Accessors.get_activation_exit_churn_limit(state) + + exit_balance_to_consume = + if state.earliest_exit_epoch < earliest_exit_epoch do + per_epoch_churn + else + state.exit_balance_to_consume + end + + {earliest_exit_epoch, exit_balance_to_consume} = + if exit_balance > exit_balance_to_consume do + balance_to_process = exit_balance - exit_balance_to_consume + additional_epochs = div(balance_to_process - 1, per_epoch_churn) + 1 + + { + earliest_exit_epoch + additional_epochs, + exit_balance_to_consume + additional_epochs * per_epoch_churn + } + else + {earliest_exit_epoch, exit_balance_to_consume} + end + + %BeaconState{ + state + | exit_balance_to_consume: exit_balance_to_consume - exit_balance, + earliest_exit_epoch: earliest_exit_epoch + } + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index 89447ff14..5022663f6 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -571,7 +571,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do {:error, "invalid signature"} true -> - with {:ok, validator} <- Mutators.initiate_validator_exit(state, validator_index) do + with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do Aja.Vector.replace_at!(state.validators, validator_index, validator) |> then(&{:ok, %BeaconState{state | validators: &1}}) end From be027082b373bb7680165bf4df05dd00a886e582 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Tue, 15 Apr 2025 10:52:52 -0300 Subject: [PATCH 12/23] fix: skipped spec tests in electra (#1427) --- test/spec/runners/bls.ex | 16 ----- test/spec/runners/epoch_processing.ex | 20 +----- test/spec/runners/finality.ex | 21 ------ test/spec/runners/fork_choice.ex | 6 -- test/spec/runners/kzg.ex | 15 ---- test/spec/runners/light_client.ex | 11 +-- test/spec/runners/operations.ex | 43 ------------ test/spec/runners/random.ex | 6 -- test/spec/runners/rewards.ex | 15 ---- test/spec/runners/sanity.ex | 98 +-------------------------- test/spec/runners/shuffling.ex | 10 --- test/spec/runners/ssz_generic.ex | 24 ------- test/spec/runners/ssz_static.ex | 52 +------------- test/spec/runners/sync.ex | 13 ---- 14 files changed, 9 insertions(+), 341 deletions(-) diff --git a/test/spec/runners/bls.ex b/test/spec/runners/bls.ex index 935fdc876..ec148603d 100644 --- a/test/spec/runners/bls.ex +++ b/test/spec/runners/bls.ex @@ -6,22 +6,6 @@ defmodule BlsTestRunner do use ExUnit.CaseTemplate use TestRunner - # Remove handler from here once you implement the corresponding functions - @disabled_handlers [ - # "sign", - # "verify", - # "aggregate", - # "fast_aggregate_verify", - # "aggregate_verify", - # "eth_aggregate_pubkeys" - # "eth_fast_aggregate_verify" - ] - - @impl TestRunner - def skip?(%SpecTestCase{} = testcase) do - Enum.member?(@disabled_handlers, testcase.handler) - end - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/epoch_processing.ex b/test/spec/runners/epoch_processing.ex index cbc53d54f..1e8891d59 100644 --- a/test/spec/runners/epoch_processing.ex +++ b/test/spec/runners/epoch_processing.ex @@ -9,22 +9,9 @@ defmodule EpochProcessingTestRunner do use ExUnit.CaseTemplate use TestRunner - # Remove handler from here once you implement the corresponding functions + # TODO: We need to make sure this 2 are still needed to be here @disabled_handlers [ - # "justification_and_finalization", - # "inactivity_updates", - # "rewards_and_penalties", - # "registry_updates", - # "slashings", - # "effective_balance_updates", - # "eth1_data_reset", - # "slashings_reset", - # "randao_mixes_reset", - # "historical_summaries_update", "participation_record_updates" - - # "participation_flag_updates", - # "sync_committee_updates" ] @deprecated_handlers [ @@ -36,10 +23,7 @@ defmodule EpochProcessingTestRunner do Enum.member?(@disabled_handlers ++ @deprecated_handlers, handler) end - @impl TestRunner - def skip?(%SpecTestCase{fork: "deneb"}), do: false - def skip?(%SpecTestCase{fork: "electra"}), do: false - def skip?(_), do: true + def skip?(_), do: false @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do diff --git a/test/spec/runners/finality.ex b/test/spec/runners/finality.ex index 99f7a3cc3..e569f0b59 100644 --- a/test/spec/runners/finality.ex +++ b/test/spec/runners/finality.ex @@ -6,27 +6,6 @@ defmodule FinalityTestRunner do use ExUnit.CaseTemplate use TestRunner - @disabled_cases [ - # "finality_no_updates_at_genesis", - # "finality_rule_1", - # "finality_rule_2", - # "finality_rule_3", - # "finality_rule_4" - ] - - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella", case: testcase}) do - Enum.member?(@disabled_cases, testcase) - end - - @impl TestRunner - def skip?(%SpecTestCase{fork: "deneb", case: testcase}) do - Enum.member?(@disabled_cases, testcase) - end - - @impl TestRunner - def skip?(_), do: true - @impl TestRunner def run_test_case(testcase) do Helpers.ProcessBlocks.process_blocks(testcase) diff --git a/test/spec/runners/fork_choice.ex b/test/spec/runners/fork_choice.ex index 2f3ecbff8..5f7702601 100644 --- a/test/spec/runners/fork_choice.ex +++ b/test/spec/runners/fork_choice.ex @@ -23,12 +23,6 @@ defmodule ForkChoiceTestRunner do :ok end - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella"}), do: false - def skip?(%SpecTestCase{fork: "deneb"}), do: false - def skip?(%SpecTestCase{fork: "electra"}), do: false - def skip?(_testcase), do: true - @impl TestRunner def run_test_case(testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/kzg.ex b/test/spec/runners/kzg.ex index f5ca1f471..7ffac9b33 100644 --- a/test/spec/runners/kzg.ex +++ b/test/spec/runners/kzg.ex @@ -6,21 +6,6 @@ defmodule KzgTestRunner do use ExUnit.CaseTemplate use TestRunner - # Remove handler from here once you implement the corresponding functions - @disabled_handlers [ - # "blob_to_kzg_commitment" - # "compute_kzg_proof", - # "verify_kzg_proof", - # "compute_blob_kzg_proof", - # "verify_blob_kzg_proof", - # "verify_blob_kzg_proof_batch" - ] - - @impl TestRunner - def skip?(%SpecTestCase{} = testcase) do - Enum.member?(@disabled_handlers, testcase.handler) - end - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/light_client.ex b/test/spec/runners/light_client.ex index 01620af0d..fec092b09 100644 --- a/test/spec/runners/light_client.ex +++ b/test/spec/runners/light_client.ex @@ -19,15 +19,8 @@ defmodule LightClientTestRunner do Enum.member?(@disabled_handlers, testcase.handler) end - def skip?(%SpecTestCase{fork: "deneb"} = _testcase) do - # TODO: all of them fail - true - end - - @impl TestRunner - def skip?(_testcase) do - true - end + # TODO: We didn't implement lightclient functions yet + def skip?(%SpecTestCase{fork: fork}) when fork in ["deneb", "electra"], do: true @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do diff --git a/test/spec/runners/operations.ex b/test/spec/runners/operations.ex index 62fa019e5..a30290ad6 100644 --- a/test/spec/runners/operations.ex +++ b/test/spec/runners/operations.ex @@ -53,49 +53,6 @@ defmodule OperationsTestRunner do # "deposit_receipt" => "deposit_receipt" Not yet implemented } - # Remove handler from here once you implement the corresponding functions - # "deposit_receipt" handler is not yet implemented - @disabled_handlers [ - # "attester_slashing", - # "attestation", - # "block_header", - # "deposit", - # "proposer_slashing", - # "voluntary_exit", - # "sync_aggregate", - # "execution_payload", - # "withdrawals", - # "bls_to_execution_change" - ] - - @disabled_handlers_deneb [ - # "attester_slashing", - # "attestation", - # "block_header", - # "deposit", - # "proposer_slashing", - # "voluntary_exit" - # "sync_aggregate", - # "execution_payload", - # "withdrawals", - # "bls_to_execution_change" - ] - - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella", handler: handler}) do - Enum.member?(@disabled_handlers, handler) - end - - @impl TestRunner - def skip?(%SpecTestCase{fork: "deneb", handler: handler}) do - Enum.member?(@disabled_handlers_deneb, handler) - end - - @impl TestRunner - def skip?(_testcase) do - true - end - @impl TestRunner def run_test_case(%SpecTestCase{handler: handler} = testcase) do case_dir = SpecTestCase.dir(testcase) <> "/" diff --git a/test/spec/runners/random.ex b/test/spec/runners/random.ex index fe47692db..e8dfb9f7d 100644 --- a/test/spec/runners/random.ex +++ b/test/spec/runners/random.ex @@ -6,12 +6,6 @@ defmodule RandomTestRunner do use ExUnit.CaseTemplate use TestRunner - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella"}), do: false - def skip?(%SpecTestCase{fork: "deneb"}), do: false - def skip?(%SpecTestCase{fork: "electra"}), do: false - def skip?(_), do: true - @impl TestRunner def run_test_case(testcase) do Helpers.ProcessBlocks.process_blocks(testcase) diff --git a/test/spec/runners/rewards.ex b/test/spec/runners/rewards.ex index 2c535df83..afbde0465 100644 --- a/test/spec/runners/rewards.ex +++ b/test/spec/runners/rewards.ex @@ -6,21 +6,6 @@ defmodule RewardsTestRunner do use TestRunner alias Types.BeaconState - @disabled [ - # "basic", - # "leak", - # "random" - ] - - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella", handler: handler}) do - Enum.member?(@disabled, handler) - end - - def skip?(%SpecTestCase{fork: "deneb"}), do: false - def skip?(%SpecTestCase{fork: "electra"}), do: false - def skip?(_), do: true - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/sanity.ex b/test/spec/runners/sanity.ex index 3cfa468e2..13be8a2e9 100644 --- a/test/spec/runners/sanity.ex +++ b/test/spec/runners/sanity.ex @@ -10,109 +10,17 @@ defmodule SanityTestRunner do alias LambdaEthereumConsensus.Utils.Diff alias Types.BeaconState - @disabled_block_cases [ - # "activate_and_partial_withdrawal_max_effective_balance", - # "activate_and_partial_withdrawal_overdeposit", - # "attestation", - # "attester_slashing", - # "balance_driven_status_transitions", - # "bls_change", - # "deposit_and_bls_change", - # "deposit_in_block", - # "deposit_top_up", - # "duplicate_attestation_same_block", - # "invalid_is_execution_enabled_false", - # "empty_block_transition", - # "empty_block_transition_large_validator_set", - # "empty_block_transition_no_tx", - # "empty_block_transition_randomized_payload", - # "empty_epoch_transition", - # "empty_epoch_transition_large_validator_set", - # "empty_epoch_transition_not_finalizing", - # "eth1_data_votes_consensus", - # "eth1_data_votes_no_consensus", - # "exit_and_bls_change", - # "full_random_operations_0", - # "full_random_operations_1", - # "full_random_operations_2", - # "full_random_operations_3", - # "full_withdrawal_in_epoch_transition", - # "high_proposer_index", - # "historical_batch", - # "inactivity_scores_full_participation_leaking", - # "inactivity_scores_leaking", - # "invalid_all_zeroed_sig", - # "invalid_duplicate_attester_slashing_same_block", - # "invalid_duplicate_bls_changes_same_block", - # "invalid_duplicate_deposit_same_block", - # "invalid_duplicate_proposer_slashings_same_block", - # "invalid_duplicate_validator_exit_same_block", - # "invalid_incorrect_block_sig", - # "invalid_incorrect_proposer_index_sig_from_expected_proposer", - # "invalid_incorrect_proposer_index_sig_from_proposer_index", - # "invalid_incorrect_state_root", - # "invalid_only_increase_deposit_count", - # "invalid_parent_from_same_slot", - # "invalid_prev_slot_block_transition", - # "invalid_same_slot_block_transition", - # "invalid_similar_proposer_slashings_same_block", - # "invalid_two_bls_changes_of_different_addresses_same_validator_same_block", - # "invalid_withdrawal_fail_second_block_payload_isnt_compatible", - # "is_execution_enabled_false", - # "many_partial_withdrawals_in_epoch_transition", - # "multiple_attester_slashings_no_overlap", - # "multiple_attester_slashings_partial_overlap", - # "multiple_different_proposer_slashings_same_block", - # "multiple_different_validator_exits_same_block", - # "partial_withdrawal_in_epoch_transition", - # "proposer_after_inactive_index", - # "proposer_self_slashing", - # "proposer_slashing", - # "skipped_slots", - # "slash_and_exit_diff_index", - # "slash_and_exit_same_index" - # "sync_committee_committee__empty", - # "sync_committee_committee__full", - # "sync_committee_committee__half", - # "sync_committee_committee_genesis__empty", - # "sync_committee_committee_genesis__full", - # "sync_committee_committee_genesis__half", - # "top_up_and_partial_withdrawable_validator", - # "top_up_to_fully_withdrawn_validator", - # "voluntary_exit", - # "withdrawal_success_two_blocks" - ] - + # TODO: We need to make sure this is still needed to be here @disabled_slot_cases [ - # "empty_epoch", - # "slots_1", - # "slots_2", - # "over_epoch_boundary", - # NOTE: too long to run in CI - # TODO: optimize "historical_accumulator" - - # "double_empty_epoch" ] @impl TestRunner - def skip?(%SpecTestCase{fork: "capella", handler: "blocks", case: testcase}) do - Enum.member?(@disabled_block_cases, testcase) - end - - def skip?(%SpecTestCase{fork: "capella", handler: "slots", case: testcase}) do - Enum.member?(@disabled_slot_cases, testcase) - end - - def skip?(%SpecTestCase{fork: "deneb", handler: "blocks", case: testcase}) do - Enum.member?(@disabled_block_cases, testcase) - end - - def skip?(%SpecTestCase{fork: "deneb", handler: "slots", case: testcase}) do + def skip?(%SpecTestCase{handler: "slots", case: testcase}) do Enum.member?(@disabled_slot_cases, testcase) end - def skip?(_), do: true + def skip?(_), do: false @impl TestRunner def run_test_case(%SpecTestCase{handler: "slots"} = testcase) do diff --git a/test/spec/runners/shuffling.ex b/test/spec/runners/shuffling.ex index 805c823d2..4f8bf669e 100644 --- a/test/spec/runners/shuffling.ex +++ b/test/spec/runners/shuffling.ex @@ -9,16 +9,6 @@ defmodule ShufflingTestRunner do alias LambdaEthereumConsensus.StateTransition.Misc alias LambdaEthereumConsensus.StateTransition.Shuffling - # Remove handler from here once you implement the corresponding functions - @disabled_handlers [ - # "core" - ] - - @impl TestRunner - def skip?(%SpecTestCase{} = testcase) do - Enum.member?(@disabled_handlers, testcase.handler) - end - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/ssz_generic.ex b/test/spec/runners/ssz_generic.ex index 2b9ef8ffc..61d4ded71 100644 --- a/test/spec/runners/ssz_generic.ex +++ b/test/spec/runners/ssz_generic.ex @@ -5,30 +5,6 @@ defmodule SszGenericTestRunner do use ExUnit.CaseTemplate use TestRunner - @disabled_handlers [ - # "basic_vector", - # "bitlist", - # "bitvector" - # "boolean", - # "containers" - # "uints" - ] - - @disabled_containers [ - # "SingleFieldTestStruct", - # "SmallTestStruct", - # "FixedTestStruct", - # "VarTestStruct", - # "ComplexTestStruct" - # "BitsStruct" - ] - - @impl TestRunner - def skip?(%SpecTestCase{fork: fork, handler: handler, case: cse}) do - skip_container? = Enum.any?(@disabled_containers, &String.contains?(cse, &1)) - fork != "phase0" or Enum.member?(@disabled_handlers, handler) or skip_container? - end - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do case_dir = SpecTestCase.dir(testcase) diff --git a/test/spec/runners/ssz_static.ex b/test/spec/runners/ssz_static.ex index f2c5a2dfb..d4c380c17 100644 --- a/test/spec/runners/ssz_static.ex +++ b/test/spec/runners/ssz_static.ex @@ -17,47 +17,6 @@ defmodule SszStaticTestRunner do @only_ssz_ex [Types.Eth1Block, Types.SyncAggregatorSelectionData] @disabled [ - # "DepositData", - # "DepositMessage", - # "Eth1Data", - # "ProposerSlashing", - # "SignedBeaconBlockHeader", - # "SignedVoluntaryExit", - # "Validator", - # "VoluntaryExit", - # "Attestation", - # "AttestationData", - # "BLSToExecutionChange", - # "BeaconBlockHeader", - # "Checkpoint", - # "Deposit", - # "SignedBLSToExecutionChange", - # "SigningData", - # "SyncCommittee", - # "SyncCommitteeMessage", - # "Withdrawal", - # "AttesterSlashing", - # "HistoricalSummary", - # "PendingAttestation", - # "Fork", - # "ForkData", - # "HistoricalBatch", - # "IndexedAttestation", - # "ExecutionPayload", - # "ExecutionPayloadHeader", - # "SignedBeaconBlock", - # "SyncAggregate", - # "AggregateAndProof", - # "BeaconBlock", - # "BeaconBlockBody", - # "BeaconState", - # "SignedAggregateAndProof", - # "Eth1Block", - # "SyncAggregatorSelectionData", - # "SignedContributionAndProof", - # "SyncCommitteeContribution", - # "ContributionAndProof", - # -- not defined yet "LightClientBootstrap", "LightClientOptimisticUpdate", "LightClientUpdate", @@ -76,15 +35,8 @@ defmodule SszStaticTestRunner do } @impl TestRunner - def skip?(%SpecTestCase{fork: "capella", handler: handler}) do - Enum.member?(@disabled, handler) - end - - def skip?(%SpecTestCase{fork: "deneb", handler: handler}) do - Enum.member?(@disabled, handler) - end - - def skip?(_), do: true + def skip?(%SpecTestCase{handler: handler}) when handler in @disabled, do: true + def skip?(_), do: false @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do diff --git a/test/spec/runners/sync.ex b/test/spec/runners/sync.ex index 6dac8f600..cdce17216 100644 --- a/test/spec/runners/sync.ex +++ b/test/spec/runners/sync.ex @@ -8,10 +8,6 @@ defmodule SyncTestRunner do alias LambdaEthereumConsensus.Execution.EngineApi - @disabled_cases [ - # "from_syncing_to_invalid" - ] - @impl TestRunner def setup() do # Start this supervisor, necessary for post state tasks. @@ -19,15 +15,6 @@ defmodule SyncTestRunner do :ok end - @impl TestRunner - def skip?(%SpecTestCase{fork: "capella"} = testcase) do - Enum.member?(@disabled_cases, testcase.case) - end - - def skip?(%SpecTestCase{fork: "deneb"}), do: false - def skip?(%SpecTestCase{fork: "electra"}), do: false - def skip?(_testcase), do: true - @impl TestRunner def run_test_case(%SpecTestCase{} = testcase) do original_engine_api_config = Application.fetch_env!(:lambda_ethereum_consensus, EngineApi) From bcc7782851a2f40061b2730f8518d39d7bb882cc Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Tue, 15 Apr 2025 16:33:52 -0300 Subject: [PATCH 13/23] fix: new ssz fixes for the static spec-test (#1430) --- native/ssz_nif/src/utils/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/ssz_nif/src/utils/mod.rs b/native/ssz_nif/src/utils/mod.rs index 3adaec86a..5e54bfa4c 100644 --- a/native/ssz_nif/src/utils/mod.rs +++ b/native/ssz_nif/src/utils/mod.rs @@ -53,6 +53,14 @@ macro_rules! schema_match { Epoch, BlobSidecar, BlobIdentifier, + PendingDeposit, + PendingPartialWithdrawal, + PendingConsolidation, + DepositRequest, + WithdrawalRequest, + ConsolidationRequest, + ExecutionRequests, + SingleAttestation, } ) }; From cb9ffb40a269cbcbded37e475667ddb241379449 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 16 Apr 2025 11:47:13 -0300 Subject: [PATCH 14/23] fix: outstanding ssz issues after #1430 (#1432) --- config/networks/mainnet/config.yaml | 6 ++++-- config/networks/minimal/config.yaml | 6 ++++-- config/presets/minimal/deneb.yaml | 8 +++----- lib/types/beacon_chain/beacon_block_body.ex | 2 +- native/ssz_nif/src/ssz_types/config.rs | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/config/networks/mainnet/config.yaml b/config/networks/mainnet/config.yaml index 37c1b6828..4bc84c29a 100644 --- a/config/networks/mainnet/config.yaml +++ b/config/networks/mainnet/config.yaml @@ -135,12 +135,14 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6 # Deneb # `2**7` (=128) MAX_REQUEST_BLOCKS_DENEB: 128 -# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK -MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 +## 6 blobs +MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars +MAX_REQUEST_BLOB_SIDECARS: 768 # Whisk # `Epoch(2**8)` diff --git a/config/networks/minimal/config.yaml b/config/networks/minimal/config.yaml index 0e08d07f2..291883bef 100644 --- a/config/networks/minimal/config.yaml +++ b/config/networks/minimal/config.yaml @@ -135,12 +135,14 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6 # Deneb # `2**7` (=128) MAX_REQUEST_BLOCKS_DENEB: 128 -# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK -MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 +## 6 blobs +MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars +MAX_REQUEST_BLOB_SIDECARS: 768 # Whisk WHISK_EPOCHS_PER_SHUFFLING_PHASE: 4 diff --git a/config/presets/minimal/deneb.yaml b/config/presets/minimal/deneb.yaml index bc4fe4369..8dde60e86 100644 --- a/config/presets/minimal/deneb.yaml +++ b/config/presets/minimal/deneb.yaml @@ -5,8 +5,6 @@ # `uint64(4096)` FIELD_ELEMENTS_PER_BLOB: 4096 # [customized] -MAX_BLOB_COMMITMENTS_PER_BLOCK: 16 -# `uint64(6)` -MAX_BLOBS_PER_BLOCK: 6 -# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 4 = 9 -KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9 +MAX_BLOB_COMMITMENTS_PER_BLOCK: 32 +# [customized] floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 4 + 1 + 5 = 10) +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 10 diff --git a/lib/types/beacon_chain/beacon_block_body.ex b/lib/types/beacon_chain/beacon_block_body.ex index 92968e17c..332f9dab8 100644 --- a/lib/types/beacon_chain/beacon_block_body.ex +++ b/lib/types/beacon_chain/beacon_block_body.ex @@ -38,7 +38,7 @@ defmodule Types.BeaconBlockBody do # max MAX_DEPOSITS deposits: list(Types.Deposit.t()), # max MAX_VOLUNTARY_EXITS - voluntary_exits: list(Types.VoluntaryExit.t()), + voluntary_exits: list(Types.SignedVoluntaryExit.t()), sync_aggregate: Types.SyncAggregate.t(), execution_payload: Types.ExecutionPayload.t(), # max MAX_BLS_TO_EXECUTION_CHANGES diff --git a/native/ssz_nif/src/ssz_types/config.rs b/native/ssz_nif/src/ssz_types/config.rs index c1c4ffff7..e3f8728f6 100644 --- a/native/ssz_nif/src/ssz_types/config.rs +++ b/native/ssz_nif/src/ssz_types/config.rs @@ -131,8 +131,8 @@ impl Config for Minimal { type SyncCommitteeSize = U32; type MaxWithdrawalsPerPayload = U4; type FieldElementsPerBlob = U4096; - type MaxBlobCommitmentsPerBlock = U16; - type KzgCommitmentInclusionProofDepth = U9; + type MaxBlobCommitmentsPerBlock = U32; + type KzgCommitmentInclusionProofDepth = U10; type MaxCommitteesPerSlot = U4; // Electra added fields type MaxDepositRequestsPerPayload = U4; From b19639b026019f52a7ec961e990097c606afc7f2 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 16 Apr 2025 15:04:29 -0300 Subject: [PATCH 15/23] fix: fixing the CI updating ubuntu and go (#1433) --- .github/workflows/ci.yml | 22 +++++++++++----------- .github/workflows/ci_skipped.yml | 12 ++++++------ .tool-versions | 2 +- Dockerfile | 2 +- test/unit/validator/block_builder_test.exs | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1ac5e42a..53cbd058c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,14 +30,14 @@ permissions: jobs: compile-native: name: Build native libraries - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Go # NOTE: this action comes with caching by default uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.24" cache-dependency-path: | native/libp2p_port/go.sum - name: Cache output artifacts @@ -58,7 +58,7 @@ jobs: download-beacon-node-oapi: name: Download Beacon Node OAPI - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Cache Beacon Node OAPI @@ -75,7 +75,7 @@ jobs: build: name: Build project needs: [compile-native, download-beacon-node-oapi] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Elixir @@ -135,7 +135,7 @@ jobs: docker-build: name: Build Docker image - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx @@ -150,7 +150,7 @@ jobs: smoke: name: Start and stop the node needs: [compile-native, download-beacon-node-oapi] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Elixir @@ -200,7 +200,7 @@ jobs: test: name: Test needs: [compile-native, download-beacon-node-oapi] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Elixir @@ -245,7 +245,7 @@ jobs: lint: name: Lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Elixir @@ -273,7 +273,7 @@ jobs: download-spectests: name: Download spectests - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Cache compressed spectests @@ -295,7 +295,7 @@ jobs: matrix: fork: ["deneb"] config: ["minimal", "general", "mainnet"] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Elixir @@ -357,7 +357,7 @@ jobs: spectests-success: name: All spec-tests passed needs: spectests-matrix - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: always() steps: - if: needs.spectests-matrix.result == 'success' diff --git a/.github/workflows/ci_skipped.yml b/.github/workflows/ci_skipped.yml index e2d1c9e50..3dc4b3232 100644 --- a/.github/workflows/ci_skipped.yml +++ b/.github/workflows/ci_skipped.yml @@ -14,36 +14,36 @@ on: jobs: compile-native: name: Build native libraries - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] build: name: Build project - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] smoke: name: Start and stop the node - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] test: name: Test - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] lint: name: Lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] spectests-success: name: All spec-tests passed - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: false steps: [run: true] diff --git a/.tool-versions b/.tool-versions index 3b0b441c2..329ddbcef 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,5 +1,5 @@ erlang 26.2 elixir 1.16.2-otp-26 -golang 1.22.12 +golang 1.24.2 rust 1.81.0 protoc 30.2 diff --git a/Dockerfile b/Dockerfile index 603376fd5..808e28f65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # libp2p port -FROM golang:1.22 AS libp2p_builder +FROM golang:1.24 AS libp2p_builder LABEL stage=builder # Install dependencies diff --git a/test/unit/validator/block_builder_test.exs b/test/unit/validator/block_builder_test.exs index fc3dd5c7a..02a94ea19 100644 --- a/test/unit/validator/block_builder_test.exs +++ b/test/unit/validator/block_builder_test.exs @@ -80,12 +80,12 @@ defmodule Unit.Validator.BlockBuilderTest do [proof] = BlockBuilder.compute_inclusion_proofs(body) - assert length(proof) == 9 + assert length(proof) == 10 commitment_root = SszEx.hash_tree_root!(commitment, TypeAliases.kzg_commitment()) # Manually computed generalized index of the commitment in the body - index = 0b101100000 + index = 0b1011000000 valid? = Predicates.valid_merkle_branch?(commitment_root, proof, length(proof), index, body_root) From c4bec8f5eb1eda8649685d4c3408d5db19c6f05c Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:06:11 -0300 Subject: [PATCH 16/23] feat: implement electra deposits (#1424) --- electra-gap.md | 15 +- .../state_transition/epoch_processing.ex | 151 ++++++++++++++++++ .../state_transition/mutators.ex | 56 ++++--- .../state_transition/operations.ex | 73 ++++++++- .../state_transition/state_transition.ex | 2 + lib/types/beacon_chain/deposit.ex | 21 ++- lib/types/beacon_chain/deposit_message.ex | 20 +++ test/spec/runners/operations.ex | 13 +- 8 files changed, 311 insertions(+), 40 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index cbecad4b1..58696950d 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -62,23 +62,26 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch)) - [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) - [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) -- [ ] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit)) -- [ ] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits)) +- [x] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) +- [x] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [ ] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations)) - [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates)) +- [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) ## Block Processing - [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) - [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) -- [ ] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations)) +- [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [ ] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation)) -- [ ] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit)) +- [x] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [ ] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit)) - [ ] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request)) -- [ ] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request)) +- [x] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [ ] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request)) - +- [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) +- [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) +- [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) ## Execution Engine - [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash)) diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex index 08833bba3..a3024c8a3 100644 --- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex +++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex @@ -10,6 +10,7 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do alias LambdaEthereumConsensus.Utils.BitVector alias LambdaEthereumConsensus.Utils.Randao alias Types.BeaconState + alias Types.DepositMessage alias Types.HistoricalSummary alias Types.Validator @@ -454,4 +455,154 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do max(balance + delta, 0) end) end + + @spec process_pending_deposits(BeaconState.t()) :: {:ok, BeaconState.t()} + def process_pending_deposits(%BeaconState{} = state) do + available_for_processing = + state.deposit_balance_to_consume + Accessors.get_activation_exit_churn_limit(state) + + finalized_slot = Misc.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) + + {state, churn_limit_reached, processed_amount, deposits_to_postpone, last_processed_index} = + state.pending_deposits + |> Enum.with_index() + |> Enum.reduce_while({state, false, 0, [], 0}, fn {deposit, index}, + {state, churn_limit_reached, + processed_amount, deposits_to_postpone, + _last_processed_index} -> + cond do + # Do not process deposit requests if Eth1 bridge deposits are not yet applied. + deposit.slot > Constants.genesis_slot() && + state.eth1_deposit_index < state.deposit_requests_start_index -> + {:halt, + {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}} + + # Check if deposit has been finalized, otherwise, stop processing. + deposit.slot > finalized_slot -> + {:halt, + {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}} + + # Check if number of processed deposits has not reached the limit, otherwise, stop processing. + index >= ChainSpec.get("MAX_PENDING_DEPOSITS_PER_EPOCH") -> + {:halt, + {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}} + + true -> + handle_pending_deposit( + deposit, + state, + churn_limit_reached, + processed_amount, + deposits_to_postpone, + index, + available_for_processing + ) + end + end) + + deposit_balance_to_consume = + if churn_limit_reached do + available_for_processing - processed_amount + else + 0 + end + + {:ok, + %BeaconState{ + state + | pending_deposits: + Enum.drop(state.pending_deposits, last_processed_index + 1) + |> Enum.concat(deposits_to_postpone), + deposit_balance_to_consume: deposit_balance_to_consume + }} + end + + defp handle_pending_deposit( + deposit, + state, + churn_limit_reached, + processed_amount, + deposits_to_postpone, + index, + available_for_processing + ) do + far_future_epoch = Constants.far_future_epoch() + next_epoch = Accessors.get_current_epoch(state) + + {is_validator_exited, is_validator_withdrawn} = + case Enum.find(state.validators, fn v -> v.pubkey == deposit.pubkey end) do + %Validator{} = validator -> + {validator.exit_epoch < far_future_epoch, validator.withdrawable_epoch < next_epoch} + + _ -> + {false, false} + end + + cond do + # Deposited balance will never become active. Increase balance but do not consume churn + is_validator_withdrawn -> + {:ok, state} = apply_pending_deposit(state, deposit) + + {:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}} + + # Validator is exiting, postpone the deposit until after withdrawable epoch + is_validator_exited -> + deposits_to_postpone = Enum.concat(deposits_to_postpone, [deposit]) + + {:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}} + + true -> + # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = + processed_amount + deposit.amount > available_for_processing + + if is_churn_limit_reached do + {:halt, {state, true, processed_amount, deposits_to_postpone, index - 1}} + else + # Consume churn and apply deposit. + processed_amount = processed_amount + deposit.amount + {:ok, state} = apply_pending_deposit(state, deposit) + {:cont, {state, false, processed_amount, deposits_to_postpone, index}} + end + end + end + + @spec process_pending_consolidations(BeaconState.t()) :: {:ok, BeaconState.t()} + def process_pending_consolidations(%BeaconState{} = state) do + # TODO: Not implemented yet + {:ok, state} + end + + defp apply_pending_deposit(state, deposit) do + index = + Enum.find_index(state.validators, fn validator -> validator.pubkey == deposit.pubkey end) + + current_validator? = is_number(index) + + valid_signature? = + current_validator? || + DepositMessage.valid_deposit_signature?( + deposit.pubkey, + deposit.withdrawal_credentials, + deposit.amount, + deposit.signature + ) + + cond do + current_validator? -> + {:ok, BeaconState.increase_balance(state, index, deposit.amount)} + + !current_validator? && valid_signature? -> + Mutators.add_validator_to_registry( + state, + deposit.pubkey, + deposit.withdrawal_credentials, + deposit.amount + ) + + true -> + # Neither a validator nor have a valid signature, we do not apply the deposit + {:ok, state} + end + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/mutators.ex b/lib/lambda_ethereum_consensus/state_transition/mutators.ex index 53a839996..3112b58cd 100644 --- a/lib/lambda_ethereum_consensus/state_transition/mutators.ex +++ b/lib/lambda_ethereum_consensus/state_transition/mutators.ex @@ -5,6 +5,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do alias LambdaEthereumConsensus.StateTransition.Accessors alias LambdaEthereumConsensus.StateTransition.Misc alias Types.BeaconState + alias Types.DepositMessage + alias Types.PendingDeposit alias Types.Validator @doc """ @@ -122,31 +124,49 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do Types.uint64(), Types.bls_signature() ) :: {:ok, BeaconState.t()} | {:error, String.t()} - def apply_deposit(state, pubkey, withdrawal_credentials, amount, signature) do - case Enum.find_index(state.validators, fn validator -> validator.pubkey == pubkey end) do - index when is_number(index) -> - {:ok, BeaconState.increase_balance(state, index, amount)} - - _ -> - deposit_message = %Types.DepositMessage{ - pubkey: pubkey, - withdrawal_credentials: withdrawal_credentials, - amount: amount - } - - domain = Misc.compute_domain(Constants.domain_deposit()) + def apply_deposit(state, pubkey, withdrawal_credentials, amount, sig) do + current_validator? = Enum.any?(state.validators, &(&1.pubkey == pubkey)) - signing_root = Misc.compute_signing_root(deposit_message, domain) + valid_signature? = + current_validator? || + DepositMessage.valid_deposit_signature?(pubkey, withdrawal_credentials, amount, sig) - if Bls.valid?(pubkey, signing_root, signature) do - apply_initial_deposit(state, pubkey, withdrawal_credentials, amount) + if !current_validator? && !valid_signature? do + # Neither a validator nor have a valid signature, we do not apply the deposit + {:ok, state} + else + updated_state = + if current_validator? do + state else - {:ok, state} + {:ok, state} = add_validator_to_registry(state, pubkey, withdrawal_credentials, 0) + state end + + deposit = %PendingDeposit{ + pubkey: pubkey, + withdrawal_credentials: withdrawal_credentials, + amount: amount, + signature: sig, + slot: Constants.genesis_slot() + } + + {:ok, + %BeaconState{ + updated_state + | pending_deposits: updated_state.pending_deposits ++ [deposit] + }} end end - defp apply_initial_deposit(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do + @spec add_validator_to_registry( + BeaconState.t(), + Types.bls_pubkey(), + Types.bytes32(), + Types.gwei() + ) :: + {:ok, BeaconState.t()} + def add_validator_to_registry(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do Types.Deposit.get_validator_from_deposit(pubkey, withdrawal_credentials, amount) |> then(&Aja.Vector.append(state.validators, &1)) |> then( diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index 5022663f6..c8f513631 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -13,17 +13,21 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do alias LambdaEthereumConsensus.Utils.BitList alias LambdaEthereumConsensus.Utils.BitVector alias LambdaEthereumConsensus.Utils.Randao + alias Types.PendingDeposit alias Types.Attestation alias Types.BeaconBlock alias Types.BeaconBlockBody alias Types.BeaconBlockHeader alias Types.BeaconState + alias Types.ConsolidationRequest + alias Types.DepositRequest alias Types.ExecutionPayload alias Types.ExecutionPayloadHeader alias Types.SyncAggregate alias Types.Validator alias Types.Withdrawal + alias Types.WithdrawalRequest @spec process_block_header(BeaconState.t(), BeaconBlock.t()) :: {:ok, BeaconState.t()} | {:error, String.t()} @@ -918,6 +922,43 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end end + @spec process_deposit_request(BeaconState.t(), DepositRequest.t()) :: {:ok, BeaconState.t()} + def process_deposit_request(state, deposit_request) do + start_index = + if state.deposit_requests_start_index == Constants.unset_deposit_requests_start_index(), + do: deposit_request.index, + else: state.deposit_requests_start_index + + deposit = %PendingDeposit{ + pubkey: deposit_request.pubkey, + withdrawal_credentials: deposit_request.withdrawal_credentials, + amount: deposit_request.amount, + signature: deposit_request.signature, + slot: state.slot + } + + {:ok, + %BeaconState{ + state + | deposit_requests_start_index: start_index, + pending_deposits: state.pending_deposits ++ [deposit] + }} + end + + @spec process_withdrawal_request(BeaconState.t(), WithdrawalRequest.t()) :: + {:ok, BeaconState.t()} + def process_withdrawal_request(state, _withdrawal_request) do + # TODO: Not implemented yet + {:ok, state} + end + + @spec process_consolidation_request(BeaconState.t(), ConsolidationRequest.t()) :: + {:ok, BeaconState.t()} + def process_consolidation_request(state, _consolidation_request) do + # TODO: Not implemented yet + {:ok, state} + end + @spec process_operations(BeaconState.t(), BeaconBlockBody.t()) :: {:ok, BeaconState.t()} | {:error, String.t()} def process_operations(state, body) do @@ -934,6 +975,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do body.bls_to_execution_changes, &process_bls_to_execution_change/2 ) + |> for_ops(:deposit_request, body.execution_requests.deposits, &process_deposit_request/2) + |> for_ops( + :withdrawal_request, + body.execution_requests.withdrawals, + &process_withdrawal_request/2 + ) + |> for_ops( + :consolidation_request, + body.execution_requests.consolidations, + &process_consolidation_request/2 + ) end end @@ -954,13 +1006,22 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do @spec verify_deposits(BeaconState.t(), BeaconBlockBody.t()) :: :ok | {:error, String.t()} defp verify_deposits(state, body) do - deposit_count = state.eth1_data.deposit_count - state.eth1_deposit_index - deposit_limit = min(ChainSpec.get("MAX_DEPOSITS"), deposit_count) + eth1_deposit_index_limit = + min(state.eth1_data.deposit_count, state.deposit_requests_start_index) - if length(body.deposits) == deposit_limit do - :ok - else - {:error, "deposits length mismatch"} + max_deposits = ChainSpec.get("MAX_DEPOSITS") + + cond do + state.eth1_deposit_index < eth1_deposit_index_limit and + length(body.deposits) == + min(max_deposits, eth1_deposit_index_limit - state.eth1_deposit_index) -> + :ok + + state.eth1_deposit_index >= eth1_deposit_index_limit and Enum.empty?(body.deposits) -> + :ok + + true -> + {:error, "Invalid deposits"} end end end diff --git a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex index c20fdf891..9217e15bc 100644 --- a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex +++ b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex @@ -156,6 +156,8 @@ defmodule LambdaEthereumConsensus.StateTransition do |> epoch_op(:registry_updates, &EpochProcessing.process_registry_updates/1) |> epoch_op(:slashings, &EpochProcessing.process_slashings/1) |> epoch_op(:eth1_data_reset, &EpochProcessing.process_eth1_data_reset/1) + |> epoch_op(:pending_deposits, &EpochProcessing.process_pending_deposits/1) + |> epoch_op(:pending_consolidations, &EpochProcessing.process_pending_consolidations/1) |> epoch_op(:effective_balance_updates, &EpochProcessing.process_effective_balance_updates/1) |> epoch_op(:slashings_reset, &EpochProcessing.process_slashings_reset/1) |> epoch_op(:randao_mixes_reset, &EpochProcessing.process_randao_mixes_reset/1) diff --git a/lib/types/beacon_chain/deposit.ex b/lib/types/beacon_chain/deposit.ex index 22808ac15..e7842095e 100644 --- a/lib/types/beacon_chain/deposit.ex +++ b/lib/types/beacon_chain/deposit.ex @@ -22,24 +22,29 @@ defmodule Types.Deposit do @spec get_validator_from_deposit(Types.bls_pubkey(), Types.bytes32(), Types.uint64()) :: Types.Validator.t() def get_validator_from_deposit(pubkey, withdrawal_credentials, amount) do - effective_balance = - min( - amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")), - ChainSpec.get("MAX_EFFECTIVE_BALANCE") - ) - far_future_epoch = Constants.far_future_epoch() - %Types.Validator{ + validator = %Types.Validator{ pubkey: pubkey, withdrawal_credentials: withdrawal_credentials, activation_eligibility_epoch: far_future_epoch, activation_epoch: far_future_epoch, exit_epoch: far_future_epoch, withdrawable_epoch: far_future_epoch, - effective_balance: effective_balance, + effective_balance: 0, slashed: false } + + effective_balance = + min( + amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")), + Types.Validator.get_max_effective_balance(validator) + ) + + %Types.Validator{ + validator + | effective_balance: effective_balance + } end @impl LambdaEthereumConsensus.Container diff --git a/lib/types/beacon_chain/deposit_message.ex b/lib/types/beacon_chain/deposit_message.ex index 3ec9b904d..74bbd621d 100644 --- a/lib/types/beacon_chain/deposit_message.ex +++ b/lib/types/beacon_chain/deposit_message.ex @@ -3,6 +3,7 @@ defmodule Types.DepositMessage do Struct definition for `DepositMessage`. Related definitions in `native/ssz_nif/src/types/`. """ + alias LambdaEthereumConsensus.StateTransition.Misc use LambdaEthereumConsensus.Container fields = [ @@ -28,4 +29,23 @@ defmodule Types.DepositMessage do {:amount, TypeAliases.gwei()} ] end + + @spec valid_deposit_signature?( + Types.bls_pubkey(), + Types.bytes32(), + Types.gwei(), + Types.bls_signature() + ) :: boolean() + def valid_deposit_signature?(pubkey, withdrawal_credentials, amount, signature) do + deposit_message = %__MODULE__{ + pubkey: pubkey, + withdrawal_credentials: withdrawal_credentials, + amount: amount + } + + domain = Misc.compute_domain(Constants.domain_deposit()) + signing_root = Misc.compute_signing_root(deposit_message, domain) + + Bls.valid?(pubkey, signing_root, signature) + end end diff --git a/test/spec/runners/operations.ex b/test/spec/runners/operations.ex index a30290ad6..12aedc666 100644 --- a/test/spec/runners/operations.ex +++ b/test/spec/runners/operations.ex @@ -5,6 +5,9 @@ defmodule OperationsTestRunner do alias LambdaEthereumConsensus.StateTransition.Operations alias LambdaEthereumConsensus.Utils.Diff + alias Types.ConsolidationRequest + alias Types.DepositRequest + alias Types.WithdrawalRequest alias Types.Attestation alias Types.AttesterSlashing @@ -32,7 +35,10 @@ defmodule OperationsTestRunner do "sync_aggregate" => SyncAggregate, "execution_payload" => BeaconBlockBody, "withdrawals" => ExecutionPayload, - "bls_to_execution_change" => SignedBLSToExecutionChange + "bls_to_execution_change" => SignedBLSToExecutionChange, + "consolidation_request" => ConsolidationRequest, + "withdrawal_request" => WithdrawalRequest, + "deposit_request" => DepositRequest # "deposit_receipt" => "DepositReceipt" Not yet implemented } @@ -48,7 +54,10 @@ defmodule OperationsTestRunner do "sync_aggregate" => "sync_aggregate", "execution_payload" => "body", "withdrawals" => "execution_payload", - "bls_to_execution_change" => "address_change" + "bls_to_execution_change" => "address_change", + "consolidation_request" => "consolidation_request", + "withdrawal_request" => "withdrawal_request", + "deposit_request" => "deposit_request" # "deposit_receipt" => "deposit_receipt" Not yet implemented } From 08b8365648e512493344a1cd991e35892059c5ad Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Tue, 22 Apr 2025 17:54:46 -0300 Subject: [PATCH 17/23] docs: `electra-gap.md` enhancement with roadmap and current status (#1442) --- electra-gap.md | 90 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index 58696950d..6f1ee044b 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -1,13 +1,47 @@ -# Implementation Gaps for Electra Upgrade +# Implementation Gaps for electra Upgrade -This document outlines the gaps in the current implementation of the Electra. It's still a WIP. +This document will guide you through our step-by-step plan for the implementation of the new electra fork. We’ve broken the work into three clear phases to make our goals and priorities easy to follow. This is a living document, so we will update it as we progress through the implementation. -## Difference Between Updated and Modified +## Roadmap -- **Updated**: Changes in validation rules, protocols, or external behaviors. These changes may not directly alter the logic of the implementation. -- **Modified**: Refers to direct changes made to the code or logic of an existing function, container, or process to accommodate new requirements or features. +| Status | Phase | What & Why | Key Steps | Testing | +|:----:|:------------------------------------------|:------------------------------------------------------|:---------------------------------------------------------------------------|:---------------------------------------------------| +| 🏗️ | [Phase 1: Beacon Chain Implementation](#phase-1-beacon-chain-implementation) | Build the electra-upgraded beacon chain core | • Apply electra-specific changes
• Run & pass full spec tests | Run spec suite (`make spec-test`), aim for 0 failures | +| ⌛ | [Phase 2: P2P & Sepolia Long-Running Sessions](#phase-2-p2p--sepolia-long-running-sessions) | Ensure stability on Sepolia | • Implement the P2P changes
• Deploy the node on our server pointing to Sepolia
• Fix every issue we found that interrupts the node execution | Continuous uptime checks & up-to-date block processing for 72+ hrs in Sepolia| +| ⌛ | [Phase 3: Validator Upgrades](#phase-3-validator-upgrades) | Ensure validators duties on devnets |• Implement the honest validator changes
• Make assertoor work
• Test via Kurtosis & Assertoor | Execute Kurtosis scenarios & Assertoor with continuous uptime checks and up-to-date validation duties for 72+ hrs on kurtosis | -## Containers +### Why this Order + +We kick off with the beacon chain implementation because passing the full spec test suite is critical for protocol correctness and a solid foundation. Once all tests are green, we move to Phase 2 for prolonged Sepolia sessions and the implementation of the p2p interface changes, ensuring real-world testnet stability before mainnet moves to electra which would limit our network options before we upgrade. This will allow us to continue running long session on our servers and monitor the node execution given that just the block/epoch processing and state transitions are needed for this. With a stable node confirmed, Phase 3 begins, upgrading the validator logic, tested through Kurtosis and Assertoor to finalize the electra upgrade roadmap. + +### Next Steps + +Once we finish the whole electra upgrade we have a clear path to follow for the next steps: +- **Hooli long running sessions:** Right now Holesky was not an option for us because of performance issues, we need to test on Hooli and see if we can run the node on it on acceptable performance. This effort will be in parallel to the performance optimization one. +- **Performance optimization:** We need to run the node on Hooli and mainnet to identify and fix the current bottlenecks, specially on block and epoch processing. +- **Electra code enhancements:** During the implementation, some complex functions were identified that could be simplified. They are mostly related to how to manage early returns in already large python reference functions and port the logic to elixir. We will work on those to improve the code quality and make it easier to maintain in the future. + +## Current Status + +Right now we are at the [Beacon Chain Implementation](#phase-1-beacon-chain-implementation) phase, our current spec test results for the past weeks are: + +- **April 15th, 2025:** `11370 tests, 2003 failures, 784 skipped` +- **April 22th, 2025:** `11370 tests, 165 failures, 784 skipped` + +**Note:** The aim is to reach `0` failures before next week, so we can start the long running sessions on Sepolia. Also, we want to validate the 784 test skipped on the second phase. + +## Implementation Gap + +Here we will detail the current implementation gaps with the specs and the way to test every phase. The initial one is the larger to implement but it has the most clear way to validate it (spec-tests). The next phases are smaller but they need to be validated on long running sessions both in devnets and testnets. + +### Phase 1: Beacon Chain Implementation + +We are at `40/57` (70%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `165/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. + + +The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows: + +#### Containers (13/13 - 100%) - [x] New `PendingDeposit` ([Spec](docs/specs/electra/beacon-chain.md#pendingdeposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) - [x] New `PendingPartialWithdrawal` ([Spec](docs/specs/electra/beacon-chain.md#pendingpartialwithdrawal), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) @@ -23,7 +57,7 @@ This document outlines the gaps in the current implementation of the Electra. It - [x] Modified `IndexedAttestation` ([Spec](docs/specs/electra/beacon-chain.md#indexedattestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) - [x] Modified `BeaconState` ([Spec](docs/specs/electra/beacon-chain.md#beaconstate), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400)) -## Predicates +#### Predicates (6/6 - 100%) - [x] Modified `is_eligible_for_activation_queue` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_eligible_for_activation_queue), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) - [x] New `is_compounding_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-is_compounding_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) @@ -33,7 +67,7 @@ This document outlines the gaps in the current implementation of the Electra. It - [x] Modified `is_partially_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) -## Beacon State Accessors +#### Beacon State Accessors (4/6 - 67%) - [x] Modified `get_attesting_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_attesting_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) - [x] Modified `get_next_sync_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) @@ -42,7 +76,7 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit)) - [ ] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw)) -## Beacon State Mutators +#### Beacon State Mutators (3/6 - 50%) - [x] Modified `initiate_validator_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-initiate_validator_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) - [ ] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator)) @@ -51,13 +85,13 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] New `compute_consolidation_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_consolidation_epoch_and_update_churn)) - [x] Modified `slash_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-slash_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) -## Miscellaneous +#### Miscellaneous (3/3 - 100%) - [x] New `get_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#new-get_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) - [x] Modified `compute_proposer_index` ([Spec](docs/specs/electra/beacon-chain.md#modified-compute_proposer_index), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) - [x] New `get_max_effective_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-get_max_effective_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) -## Epoch Processing +#### Epoch Processing (5/8 - 63%) - [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch)) - [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) @@ -68,7 +102,7 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates)) - [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -## Block Processing +#### Block Processing (6/12 - 50%) - [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) - [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) @@ -82,13 +116,24 @@ This document outlines the gaps in the current implementation of the Electra. It - [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -## Execution Engine + +#### Execution Engine (0/3 - 0%) - [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash)) - [ ] Modified `notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-notify_new_payload)) - [ ] Modified `verify_and_notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-verify_and_notify_new_payload)) -## Networking +## Phase 2: P2P & Sepolia Long-Running Sessions + +We didn't start this phase yet, its goals are: + +- [P2P electra changes](docs/specs/electra/p2p-interface.md) implementated. +- To have a stable node processing state transitions in testnets, especially Sepolia. +- Validate the remaining 784 skipped tests to make sure they are not masking any issues. + +The aim is to have the node running on Sepolia uninterrupted for 72+ hrs. The following is the implementation gap for this phase: + +### Networking (0/8 - 0% Complete) - [ ] Updated `beacon_block` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_block)) - [ ] Updated `beacon_aggregate_and_proof` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_aggregate_and_proof)) @@ -99,7 +144,16 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] Updated `BlobSidecarsByRange v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyrange-v1)) - [ ] Updated `BlobSidecarsByRoot v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyroot-v1)) -## Honest Validator +## Phase 3: Validator Upgrades + +We didn't start this phase yet, its goals are: +- [Honest validator changes](docs/specs/electra/validator.md) implemented. +- To have a stable node processing validator duties in a kurtosis devnet. +- Have assertoor working with automatic test for the validator duties. + +The aim is to have the node running on kurtosis uninterrupted for 72+ hrs. The following is the implementation gap for this phase: + +### Honest Validator (0/9 - 0% Complete) - [ ] Modified `GetPayloadResponse` ([Spec](docs/specs/electra/validator.md#modified-getpayloadresponse)) - [ ] Modified `AggregateAndProof` ([Spec](docs/specs/electra/validator.md#aggregateandproof)) @@ -111,7 +165,7 @@ This document outlines the gaps in the current implementation of the Electra. It - [ ] Updated `construct attestation` ([Spec](docs/specs/electra/validator.md#construct-attestation)) - [ ] Updated `construct aggregate` ([Spec](docs/specs/electra/validator.md#construct-aggregate)) -## Fork Logic -- [ ] Modified `compute_fork_version` ([Spec](docs/specs/electra/fork.md#modified-compute_fork_version)) -- [ ] New `upgrade_to_electra` ([Spec](docs/specs/electra/fork.md#upgrade_to_electra)) +## Changelog +- **April 10th, 2025:** Created the document with the implementation gap. +- **April 22th, 2025:** Updated the document with a clear roadmap, next steps and detailed current status. \ No newline at end of file From e5ef0028ad671370376f18dda5fcd61caf1c2177 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:16:26 -0300 Subject: [PATCH 18/23] feat: electra process_operations changes (#1426) Co-authored-by: Rodrigo Oliveri --- electra-gap.md | 18 +- lib/constants.ex | 3 + .../state_transition/accessors.ex | 21 +- .../state_transition/mutators.ex | 91 +++++ .../state_transition/operations.ex | 370 ++++++++++++++++-- 5 files changed, 466 insertions(+), 37 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index 6f1ee044b..fde12ff1d 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -73,16 +73,16 @@ The current status of the implementation in the [electra-support](https://github - [x] Modified `get_next_sync_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) - [x] New `get_balance_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_balance_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) - [x] New `get_activation_exit_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_activation_exit_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) -- [ ] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit)) -- [ ] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw)) +- [x] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) +- [x] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) #### Beacon State Mutators (3/6 - 50%) - [x] Modified `initiate_validator_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-initiate_validator_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) -- [ ] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator)) -- [ ] New `queue_excess_active_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-queue_excess_active_balance)) +- [x] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) +- [x] New `queue_excess_active_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-queue_excess_active_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] New `compute_exit_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_exit_epoch_and_update_churn), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) -- [ ] New `compute_consolidation_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_consolidation_epoch_and_update_churn)) +- [x] New `compute_consolidation_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_consolidation_epoch_and_update_churn), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] Modified `slash_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-slash_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) #### Miscellaneous (3/3 - 100%) @@ -107,12 +107,12 @@ The current status of the implementation in the [electra-support](https://github - [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) - [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) - [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -- [ ] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation)) +- [x] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -- [ ] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit)) -- [ ] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request)) +- [x] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) +- [x] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -- [ ] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request)) +- [x] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) diff --git a/lib/constants.ex b/lib/constants.ex index a072d60de..03a6039d5 100644 --- a/lib/constants.ex +++ b/lib/constants.ex @@ -151,4 +151,7 @@ defmodule Constants do @spec consolidation_request_type() :: Types.bytes1() def consolidation_request_type(), do: <<2>> + + @spec g2_point_at_infinity() :: Types.bls_signature() + def g2_point_at_infinity(), do: <<0xC0, 0::8*95>> end diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index d35709fec..a6d0a6dcd 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -653,7 +653,11 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do @spec get_committee_indices(Types.bitvector()) :: Enumerable.t(Types.commitee_index()) def get_committee_indices(committee_bits) do - bitlist = committee_bits |> :binary.bin_to_list() |> Enum.reverse() + bitlist = + for <> do + bit + end + |> Enum.reverse() for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index end @@ -682,4 +686,19 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do get_balance_churn_limit(state) ) end + + @spec get_pending_balance_to_withdraw(BeaconState.t(), Types.validator_index()) :: Types.gwei() + def get_pending_balance_to_withdraw(state, validator_index) do + for( + withdrawal <- state.pending_partial_withdrawals, + withdrawal.validator_index == validator_index, + do: withdrawal.amount + ) + |> Enum.sum() + end + + @spec get_consolidation_churn_limit(BeaconState.t()) :: Types.gwei() + def get_consolidation_churn_limit(state) do + get_balance_churn_limit(state) - get_activation_exit_churn_limit(state) + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/mutators.ex b/lib/lambda_ethereum_consensus/state_transition/mutators.ex index 3112b58cd..60d26f8e0 100644 --- a/lib/lambda_ethereum_consensus/state_transition/mutators.ex +++ b/lib/lambda_ethereum_consensus/state_transition/mutators.ex @@ -218,4 +218,95 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do earliest_exit_epoch: earliest_exit_epoch } end + + @spec compute_consolidation_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) :: + Types.BeaconState.t() + def compute_consolidation_epoch_and_update_churn(state, consolidation_balance) do + current_epoch = Accessors.get_current_epoch(state) + + earliest_consolidation_epoch = + max(state.earliest_consolidation_epoch, Misc.compute_activation_exit_epoch(current_epoch)) + + per_epoch_consolidation_churn = Accessors.get_consolidation_churn_limit(state) + + consolidation_balance_to_consume = + if state.earliest_consolidation_epoch < earliest_consolidation_epoch do + per_epoch_consolidation_churn + else + state.consolidation_balance_to_consume + end + + {earliest_consolidation_epoch, consolidation_balance_to_consume} = + if consolidation_balance > consolidation_balance_to_consume do + balance_to_process = consolidation_balance - consolidation_balance_to_consume + additional_epochs = div(balance_to_process - 1, per_epoch_consolidation_churn) + 1 + + { + earliest_consolidation_epoch + additional_epochs, + consolidation_balance_to_consume + additional_epochs * per_epoch_consolidation_churn + } + else + {earliest_consolidation_epoch, consolidation_balance_to_consume} + end + + %BeaconState{ + state + | consolidation_balance_to_consume: + consolidation_balance_to_consume - consolidation_balance, + earliest_consolidation_epoch: earliest_consolidation_epoch + } + end + + @spec switch_to_compounding_validator(BeaconState.t(), Types.validator_index()) :: + BeaconState.t() + def switch_to_compounding_validator(state, index) do + validator = Aja.Enum.at(state.validators, index) + <<_first_byte::binary-size(1), rest::binary>> = validator.withdrawal_credentials + + withdrawal_credentials = + Constants.compounding_withdrawal_prefix() <> rest + + updated_validator = %Validator{ + validator + | withdrawal_credentials: withdrawal_credentials + } + + state = %BeaconState{ + state + | validators: Aja.Vector.replace_at(state.validators, index, updated_validator) + } + + queue_excess_active_balance(state, index) + end + + @spec queue_excess_active_balance(BeaconState.t(), Types.validator_index()) :: + BeaconState.t() + def queue_excess_active_balance(state, index) do + min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE") + balance = Aja.Vector.at(state.balances, index) + + if balance > min_activation_balance do + excess_balance = balance - min_activation_balance + validator = Aja.Vector.at(state.validators, index) + + updated_balances = Aja.Vector.replace_at(state.balances, index, min_activation_balance) + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + pending_deposit = %PendingDeposit{ + pubkey: validator.pubkey, + withdrawal_credentials: validator.withdrawal_credentials, + amount: excess_balance, + signature: Constants.g2_point_at_infinity(), + slot: Constants.genesis_slot() + } + + %BeaconState{ + state + | balances: updated_balances, + pending_deposits: state.pending_deposits ++ [pending_deposit] + } + else + state + end + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index c8f513631..924dec218 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -13,7 +13,9 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do alias LambdaEthereumConsensus.Utils.BitList alias LambdaEthereumConsensus.Utils.BitVector alias LambdaEthereumConsensus.Utils.Randao + alias Types.PendingConsolidation alias Types.PendingDeposit + alias Types.PendingPartialWithdrawal alias Types.Attestation alias Types.BeaconBlock @@ -565,6 +567,9 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do current_epoch < validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") -> {:error, "validator cannot exit yet"} + Accessors.get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) != 0 -> + {:error, "validator has pending withdrawals in the queue"} + not (Misc.compute_domain( Constants.domain_voluntary_exit(), fork_version: ChainSpec.get("CAPELLA_FORK_VERSION"), @@ -592,16 +597,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end @spec validate_attestation(BeaconState.t(), Attestation.t()) :: :ok | {:error, String.t()} - def validate_attestation(state, %Attestation{data: data} = attestation) do + def validate_attestation( + state, + %Attestation{data: data, aggregation_bits: aggregation_bits} = attestation + ) do with :ok <- check_valid_target_epoch(data, state), :ok <- check_epoch_matches(data), :ok <- check_valid_slot_range(data, state), - :ok <- check_committee_count(data, state), - {:ok, beacon_committee} <- Accessors.get_beacon_committee(state, data.slot, data.index), - :ok <- check_matching_aggregation_bits_length(attestation, beacon_committee) do - beacon_committee - |> Accessors.get_committee_indexed_attestation(attestation) - |> then(&check_valid_indexed_attestation(state, &1)) + :ok <- check_data_index_zero(data), + {:ok, committee_offset} <- check_committee_indices(attestation, state), + :ok <- check_matching_aggregation_bits_length(aggregation_bits, committee_offset), + {:ok, indexed_attestation} <- Accessors.get_indexed_attestation(state, attestation) do + check_valid_indexed_attestation(state, indexed_attestation) end end @@ -699,7 +706,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do def fast_process_attestation( state, - %Attestation{data: data, aggregation_bits: aggregation_bits} = att, + %Attestation{data: data} = att, previous_epoch_updates, current_epoch_updates, attestation_index @@ -708,10 +715,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do slot = state.slot - data.slot, {:ok, flag_indices} <- Accessors.get_attestation_participation_flag_indices(state, data, slot), - {:ok, committee} <- Accessors.get_beacon_committee(state, data.slot, data.index) do - attesting_indices = - Accessors.get_committee_attesting_indices(committee, aggregation_bits) - + {:ok, attesting_indices} <- Accessors.get_attesting_indices(state, att) do is_current_epoch = data.target.epoch == Accessors.get_current_epoch(state) epoch_updates = if is_current_epoch, do: current_epoch_updates, else: previous_epoch_updates @@ -726,6 +730,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do new_epoch_updates = attesting_indices + |> Enum.to_list() |> Enum.reduce(epoch_updates, fn i, epoch_updates -> Map.update(epoch_updates, i, [v], &merge_masks(&1, v)) end) @@ -842,23 +847,22 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end end - defp check_committee_count(data, state) do - if data.index >= Accessors.get_committee_count_per_slot(state, data.target.epoch) do - {:error, "Index exceeds committee count"} + defp check_committee_count(comittee_index, data, state) do + if comittee_index >= Accessors.get_committee_count_per_slot(state, data.target.epoch) do + {:error, "Comitee index exceeds committee count"} else :ok end end - defp check_matching_aggregation_bits_length(attestation, beacon_committee) do - aggregation_bits_length = BitList.length(attestation.aggregation_bits) - beacon_committee_length = length(beacon_committee) + defp check_matching_aggregation_bits_length(aggregation_bits, committe_offset) do + aggregation_bits_length = BitList.length(aggregation_bits) - if aggregation_bits_length == beacon_committee_length do + if aggregation_bits_length == committe_offset do :ok else {:error, - "Mismatched length. aggregation_bits: #{aggregation_bits_length}. beacon_committee: #{beacon_committee_length}"} + "Mismatched length. aggregation_bits: #{aggregation_bits_length}. committee_offset: #{committe_offset}"} end end @@ -870,6 +874,36 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end end + defp check_data_index_zero(%{index: 0}), do: :ok + defp check_data_index_zero(_data), do: {:error, "Data index should be zero"} + + defp check_committee_attesters_exists(committee, aggregation_bits, committee_offset) do + committee + |> Enum.with_index() + |> Enum.any?(&BitList.set?(aggregation_bits, elem(&1, 1) + committee_offset)) + |> case do + true -> :ok + false -> {:error, "No committee attesters exist"} + end + end + + defp check_committee_indices(attestation, state) do + %Attestation{data: data, aggregation_bits: aggregation_bits, committee_bits: committee_bits} = + attestation + + committee_bits + |> Accessors.get_committee_indices() + |> Enum.reduce_while({:ok, 0}, fn committee_index, {:ok, committee_offset} -> + with :ok <- check_committee_count(committee_index, data, state), + {:ok, committee} <- Accessors.get_beacon_committee(state, data.slot, committee_index), + :ok <- check_committee_attesters_exists(committee, aggregation_bits, committee_offset) do + {:cont, {:ok, committee_offset + length(committee)}} + else + error -> {:halt, error} + end + end) + end + def process_bls_to_execution_change(state, signed_address_change) do address_change = signed_address_change.message @@ -946,17 +980,299 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end @spec process_withdrawal_request(BeaconState.t(), WithdrawalRequest.t()) :: - {:ok, BeaconState.t()} - def process_withdrawal_request(state, _withdrawal_request) do - # TODO: Not implemented yet - {:ok, state} + {:ok, BeaconState.t()} | {:error, String.t()} + def process_withdrawal_request(state, withdrawal_request) do + amount = withdrawal_request.amount + is_full_exit_request = amount == Constants.full_exit_request_amount() + request_pubkey = withdrawal_request.validator_pubkey + current_epoch = Accessors.get_current_epoch(state) + far_future_epoch = Constants.far_future_epoch() + + with false <- partial_withdrawal_on_full_queue?(state, is_full_exit_request), + {validator, validator_index} <- find_validator(state, request_pubkey), + true <- + not invalid_withdrawal_credentials?(validator, withdrawal_request.source_address), + true <- Predicates.active_validator?(validator, current_epoch), + true <- validator.exit_epoch == far_future_epoch, + true <- + current_epoch >= validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") do + pending_balance_to_withdraw = + Accessors.get_pending_balance_to_withdraw(state, validator_index) + + withdrawal_request_type = + cond do + is_full_exit_request and pending_balance_to_withdraw == 0 -> :full_exit + is_full_exit_request -> :full_exit_with_pending_balance + true -> :partial_exit + end + + handle_valid_withdrawal_request( + state, + validator, + validator_index, + amount, + pending_balance_to_withdraw, + withdrawal_request_type + ) + else + _ -> + {:ok, state} + end + end + + defp partial_withdrawal_on_full_queue?(state, is_full_exit_request) do + length(state.pending_partial_withdrawals) == + ChainSpec.get("PENDING_PARTIAL_WITHDRAWALS_LIMIT") && !is_full_exit_request + end + + defp invalid_withdrawal_credentials?(validator, address) do + has_correct_credential = Validator.has_execution_withdrawal_credential(validator) + + is_correct_source_address = + case validator.withdrawal_credentials do + <<_::binary-size(12), rest>> -> rest == address + _ -> false + end + + !(has_correct_credential && is_correct_source_address) + end + + @spec find_validator(Types.BeaconState.t(), Types.bls_pubkey()) :: + {Types.Validator.t(), non_neg_integer()} | nil + defp find_validator(state, request_pubkey) do + state.validators + |> Aja.Enum.find_index(fn validator -> validator.pubkey == request_pubkey end) + |> then(fn + nil -> nil + index -> {Aja.Vector.at(state.validators, index), index} + end) + end + + defp handle_valid_withdrawal_request(state, _, validator_index, _, _, :full_exit) do + with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do + {:ok, + %Types.BeaconState{ + state + | validators: Aja.Vector.replace_at(state.validators, validator_index, validator) + }} + end + end + + defp handle_valid_withdrawal_request(state, _, _, _, _, :full_exit_with_pending_balance), + do: {:ok, state} + + defp handle_valid_withdrawal_request( + state, + validator, + validator_index, + amount, + pending_balance_to_withdraw, + :partial_exit + ) do + min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE") + + has_sufficient_effective_balance = + validator.effective_balance >= min_activation_balance + + has_excess_balance = + Aja.Vector.at(state.balances, validator_index) > + min_activation_balance + pending_balance_to_withdraw + + if Validator.has_compounding_withdrawal_credential(validator) && + has_sufficient_effective_balance && has_excess_balance do + to_withdraw = + min( + Aja.Vector.at(state.balances, validator_index) - min_activation_balance - + pending_balance_to_withdraw, + amount + ) + + state = Mutators.compute_exit_epoch_and_update_churn(state, to_withdraw) + exit_queue_epoch = state.earliest_exit_epoch + + withdrawable_epoch = + exit_queue_epoch + ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY") + + pending_partial_withdrawal = %PendingPartialWithdrawal{ + validator_index: validator_index, + amount: to_withdraw, + withdrawable_epoch: withdrawable_epoch + } + + {:ok, + %BeaconState{ + state + | pending_partial_withdrawals: + state.pending_partial_withdrawals ++ [pending_partial_withdrawal] + }} + else + {:ok, state} + end end @spec process_consolidation_request(BeaconState.t(), ConsolidationRequest.t()) :: {:ok, BeaconState.t()} - def process_consolidation_request(state, _consolidation_request) do - # TODO: Not implemented yet - {:ok, state} + def process_consolidation_request(state, consolidation_request) do + request_type = + if valid_switch_to_compounding_request?(state, consolidation_request), + do: :compounding, + else: :consolidation + + do_process_consolidation_request(state, consolidation_request, request_type) + end + + defp do_process_consolidation_request(state, consolidation_request, :compounding) do + case find_validator(state, consolidation_request.source_pubkey) do + {_validator, validator_index} -> + {:ok, Mutators.switch_to_compounding_validator(state, validator_index)} + + nil -> + {:ok, state} + end + end + + defp do_process_consolidation_request(state, consolidation_request, :consolidation) do + with :ok <- verify_consolidation_request(state, consolidation_request), + {source_validator, source_index} <- + find_validator(state, consolidation_request.source_pubkey), + {_target_validator, target_index} <- + find_validator(state, consolidation_request.target_pubkey), + :ok <- + verify_consolidation_validators( + state, + source_index, + target_index, + consolidation_request + ) do + state = + Mutators.compute_consolidation_epoch_and_update_churn( + state, + source_validator.effective_balance + ) + + consolidation_epoch = state.earliest_consolidation_epoch + + withdrawable_epoch = + consolidation_epoch + ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY") + + updated_source_validator = %Validator{ + source_validator + | exit_epoch: consolidation_epoch, + withdrawable_epoch: withdrawable_epoch + } + + pending_consolidation = %PendingConsolidation{ + source_index: source_index, + target_index: target_index + } + + updated_state = %BeaconState{ + state + | validators: + Aja.Vector.replace_at(state.validators, source_index, updated_source_validator), + pending_consolidations: state.pending_consolidations ++ [pending_consolidation] + } + + {:ok, updated_state} + else + _error -> {:ok, state} + end + end + + defp verify_consolidation_request(state, consolidation_request) do + cond do + consolidation_request.source_pubkey == consolidation_request.target_pubkey -> + {:error, :source_target_same} + + # If the pending consolidations queue is full, consolidation requests are ignored + length(state.pending_consolidations) >= ChainSpec.get("PENDING_CONSOLIDATIONS_LIMIT") -> + {:error, :queue_full} + + # If there is too little available consolidation churn limit, consolidation requests are ignored + Accessors.get_consolidation_churn_limit(state) <= ChainSpec.get("MIN_ACTIVATION_BALANCE") -> + {:error, :churn_limit_not_met} + + true -> + :ok + end + end + + defp verify_consolidation_validators(state, source_index, target_index, consolidation_request) do + source_validator = Aja.Vector.at(state.validators, source_index) + target_validator = Aja.Vector.at(state.validators, target_index) + current_epoch = Accessors.get_current_epoch(state) + far_future_epoch = Constants.far_future_epoch() + + cond do + invalid_consolidation_request_credentials?( + source_validator, + target_validator, + consolidation_request + ) -> + {:error, :invalid_credentials} + + # Verify the source and the target are active + !Predicates.active_validator?(source_validator, current_epoch) || + !Predicates.active_validator?(target_validator, current_epoch) -> + {:error, :validator_not_active} + + # Verify exits for source and target have not been initiated + source_validator.exit_epoch != far_future_epoch || + target_validator.exit_epoch != far_future_epoch -> + {:error, :validator_exiting} + + # Verify the source has been active long enough + current_epoch < + source_validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") -> + {:error, :validator_not_active_long_enough} + + # Verify the source has no pending withdrawals in the queue + Accessors.get_pending_balance_to_withdraw(state, source_index) > 0 -> + {:error, :validator_has_pending_balance} + + # Initiate source validator exit and append pending consolidation + true -> + :ok + end + end + + defp invalid_consolidation_request_credentials?( + source_validator, + target_validator, + consolidation_request + ) do + invalid_withdrawal_credentials?(source_validator, consolidation_request.source_address) || + not Validator.has_compounding_withdrawal_credential(target_validator) + end + + @spec valid_switch_to_compounding_request?(BeaconState.t(), ConsolidationRequest.t()) :: + boolean() + def valid_switch_to_compounding_request?(state, consolidation_request) do + current_epoch = Accessors.get_current_epoch(state) + far_future_epoch = Constants.far_future_epoch() + + # Verify pubkey exists + with {source_validator, _source_index} <- + find_validator(state, consolidation_request.source_pubkey), + # Switch to compounding requires source and target be equal + true <- consolidation_request.source_pubkey == consolidation_request.target_pubkey, + # Verify request has been authorized + true <- + not invalid_withdrawal_credentials?( + source_validator, + consolidation_request.source_address + ), + # Verify source withdrawal credentials + true <- Validator.has_eth1_withdrawal_credential(source_validator), + # Verify the source is active + true <- Predicates.active_validator?(source_validator, current_epoch), + # Verify exit for source has not been initiated + true <- source_validator.exit_epoch == far_future_epoch do + true + else + _ -> + false + end end @spec process_operations(BeaconState.t(), BeaconBlockBody.t()) :: From b65087b0ff14e0853c1071723df3e5a07fa37fc3 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 24 Apr 2025 16:30:31 -0300 Subject: [PATCH 19/23] updated electra-gap.md doc --- electra-gap.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index fde12ff1d..3dacfcf6b 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -36,7 +36,7 @@ Here we will detail the current implementation gaps with the specs and the way t ### Phase 1: Beacon Chain Implementation -We are at `40/57` (70%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `165/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. +We are at `49/57` (86%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `108/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows: @@ -67,7 +67,7 @@ The current status of the implementation in the [electra-support](https://github - [x] Modified `is_partially_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) -#### Beacon State Accessors (4/6 - 67%) +#### Beacon State Accessors (6/6 - 100%) - [x] Modified `get_attesting_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_attesting_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) - [x] Modified `get_next_sync_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) @@ -76,7 +76,7 @@ The current status of the implementation in the [electra-support](https://github - [x] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) - [x] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) -#### Beacon State Mutators (3/6 - 50%) +#### Beacon State Mutators (6/6 - 100%) - [x] Modified `initiate_validator_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-initiate_validator_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) - [x] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) @@ -102,7 +102,7 @@ The current status of the implementation in the [electra-support](https://github - [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates)) - [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -#### Block Processing (6/12 - 50%) +#### Block Processing (10/12 - 83%) - [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) - [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) From aae93fb332458e3a96f3b74e364194e0fdd62680 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:54:03 -0300 Subject: [PATCH 20/23] feat: electra consolidations (#1428) --- electra-gap.md | 10 ++-- .../state_transition/epoch_processing.ex | 47 +++++++++++++++++-- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index 3dacfcf6b..739f7ed8b 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -36,7 +36,7 @@ Here we will detail the current implementation gaps with the specs and the way t ### Phase 1: Beacon Chain Implementation -We are at `49/57` (86%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `108/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. +We are at `52/57` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `73/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows: @@ -91,15 +91,15 @@ The current status of the implementation in the [electra-support](https://github - [x] Modified `compute_proposer_index` ([Spec](docs/specs/electra/beacon-chain.md#modified-compute_proposer_index), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) - [x] New `get_max_effective_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-get_max_effective_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419)) -#### Epoch Processing (5/8 - 63%) +#### Epoch Processing (8/8 - 100%) -- [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch)) +- [x] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428)) - [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420)) - [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417)) - [x] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -- [ ] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations)) -- [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates)) +- [x] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428)) +- [x] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428)) - [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) #### Block Processing (10/12 - 83%) diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex index a3024c8a3..1139e254f 100644 --- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex +++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex @@ -44,7 +44,6 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do hysteresis_quotient = ChainSpec.get("HYSTERESIS_QUOTIENT") hysteresis_downward_multiplier = ChainSpec.get("HYSTERESIS_DOWNWARD_MULTIPLIER") hysteresis_upward_multiplier = ChainSpec.get("HYSTERESIS_UPWARD_MULTIPLIER") - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") hysteresis_increment = div(effective_balance_increment, hysteresis_quotient) downward_threshold = hysteresis_increment * hysteresis_downward_multiplier @@ -55,7 +54,10 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do |> Aja.Vector.zip_with(balances, fn %Validator{} = validator, balance -> if balance + downward_threshold < validator.effective_balance or validator.effective_balance + upward_threshold < balance do - min(balance - rem(balance, effective_balance_increment), max_effective_balance) + min( + balance - rem(balance, effective_balance_increment), + Validator.get_max_effective_balance(validator) + ) |> then(&%{validator | effective_balance: &1}) else validator @@ -569,8 +571,45 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do @spec process_pending_consolidations(BeaconState.t()) :: {:ok, BeaconState.t()} def process_pending_consolidations(%BeaconState{} = state) do - # TODO: Not implemented yet - {:ok, state} + next_epoch = Accessors.get_current_epoch(state) + 1 + + {next_pending_consolidation, state} = + state.pending_consolidations + |> Enum.reduce_while({0, state}, fn pending_consolidation, + {next_pending_consolidation, state} -> + source_index = pending_consolidation.source_index + target_index = pending_consolidation.target_index + source_validator = state.validators |> Aja.Vector.at(source_index) + + cond do + source_validator.slashed -> + {:cont, {next_pending_consolidation + 1, state}} + + source_validator.withdrawable_epoch > next_epoch -> + {:halt, {next_pending_consolidation, state}} + + true -> + source_effective_balance = + min( + Aja.Vector.at(state.balances, source_index), + source_validator.effective_balance + ) + + updated_state = + state + |> BeaconState.decrease_balance(source_index, source_effective_balance) + |> BeaconState.increase_balance(target_index, source_effective_balance) + + {:cont, {next_pending_consolidation + 1, updated_state}} + end + end) + + {:ok, + %BeaconState{ + state + | pending_consolidations: + Enum.drop(state.pending_consolidations, next_pending_consolidation) + }} end defp apply_pending_deposit(state, deposit) do From f04ecaa19de4543a9760e1b3e034584ba799d2f3 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 24 Apr 2025 18:31:55 -0300 Subject: [PATCH 21/23] chore: disable assertoor failing checks (#1447) --- .../config/assertoor/cl-stability-check.yml | 67 ++++++++++--------- .github/config/assertoor/network-params.yml | 2 +- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/.github/config/assertoor/cl-stability-check.yml b/.github/config/assertoor/cl-stability-check.yml index 9f9338705..998a27341 100644 --- a/.github/config/assertoor/cl-stability-check.yml +++ b/.github/config/assertoor/cl-stability-check.yml @@ -12,38 +12,39 @@ tasks: title: "Check if all clients are ready" timeout: 1m -- name: run_tasks_concurrent - title: "Check if all EL & CL clients are synced and the tx spammer is working" - timeout: 5m - config: - tasks: - - name: check_consensus_sync_status - title: "Check if CL clients are synced" - - name: check_execution_sync_status - title: "Check if EL clients are synced" +# TODO(#1413) The following tasks are not working yet, this will be part of the 3rd electra phase +# - name: run_tasks_concurrent +# title: "Check if all EL & CL clients are synced and the tx spammer is working" +# timeout: 5m +# config: +# tasks: +# - name: check_consensus_sync_status +# title: "Check if CL clients are synced" +# - name: check_execution_sync_status +# title: "Check if EL clients are synced" -- name: run_task_matrix - title: "Check block proposals from all client pairs" - timeout: 6m - configVars: - matrixValues: "validatorPairNames" - config: - runConcurrent: true - matrixVar: "validatorPairName" - task: - name: check_consensus_block_proposals - title: "Wait for block proposal from ${validatorPairName}" - config: - minTransactionCount: 80 # For some reason the tx fuzz is working different than the old spammer, we need to check it - configVars: - validatorNamePattern: "validatorPairName" +# - name: run_task_matrix +# title: "Check block proposals from all client pairs" +# timeout: 6m +# configVars: +# matrixValues: "validatorPairNames" +# config: +# runConcurrent: true +# matrixVar: "validatorPairName" +# task: +# name: check_consensus_block_proposals +# title: "Wait for block proposal from ${validatorPairName}" +# config: +# minTransactionCount: 80 # For some reason the tx fuzz is working different than the old spammer, we need to check it +# configVars: +# validatorNamePattern: "validatorPairName" -- name: run_tasks_concurrent - title: "Check chain stability (reorgs and forks)" - timeout: 7m - config: - tasks: - - name: check_consensus_reorgs - title: "Check consensus reorgs" - - name: check_consensus_forks - title: "Check consensus forks" +# - name: run_tasks_concurrent +# title: "Check chain stability (reorgs and forks)" +# timeout: 7m +# config: +# tasks: +# - name: check_consensus_reorgs +# title: "Check consensus reorgs" +# - name: check_consensus_forks +# title: "Check consensus forks" diff --git a/.github/config/assertoor/network-params.yml b/.github/config/assertoor/network-params.yml index 4cfd40399..f72e10580 100644 --- a/.github/config/assertoor/network-params.yml +++ b/.github/config/assertoor/network-params.yml @@ -27,7 +27,7 @@ assertoor_params: run_stability_check: false run_block_proposal_check: false tests: - - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml + - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/electra-support/.github/config/assertoor/cl-stability-check.yml tx_fuzz_params: tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"] From 911af4585bd983d32ac418fc94108069e886bf24 Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Fri, 25 Apr 2025 14:39:52 -0300 Subject: [PATCH 22/23] feat: electra withdrawals (#1431) --- electra-gap.md | 11 +- .../state_transition/operations.ex | 174 ++++++++++++++---- .../validator/block_builder.ex | 2 +- 3 files changed, 148 insertions(+), 39 deletions(-) diff --git a/electra-gap.md b/electra-gap.md index 739f7ed8b..1091d2711 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -36,7 +36,7 @@ Here we will detail the current implementation gaps with the specs and the way t ### Phase 1: Beacon Chain Implementation -We are at `52/57` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `73/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. +We are at `54/58` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `4/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows: @@ -102,9 +102,9 @@ The current status of the implementation in the [electra-support](https://github - [x] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428)) - [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) -#### Block Processing (10/12 - 83%) +#### Block Processing (12/13 - 92%) -- [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals)) +- [x] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1431)) - [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload)) - [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426)) @@ -116,6 +116,9 @@ The current status of the implementation in the [electra-support](https://github - [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) - [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424)) +- [x] Modified `get_expected_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_expected_withdrawals), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1431)) + +## Execution Engine #### Execution Engine (0/3 - 0%) @@ -168,4 +171,4 @@ The aim is to have the node running on kurtosis uninterrupted for 72+ hrs. The f ## Changelog - **April 10th, 2025:** Created the document with the implementation gap. -- **April 22th, 2025:** Updated the document with a clear roadmap, next steps and detailed current status. \ No newline at end of file +- **April 22th, 2025:** Updated the document with a clear roadmap, next steps and detailed current status. diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index 924dec218..3a845be61 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -294,11 +294,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do %BeaconState{validators: validators} = state, %ExecutionPayload{withdrawals: withdrawals} ) do - expected_withdrawals = get_expected_withdrawals(state) + {expected_withdrawals, processed_partial_withdrawals_count} = get_expected_withdrawals(state) with :ok <- check_withdrawals(withdrawals, expected_withdrawals) do state |> Map.update!(:balances, &decrease_balances(&1, withdrawals)) + |> then( + &%BeaconState{ + &1 + | pending_partial_withdrawals: + Enum.drop(&1.pending_partial_withdrawals, processed_partial_withdrawals_count) + } + ) |> update_next_withdrawal_index(withdrawals) |> update_next_withdrawal_validator_index(withdrawals, Aja.Vector.size(validators)) |> then(&{:ok, &1}) @@ -355,49 +362,147 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do end) end - @spec get_expected_withdrawals(BeaconState.t()) :: list(Withdrawal.t()) + @spec get_expected_withdrawals(BeaconState.t()) :: + {list(Withdrawal.t()), non_neg_integer()} def get_expected_withdrawals(%BeaconState{} = state) do # Compute the next batch of withdrawals which should be included in a block. epoch = Accessors.get_current_epoch(state) max_validators_per_withdrawals_sweep = ChainSpec.get("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP") max_withdrawals_per_payload = ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD") - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + # Consume pending partial withdrawals + {processed_partial_withdrawals_count, withdrawal_index, pending_partial_withdrawals} = + state.pending_partial_withdrawals + |> Enum.reduce_while({0, state.next_withdrawal_index, []}, fn withdrawal, + {processed_partial_withdrawals_count, + withdrawal_index, + withdrawals} -> + process_partial_withdrawal( + state, + withdrawal, + processed_partial_withdrawals_count, + withdrawal_index, + withdrawals + ) + end) bound = state.validators |> Aja.Vector.size() |> min(max_validators_per_withdrawals_sweep) + # Sweep for remaining. + non_partial_withdrawals = + Stream.zip([state.validators, state.balances]) + |> Stream.with_index() + |> Stream.cycle() + |> Stream.drop(state.next_withdrawal_validator_index) + |> Stream.take(bound) + |> Stream.map(fn {{validator, balance}, index} -> + partially_withdrawn_balance = + Enum.sum( + for withdrawal <- pending_partial_withdrawals, + withdrawal.validator_index == index, + do: withdrawal.amount + ) + + balance = balance - partially_withdrawn_balance - Stream.zip([state.validators, state.balances]) - |> Stream.with_index() - |> Stream.cycle() - |> Stream.drop(state.next_withdrawal_validator_index) - |> Stream.take(bound) - |> Stream.map(fn {{validator, balance}, index} -> - cond do - Validator.fully_withdrawable_validator?(validator, balance, epoch) -> - {validator, balance, index} - - Validator.partially_withdrawable_validator?(validator, balance) -> - {validator, balance - max_effective_balance, index} - - true -> - nil - end - end) - |> Stream.reject(&is_nil/1) - |> Stream.with_index() - |> Stream.map(fn {{validator, balance, validator_index}, index} -> - %Validator{withdrawal_credentials: withdrawal_credentials} = validator + cond do + Validator.fully_withdrawable_validator?(validator, balance, epoch) -> + {validator, balance, index} - <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials + Validator.partially_withdrawable_validator?(validator, balance) -> + {validator, balance - Validator.get_max_effective_balance(validator), index} - %Withdrawal{ - index: index + state.next_withdrawal_index, - validator_index: validator_index, - address: execution_address, - amount: balance + true -> + nil + end + end) + |> Stream.reject(&is_nil/1) + |> Stream.with_index() + |> Stream.map(fn {{validator, balance, validator_index}, index} -> + %Validator{withdrawal_credentials: withdrawal_credentials} = validator + + <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials + + %Withdrawal{ + index: index + withdrawal_index, + validator_index: validator_index, + address: execution_address, + amount: balance + } + end) + + complete_withdrawals = + (pending_partial_withdrawals ++ Enum.to_list(non_partial_withdrawals)) + |> Enum.take(max_withdrawals_per_payload) + + {complete_withdrawals, processed_partial_withdrawals_count} + end + + defp process_partial_withdrawal( + state, + withdrawal, + processed_partial_withdrawals_count, + withdrawal_index, + withdrawals + ) do + epoch = Accessors.get_current_epoch(state) + + max_pending_partials_per_withdrawals_sweep = + ChainSpec.get("MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP") + + # We expect partial withdrawals to be ordered by withdrawable epoch + if withdrawal.withdrawable_epoch > epoch || + processed_partial_withdrawals_count == max_pending_partials_per_withdrawals_sweep do + {:halt, {processed_partial_withdrawals_count, withdrawal_index, withdrawals}} + else + do_process_partial_withdrawal( + state, + withdrawal, + processed_partial_withdrawals_count, + withdrawal_index, + withdrawals + ) + end + end + + defp do_process_partial_withdrawal( + state, + withdrawal, + processed_partial_withdrawals_count, + withdrawal_index, + withdrawals + ) do + far_future_epoch = Constants.far_future_epoch() + min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE") + validator = Aja.Vector.at(state.validators, withdrawal.validator_index) + has_sufficient_effective_balance = validator.effective_balance >= min_activation_balance + + has_excess_balance = + Aja.Vector.at(state.balances, withdrawal.validator_index) > min_activation_balance + + if validator.exit_epoch == far_future_epoch && has_sufficient_effective_balance && + has_excess_balance do + withdrawable_balance = + min( + Aja.Vector.at(state.balances, withdrawal.validator_index) - + min_activation_balance, + withdrawal.amount + ) + + <<_::binary-size(12), address::binary>> = validator.withdrawal_credentials + + withdrawal = %Withdrawal{ + index: withdrawal_index, + validator_index: withdrawal.validator_index, + address: address, + amount: withdrawable_balance } - end) - |> Enum.take(max_withdrawals_per_payload) + + {:cont, + {processed_partial_withdrawals_count + 1, withdrawal_index + 1, + withdrawals ++ [withdrawal]}} + else + {:cont, {processed_partial_withdrawals_count + 1, withdrawal_index, withdrawals}} + end end @spec process_proposer_slashing(BeaconState.t(), Types.ProposerSlashing.t()) :: @@ -1030,7 +1135,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do is_correct_source_address = case validator.withdrawal_credentials do - <<_::binary-size(12), rest>> -> rest == address + <<_::binary-size(12), validator_address::binary>> -> validator_address == address _ -> false end @@ -1102,7 +1207,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do {:ok, %BeaconState{ state - | pending_partial_withdrawals: + | # We should make sure that partial withdrawals are ordered by withdrawable epoch + pending_partial_withdrawals: state.pending_partial_withdrawals ++ [pending_partial_withdrawal] }} else diff --git a/lib/lambda_ethereum_consensus/validator/block_builder.ex b/lib/lambda_ethereum_consensus/validator/block_builder.ex index f24184a29..7ad0a20a0 100644 --- a/lib/lambda_ethereum_consensus/validator/block_builder.ex +++ b/lib/lambda_ethereum_consensus/validator/block_builder.ex @@ -137,7 +137,7 @@ defmodule LambdaEthereumConsensus.Validator.BlockBuilder do prev_randao: Randao.get_randao_mix(mid_state.randao_mixes, current_epoch), # TODO: add suggested fee recipient suggested_fee_recipient: <<0::160>>, - withdrawals: Operations.get_expected_withdrawals(mid_state), + withdrawals: elem(Operations.get_expected_withdrawals(mid_state), 0), parent_beacon_block_root: head_root } From e56e7d481955ca581cbfc8b769b8442411af5c5d Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:19:19 -0300 Subject: [PATCH 23/23] feat: electra `MAX_BLOBS_PER_BLOCK_ELECTRA` (#1438) --- .github/workflows/ci.yml | 2 +- config/networks/mainnet/config.yaml | 2 ++ config/networks/minimal/config.yaml | 2 ++ electra-gap.md | 3 +-- lib/lambda_ethereum_consensus/state_transition/operations.ex | 2 +- test/spec/runners/fork_choice.ex | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53cbd058c..b480abe9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -293,7 +293,7 @@ jobs: strategy: fail-fast: false matrix: - fork: ["deneb"] + fork: ["electra"] config: ["minimal", "general", "mainnet"] runs-on: ubuntu-24.04 steps: diff --git a/config/networks/mainnet/config.yaml b/config/networks/mainnet/config.yaml index 4bc84c29a..6db16ed68 100644 --- a/config/networks/mainnet/config.yaml +++ b/config/networks/mainnet/config.yaml @@ -159,3 +159,5 @@ EIP7594_FORK_EPOCH: 18446744073709551615 MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**8 * 10**9) (= 256,000,000,000) Gwei MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# 9 +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 diff --git a/config/networks/minimal/config.yaml b/config/networks/minimal/config.yaml index 291883bef..ed4f79b87 100644 --- a/config/networks/minimal/config.yaml +++ b/config/networks/minimal/config.yaml @@ -157,3 +157,5 @@ EIP7594_FORK_EPOCH: 18446744073709551615 MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # [customized] 2**8 * 10**9) (= 256,000,000,000) Gwei MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 +# 9 +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 diff --git a/electra-gap.md b/electra-gap.md index 1091d2711..d89f5e6c1 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -36,8 +36,7 @@ Here we will detail the current implementation gaps with the specs and the way t ### Phase 1: Beacon Chain Implementation -We are at `54/58` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have still `4/11370` spec test failures, but we are working on them. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. Our aim as mentioned before is to reach `0` failures before going to the next phase. - +We are at `54/58` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have fixed all `11370` spec tests. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase. The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows: diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index 3a845be61..7625fdb77 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -241,7 +241,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do payload.timestamp != Misc.compute_timestamp_at_slot(state, state.slot) -> {:error, "Timestamp verification failed"} - body.blob_kzg_commitments |> length() > ChainSpec.get("MAX_BLOBS_PER_BLOCK") -> + body.blob_kzg_commitments |> length() > ChainSpec.get("MAX_BLOBS_PER_BLOCK_ELECTRA") -> {:error, "Too many commitments"} # Cache execution payload header diff --git a/test/spec/runners/fork_choice.ex b/test/spec/runners/fork_choice.ex index 5f7702601..aa7ed969a 100644 --- a/test/spec/runners/fork_choice.ex +++ b/test/spec/runners/fork_choice.ex @@ -172,7 +172,7 @@ defmodule ForkChoiceTestRunner do # TODO: validate the filename's hash defp load_blob_data(case_dir, block, %{blobs: "blobs_0x" <> _hash = blobs_file, proofs: proofs}) do - schema = {:list, TypeAliases.blob(), ChainSpec.get("MAX_BLOBS_PER_BLOCK")} + schema = {:list, TypeAliases.blob(), ChainSpec.get("MAX_BLOBS_PER_BLOCK_ELECTRA")} blobs = SpecTestUtils.read_ssz_ex_from_file!(case_dir <> "/#{blobs_file}.ssz_snappy", schema)