From 29043d9f3ab3582da5f544f3390ee49b8cf83485 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 30 Dec 2022 10:55:48 +0300 Subject: [PATCH 1/5] Add beacon primitives --- Cargo.lock | 201 ++------- Cargo.toml | 3 + pallets/leaf-provider/Cargo.toml | 5 - pallets/types/Cargo.toml | 18 +- pallets/types/src/lib.rs | 35 +- pallets/types/src/types.rs | 4 +- primitives/ethereum/Cargo.toml | 67 +++ primitives/ethereum/src/beacon/config.rs | 229 ++++++++++ primitives/ethereum/src/beacon/mod.rs | 401 ++++++++++++++++++ .../ethereum}/src/difficulty.rs | 0 .../ethereum}/src/ethashdata.rs | 0 .../ethereum}/src/ethashproof.rs | 0 .../ethereum}/src/header.rs | 0 primitives/ethereum/src/lib.rs | 36 ++ .../types => primitives/ethereum}/src/log.rs | 0 .../types => primitives/ethereum}/src/mpt.rs | 0 .../ethereum}/src/network_config.rs | 55 ++- .../ethereum}/src/receipt.rs | 0 primitives/ethereum/src/serde_utils.rs | 80 ++++ .../ethereum}/src/test_utils.rs | 5 +- .../ethereum}/tests/fixtures/11090290.json | 0 .../ethereum}/tests/fixtures/11550000.json | 0 .../ethereum}/tests/fixtures/3.json | 0 .../tests/difficultyArrowGlacier.json | 0 .../difficultyArrowGlacierForkBlock.json | 0 .../tests/difficultyArrowGlacierMinus1.json | 0 .../difficultyArrowGlacierTimeDiff1.json | 0 .../difficultyArrowGlacierTimeDiff2.json | 0 .../fixtures/tests/difficultyByzantium.json | 0 .../tests/difficultyConstantinople.json | 0 .../fixtures/tests/difficultyEIP2384.json | 0 .../tests/difficultyEIP2384_random.json | 0 .../tests/difficultyEIP2384_random_to20M.json | 0 .../fixtures/tests/difficultyFrontier.json | 0 .../fixtures/tests/difficultyGrayGlacier.json | 0 .../tests/difficultyGrayGlacierForkBlock.json | 0 .../tests/difficultyGrayGlacierMinus1.json | 0 .../tests/difficultyGrayGlacierTimeDiff1.json | 0 .../tests/difficultyGrayGlacierTimeDiff2.json | 0 .../fixtures/tests/difficultyHomestead.json | 0 .../fixtures/tests/difficultyMainNetwork.json | 0 .../fixtures/tests/difficultyRopsten.json | 0 42 files changed, 915 insertions(+), 224 deletions(-) create mode 100644 primitives/ethereum/Cargo.toml create mode 100644 primitives/ethereum/src/beacon/config.rs create mode 100644 primitives/ethereum/src/beacon/mod.rs rename {pallets/types => primitives/ethereum}/src/difficulty.rs (100%) rename {pallets/types => primitives/ethereum}/src/ethashdata.rs (100%) rename {pallets/types => primitives/ethereum}/src/ethashproof.rs (100%) rename {pallets/types => primitives/ethereum}/src/header.rs (100%) create mode 100644 primitives/ethereum/src/lib.rs rename {pallets/types => primitives/ethereum}/src/log.rs (100%) rename {pallets/types => primitives/ethereum}/src/mpt.rs (100%) rename {pallets/types => primitives/ethereum}/src/network_config.rs (70%) rename {pallets/types => primitives/ethereum}/src/receipt.rs (100%) create mode 100644 primitives/ethereum/src/serde_utils.rs rename {pallets/types => primitives/ethereum}/src/test_utils.rs (97%) rename {pallets/types => primitives/ethereum}/tests/fixtures/11090290.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/11550000.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/3.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyArrowGlacier.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyArrowGlacierMinus1.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyByzantium.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyConstantinople.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyEIP2384.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyEIP2384_random.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyEIP2384_random_to20M.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyFrontier.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyGrayGlacier.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyGrayGlacierMinus1.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyHomestead.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyMainNetwork.json (100%) rename {pallets/types => primitives/ethereum}/tests/fixtures/tests/difficultyRopsten.json (100%) diff --git a/Cargo.lock b/Cargo.lock index 9397492c..b7bb60e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,7 +129,7 @@ checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -339,21 +339,18 @@ name = "bridge-types" version = "0.1.0" dependencies = [ "beefy-primitives", - "enum-iterator", "ethabi", - "ethash", "ethbloom", + "ethereum-primitives", "ethereum-types", "frame-support", "frame-system", "getrandom 0.2.8", - "hex-literal", + "hex", "libsecp256k1", - "parity-bytes", "parity-scale-codec", "rand 0.7.3", "rlp", - "rustc-hex", "scale-info", "serde", "serde_json", @@ -361,7 +358,6 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", - "wasm-bindgen-test", "xcm", ] @@ -419,12 +415,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -443,15 +433,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ckb-merkle-mountain-range" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f061f97d64fd1822664bdfb722f7ae5469a97b77567390f7442be5b5dc82a5b" -dependencies = [ - "cfg-if 0.1.10", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -468,7 +449,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen", ] @@ -773,26 +754,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "enum-iterator" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "environmental" version = "1.1.4" @@ -837,6 +798,33 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethereum-primitives" +version = "0.1.0" +dependencies = [ + "ethabi", + "ethash", + "ethbloom", + "ethereum-types", + "frame-support", + "getrandom 0.2.8", + "hex", + "hex-literal", + "libsecp256k1", + "parity-bytes", + "parity-scale-codec", + "rand 0.7.3", + "rlp", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "wasm-bindgen-test", +] + [[package]] name = "ethereum-types" version = "0.14.1" @@ -916,7 +904,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "parity-scale-codec", "scale-info", "serde", @@ -1135,7 +1123,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -1148,7 +1136,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -1562,7 +1550,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", "sec1", @@ -1597,7 +1585,6 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "leaf-provider" version = "0.1.1" dependencies = [ - "beefy-merkle-tree", "beefy-primitives", "bridge-types", "ethabi", @@ -1605,8 +1592,6 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-beefy-mmr", - "pallet-mmr", "parity-scale-codec", "scale-info", "serde", @@ -1743,7 +1728,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -2042,84 +2027,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-beefy" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "beefy-primitives", - "frame-support", - "frame-system", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-beefy-mmr" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "array-bytes", - "beefy-merkle-tree", - "beefy-primitives", - "frame-support", - "frame-system", - "log", - "pallet-beefy", - "pallet-mmr", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-mmr" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "ckb-merkle-mountain-range", - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-mmr-primitives", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-session" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "frame-support", - "frame-system", - "impl-trait-for-tuples", - "log", - "pallet-timestamp", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-session", - "sp-staking", - "sp-std", - "sp-trie", -] - [[package]] name = "pallet-timestamp" version = "4.0.0-dev" @@ -2177,7 +2084,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown", "impl-trait-for-tuples", "parity-util-mem-derive", @@ -2219,7 +2126,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -2582,7 +2489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" dependencies = [ "bitvec", - "cfg-if 1.0.0", + "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", @@ -2715,7 +2622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -2740,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -2752,7 +2659,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.6", ] @@ -3200,20 +3107,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sp-session" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-core", - "sp-runtime", - "sp-staking", - "sp-std", -] - [[package]] name = "sp-staking" version = "4.0.0-dev" @@ -3586,7 +3479,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "proc-macro-error", "proc-macro2", "quote", @@ -3741,7 +3634,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3861,7 +3754,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "digest 0.10.6", "rand 0.8.5", "static_assertions", @@ -3961,7 +3854,7 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -3986,7 +3879,7 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 872ae1d3..5cb54b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,7 @@ members = [ "pallets/substrate-channel/rpc", "pallets/beefy-light-client/runtime-api", "pallets/beefy-light-client/rpc", + "primitives/*", ] + +resolver = "2" diff --git a/pallets/leaf-provider/Cargo.toml b/pallets/leaf-provider/Cargo.toml index b3ef5219..dd2fd98a 100644 --- a/pallets/leaf-provider/Cargo.toml +++ b/pallets/leaf-provider/Cargo.toml @@ -25,9 +25,6 @@ sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkado sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } sp-mmr-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -pallet-mmr = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -beefy-merkle-tree = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } beefy-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } bridge-types = { path = "../types", default-features = false } @@ -51,8 +48,6 @@ std = [ "sp-std/std", "bridge-types/std", "sp-mmr-primitives/std", - "pallet-mmr/std", - "pallet-beefy-mmr/std", ] runtime-benchmarks = [ "bridge-types/runtime-benchmarks", diff --git a/pallets/types/Cargo.toml b/pallets/types/Cargo.toml index d6802ca8..51e30a2c 100644 --- a/pallets/types/Cargo.toml +++ b/pallets/types/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Snowfork "] edition = "2021" [dependencies] -enum-iterator = "0.6.0" codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive", ] } @@ -19,13 +18,11 @@ ethereum-types = { version = "0.14.1", default-features = false, features = [ "rlp", "serialize", ] } -hex = { package = "rustc-hex", version = "2.1.0", default-features = false } -hex-literal = { version = "0.3.1", default-features = false } +hex = { version = "0.4", default-features = false } libsecp256k1 = { version = "0.7", default-features = false } -parity-bytes = { version = "0.1.2", default-features = false } rlp = { version = "0.5", default-features = false } -serde = { version = "1.0.101", optional = true } -getrandom = { version = "0.2.1", features = ["js"] } +serde = { version = "1.0.101", optional = true, features = ["derive"] } +getrandom = { version = "0.2.8", features = ["js"], default-features = false } frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } @@ -36,12 +33,12 @@ sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "po beefy-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0", default-features = false } -ethash = { git = "https://github.com/sora-xor/ethash.git", branch = "sora-v1.6.0", default-features = false } serde_json = { version = "1.0", optional = true } xcm = { git = "https://github.com/paritytech/polkadot.git", branch = "release-v0.9.31", default-features = false } +ethereum-primitives = { path = "../../primitives/ethereum", default-features = false } + [dev-dependencies] -wasm-bindgen-test = "0.3.19" rand = "0.7.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -59,14 +56,17 @@ std = [ "ethereum-types/std", "hex/std", "libsecp256k1/std", - "parity-bytes/std", "rlp/std", + "frame-support/std", + "frame-system/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", "beefy-primitives/std", + "ethereum-primitives/std", "xcm/std", + "getrandom/std", ] runtime-benchmarks = [ diff --git a/pallets/types/src/lib.rs b/pallets/types/src/lib.rs index 98499fe0..7c2c735d 100644 --- a/pallets/types/src/lib.rs +++ b/pallets/types/src/lib.rs @@ -31,30 +31,16 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod channel_abi; -pub mod difficulty; -pub mod ethashdata; -pub mod ethashproof; -pub mod header; -pub mod log; -mod mpt; -pub mod network_config; -pub mod receipt; pub mod substrate; pub mod traits; pub mod types; -#[cfg(any(feature = "test", test))] -pub mod test_utils; - +#[cfg(feature = "std")] +use ::serde::{Deserialize, Serialize}; use codec::{Decode, Encode}; -pub use ethereum_types::{Address, H128, H160, H256, H512, H64, U256}; use frame_support::RuntimeDebug; -use sp_std::vec; -use sp_std::vec::Vec; -pub use header::{Header, HeaderId}; -pub use log::Log; -pub use receipt::Receipt; +pub use ethereum_primitives::*; #[derive(Debug)] pub enum DecodeError { @@ -91,7 +77,7 @@ pub type EVMChainId = U256; scale_info::TypeInfo, codec::MaxEncodedLen, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum SubNetworkId { Mainnet, Kusama, @@ -111,7 +97,7 @@ pub enum SubNetworkId { scale_info::TypeInfo, codec::MaxEncodedLen, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum GenericNetworkId { EVM(EVMChainId), Sub(SubNetworkId), @@ -139,14 +125,3 @@ pub enum GenericAccount { } pub const CHANNEL_INDEXING_PREFIX: &[u8] = b"commitment"; - -pub fn import_digest(network_id: &EVMChainId, header: &Header) -> Vec -where - EVMChainId: Encode, - Header: Encode, -{ - let mut digest = vec![]; - network_id.encode_to(&mut digest); - header.encode_to(&mut digest); - digest -} diff --git a/pallets/types/src/types.rs b/pallets/types/src/types.rs index 98356372..87a3befe 100644 --- a/pallets/types/src/types.rs +++ b/pallets/types/src/types.rs @@ -89,7 +89,7 @@ pub struct Message { /// A message relayed from Parachain. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ParachainMessage { pub payload: Vec, pub nonce: MessageNonce, @@ -132,7 +132,7 @@ impl From for AuxiliaryDigest { /// Auxiliary [`DigestItem`] to include in header digest. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AuxiliaryDigestItem { /// A batch of messages has been committed. Commitment(GenericNetworkId, H256), diff --git a/primitives/ethereum/Cargo.toml b/primitives/ethereum/Cargo.toml new file mode 100644 index 00000000..197c7b2a --- /dev/null +++ b/primitives/ethereum/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "ethereum-primitives" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2", default-features = false, features = ["derive"] } +ethbloom = { version = "0.13.0", default-features = false, features = [ + "rlp", + "codec", +] } +ethereum-types = { version = "0.14.1", default-features = false, features = [ + "codec", + "rlp", + "serialize", +] } +hex = { version = "0.4", default-features = false } +hex-literal = { version = "0.3.1", default-features = false } +libsecp256k1 = { version = "0.7", default-features = false } +parity-bytes = { version = "0.1.2", default-features = false } +rlp = { version = "0.5", default-features = false } +serde = { version = "1.0.101", optional = true, features = ["derive"] } +getrandom = { version = "0.2.1", features = ["js"] } + +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } + +ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0", default-features = false } +ethash = { git = "https://github.com/sora-xor/ethash.git", branch = "sora-v1.6.0", default-features = false } +serde_json = { version = "1.0", optional = true } + +[dev-dependencies] +wasm-bindgen-test = "0.3.19" +rand = "0.7.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[features] +default = ["std"] +expensive_tests = [] +test = ["serde_json"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "ethabi/std", + "ethbloom/std", + "ethereum-types/std", + "hex/std", + "libsecp256k1/std", + "parity-bytes/std", + "rlp/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = ["frame-support/runtime-benchmarks"] diff --git a/primitives/ethereum/src/beacon/config.rs b/primitives/ethereum/src/beacon/config.rs new file mode 100644 index 00000000..e257e1bd --- /dev/null +++ b/primitives/ethereum/src/beacon/config.rs @@ -0,0 +1,229 @@ +use codec::{Decode, Encode}; +use frame_support::RuntimeDebug; +use hex_literal::hex; +use scale_info::TypeInfo; + +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct BeaconFork { + #[cfg_attr( + feature = "std", + serde(with = "crate::serde_utils::serde_fork_version") + )] + fork_version: ForkVersion, + epoch: u64, +} + +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct BeaconForkSchedule { + pub phase0: BeaconFork, + pub altair: BeaconFork, + pub bellatrix: BeaconFork, + // Disabled forks + // capella: BeaconFork, + // sharding: BeaconFork, +} + +impl BeaconForkSchedule { + pub fn fork_version(&self, epoch: u64) -> ForkVersion { + if epoch >= self.bellatrix.epoch { + self.bellatrix.fork_version + } else if epoch >= self.altair.epoch { + self.altair.fork_version + } else { + self.phase0.fork_version + } + } + + // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/sepolia.ts + pub fn sepolia() -> Self { + Self { + phase0: BeaconFork { + fork_version: hex!("90000069"), + epoch: 0, + }, + altair: BeaconFork { + fork_version: hex!("90000070"), + epoch: 50, + }, + bellatrix: BeaconFork { + fork_version: hex!("90000071"), + epoch: 100, + }, + } + } + + // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/goerli.ts + pub fn goerli() -> Self { + Self { + phase0: BeaconFork { + fork_version: hex!("00001020"), + epoch: 0, + }, + altair: BeaconFork { + fork_version: hex!("01001020"), + epoch: 36660, + }, + bellatrix: BeaconFork { + fork_version: hex!("02001020"), + epoch: 112260, + }, + } + } + + // https://github.com/eth-clients/merge-testnets/blob/302fe27afdc7a9d15b1766a0c0a9d64319140255/mainnet-shadow-fork-13/config.yaml + pub fn mainnet() -> Self { + Self { + phase0: BeaconFork { + fork_version: hex!("00000000"), + epoch: 0, + }, + altair: BeaconFork { + fork_version: hex!("01000000"), + epoch: 74240, + }, + bellatrix: BeaconFork { + fork_version: hex!("02000000"), + epoch: 144896, + }, + } + } + + pub fn local() -> Self { + Self { + phase0: BeaconFork { + fork_version: hex!("00000001"), + epoch: 0, + }, + altair: BeaconFork { + fork_version: hex!("01000001"), + epoch: 0, + }, + bellatrix: BeaconFork { + fork_version: hex!("02000001"), + epoch: 0, + }, + } + } +} + +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BeaconNetworkConfig { + Mainnet, + Minimal, +} + +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct BeaconConsensusConfig { + pub config: BeaconNetworkConfig, + pub fork_schedule: BeaconForkSchedule, +} + +impl BeaconConsensusConfig { + pub fn mainnet() -> Self { + Self { + config: BeaconNetworkConfig::Mainnet, + fork_schedule: BeaconForkSchedule::mainnet(), + } + } + + pub fn goerli() -> Self { + Self { + config: BeaconNetworkConfig::Mainnet, + fork_schedule: BeaconForkSchedule::goerli(), + } + } + + pub fn sepolia() -> Self { + Self { + config: BeaconNetworkConfig::Mainnet, + fork_schedule: BeaconForkSchedule::sepolia(), + } + } + + pub fn local() -> Self { + Self { + config: BeaconNetworkConfig::Minimal, + fork_schedule: BeaconForkSchedule::local(), + } + } + + pub fn fork_version_from_slot(&self, slot: u64) -> ForkVersion { + let epoch = self.config.compute_epoch(slot); + self.fork_schedule.fork_version(epoch) + } +} + +impl BeaconNetworkConfig { + pub fn compute_current_sync_period(&self, slot: u64) -> u64 { + match self { + Self::Minimal => { + slot / MINIMAL_SLOTS_PER_EPOCH / MINIMAL_EPOCHS_PER_SYNC_COMMITTEE_PERIOD + } + Self::Mainnet => { + slot / MAINNET_SLOTS_PER_EPOCH / MAINNET_EPOCHS_PER_SYNC_COMMITTEE_PERIOD + } + } + } + + pub fn compute_epoch(&self, slot: u64) -> u64 { + match self { + Self::Minimal => slot / MINIMAL_SLOTS_PER_EPOCH, + Self::Mainnet => slot / MAINNET_SLOTS_PER_EPOCH, + } + } + + pub fn epoch_length(&self) -> u64 { + match self { + Self::Minimal => MINIMAL_SLOTS_PER_EPOCH, + Self::Mainnet => MAINNET_SLOTS_PER_EPOCH, + } + } +} + +pub const MAINNET_SLOTS_PER_EPOCH: u64 = 32; +pub const MAINNET_EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 256; +pub const MAINNET_SYNC_COMMITTEE_SIZE: usize = 512; + +pub const MINIMAL_SLOTS_PER_EPOCH: u64 = 8; +pub const MINIMAL_EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 8; +pub const MINIMAL_SYNC_COMMITTEE_SIZE: usize = 32; + +use super::ForkVersion; + +pub const CURRENT_SYNC_COMMITTEE_INDEX: u64 = 22; +pub const CURRENT_SYNC_COMMITTEE_DEPTH: u64 = 5; + +pub const NEXT_SYNC_COMMITTEE_DEPTH: u64 = 5; +pub const NEXT_SYNC_COMMITTEE_INDEX: u64 = 23; + +pub const FINALIZED_ROOT_DEPTH: u64 = 6; +pub const FINALIZED_ROOT_INDEX: u64 = 41; + +pub const MAX_PROPOSER_SLASHINGS: usize = 16; +pub const MAX_ATTESTER_SLASHINGS: usize = 2; +pub const MAX_ATTESTATIONS: usize = 128; +pub const MAX_DEPOSITS: usize = 16; +pub const MAX_VOLUNTARY_EXITS: usize = 16; +pub const MAX_VALIDATORS_PER_COMMITTEE: usize = 2048; +pub const MAX_EXTRA_DATA_BYTES: usize = 32; +pub const MAX_LOGS_BLOOM_SIZE: usize = 256; +pub const MAX_FEE_RECIPIENT_SIZE: usize = 20; +pub const MAX_TRANSACTIONS: usize = 1048576; +pub const MAX_BYTES_PER_TRANSACTION: usize = 1073741824; +pub const MAX_H256_PER_TRANSACTION: usize = (MAX_BYTES_PER_TRANSACTION + 31) / 32; + +pub const DEPOSIT_CONTRACT_TREE_DEPTH: usize = 32; + +/// GENESIS_FORK_VERSION('0x00000000') +pub const GENESIS_FORK_VERSION: ForkVersion = [30, 30, 30, 30]; + +/// DomainType('0x07000000') +/// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#domain-types +pub const DOMAIN_SYNC_COMMITTEE: [u8; 4] = [7, 0, 0, 0]; + +pub const PUBKEY_SIZE: usize = 48; +pub const SIGNATURE_SIZE: usize = 96; diff --git a/primitives/ethereum/src/beacon/mod.rs b/primitives/ethereum/src/beacon/mod.rs new file mode 100644 index 00000000..e249478b --- /dev/null +++ b/primitives/ethereum/src/beacon/mod.rs @@ -0,0 +1,401 @@ +pub mod config; + +use crate::mpt; + +#[cfg(feature = "std")] +use crate::serde_utils::{serde_hex, serde_str}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ + traits::{Hash, Keccak256}, + RuntimeDebug, +}; +use sp_std::prelude::*; + +#[cfg(feature = "std")] +use core::fmt::Formatter; +#[cfg(feature = "std")] +use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "std")] +use sp_std::fmt::Result as StdResult; + +pub fn keccak_256(data: &[u8]) -> [u8; 32] { + Keccak256::hash(data).0 +} + +pub type Root = H256; +pub type Domain = H256; +pub type ValidatorIndex = u64; +pub type ProofBranch = Vec; +pub type ForkVersion = [u8; 4]; + +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct PublicKey(pub [u8; 48]); + +impl Default for PublicKey { + fn default() -> Self { + PublicKey([0u8; 48]) + } +} + +#[cfg(feature = "std")] +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +#[cfg(feature = "std")] +struct PublicKeyVisitor; + +#[cfg(feature = "std")] +impl<'de> Visitor<'de> for PublicKeyVisitor { + type Value = PublicKey; + + fn expecting(&self, formatter: &mut Formatter) -> StdResult { + formatter.write_str("a hex string") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + let str_without_0x = match v.strip_prefix("0x") { + Some(val) => val, + None => v, + }; + + let hex_bytes = match hex::decode(str_without_0x) { + Ok(bytes) => bytes, + Err(e) => return Err(Error::custom(e.to_string())), + }; + if hex_bytes.len() != 48 { + return Err(Error::custom("publickey expected to be 48 characters")); + } + + let mut data = [0u8; 48]; + data[0..48].copy_from_slice(&hex_bytes); + Ok(PublicKey(data)) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(PublicKeyVisitor) + } +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct ForkData { + // 1 or 0 bit, indicates whether a sync committee participated in a vote + pub current_version: [u8; 4], + pub genesis_validators_root: [u8; 32], +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct SigningData { + pub object_root: Root, + pub domain: Domain, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ExecutionHeader { + pub parent_hash: H256, + pub fee_recipient: H160, + pub state_root: H256, + pub receipts_root: H256, + pub logs_bloom: Vec, + pub prev_randao: H256, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub block_number: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub gas_limit: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub gas_used: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub timestamp: u64, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub extra_data: Vec, + pub base_fee_per_gas: U256, + pub block_hash: H256, + pub transactions_root: H256, +} + +/// Sync committee as it is stored in the runtime storage. +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct SyncCommittee { + pub pubkeys: Vec, + pub aggregate_pubkey: PublicKey, +} + +/// Beacon block header as it is stored in the runtime storage. The block root is the +/// Merklization of a BeaconHeader. +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BeaconHeader { + // The slot for which this block is created. Must be greater than the slot of the block defined by parentRoot. + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub slot: u64, + // The index of the validator that proposed the block. + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub proposer_index: ValidatorIndex, + // The block root of the parent block, forming a block chain. + pub parent_root: Root, + // The hash root of the post state of running the state transition through this block. + pub state_root: Root, + // The hash root of the beacon block body + pub body_root: Root, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct DepositData { + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub pubkey: Vec, + pub withdrawal_credentials: H256, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub amount: u64, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub signature: Vec, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Deposit { + pub proof: Vec, + pub data: DepositData, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Checkpoint { + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub epoch: u64, + pub root: H256, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct AttestationData { + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub slot: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub index: u64, + pub beacon_block_root: H256, + pub source: Checkpoint, + pub target: Checkpoint, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct IndexedAttestation { + pub attesting_indices: Vec, + pub data: AttestationData, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub signature: Vec, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct SignedHeader { + pub message: BeaconHeader, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub signature: Vec, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ProposerSlashing { + pub signed_header_1: SignedHeader, + pub signed_header_2: SignedHeader, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct AttesterSlashing { + pub attestation_1: IndexedAttestation, + pub attestation_2: IndexedAttestation, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Attestation { + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub aggregation_bits: Vec, + pub data: AttestationData, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub signature: Vec, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct VoluntaryExit { + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub epoch: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub validator_index: u64, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Eth1Data { + pub deposit_root: H256, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub deposit_count: u64, + pub block_hash: H256, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct SyncAggregate { + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub sync_committee_bits: Vec, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub sync_committee_signature: Vec, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ExecutionPayload { + pub parent_hash: H256, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub fee_recipient: Vec, + pub state_root: H256, + pub receipts_root: H256, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub logs_bloom: Vec, + pub prev_randao: H256, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub block_number: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub gas_limit: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub gas_used: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub timestamp: u64, + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub extra_data: Vec, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub base_fee_per_gas: U256, + pub block_hash: H256, + pub transactions_root: H256, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Body { + #[cfg_attr(feature = "std", serde(with = "serde_hex"))] + pub randao_reveal: Vec, + pub eth1_data: Eth1Data, + pub graffiti: H256, + pub proposer_slashings: Vec, + pub attester_slashings: Vec, + pub attestations: Vec, + pub deposits: Vec, + pub voluntary_exits: Vec, + pub sync_aggregate: SyncAggregate, + pub execution_payload: ExecutionPayload, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BeaconBlock { + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub slot: u64, + #[cfg_attr(feature = "std", serde(with = "serde_str"))] + pub proposer_index: u64, + pub parent_root: H256, + pub state_root: H256, + pub body: Body, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct InitialSync { + pub header: BeaconHeader, + pub current_sync_committee: SyncCommittee, + pub current_sync_committee_branch: ProofBranch, + pub validators_root: Root, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct SyncCommitteePeriodUpdate { + pub attested_header: BeaconHeader, + pub next_sync_committee: SyncCommittee, + pub next_sync_committee_branch: ProofBranch, + pub finalized_header: BeaconHeader, + pub finality_branch: ProofBranch, + pub sync_aggregate: SyncAggregate, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct FinalizedHeaderUpdate { + pub attested_header: BeaconHeader, + pub finalized_header: BeaconHeader, + pub finality_branch: ProofBranch, + pub sync_aggregate: SyncAggregate, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct BlockUpdate { + pub block: BeaconBlock, + pub sync_aggregate: SyncAggregate, +} + +#[derive(Default, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct ExecutionHeaderState { + pub beacon_block_root: H256, + pub beacon_slot: u64, + pub block_hash: H256, + pub block_number: u64, +} + +impl ExecutionHeader { + // Copied from ethereum_snowbridge::header + pub fn check_receipt_proof( + &self, + proof: &[Vec], + ) -> Option> { + match self.apply_merkle_proof(proof) { + Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)), + Some((_, _)) => None, + None => None, + } + } + + // Copied from ethereum_snowbridge::header + pub fn apply_merkle_proof(&self, proof: &[Vec]) -> Option<(H256, Vec)> { + let mut iter = proof.iter().rev(); + let first_bytes = match iter.next() { + Some(b) => b, + None => return None, + }; + let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?; + + let final_hash: Option<[u8; 32]> = + iter.fold(Some(keccak_256(first_bytes)), |maybe_hash, bytes| { + let expected_hash = maybe_hash?; + let node: Box = bytes.as_slice().try_into().ok()?; + if (*node).contains_hash(expected_hash.into()) { + return Some(keccak_256(bytes)); + } + None + }); + + final_hash.map(|hash| (hash.into(), item_to_prove.value)) + } +} diff --git a/pallets/types/src/difficulty.rs b/primitives/ethereum/src/difficulty.rs similarity index 100% rename from pallets/types/src/difficulty.rs rename to primitives/ethereum/src/difficulty.rs diff --git a/pallets/types/src/ethashdata.rs b/primitives/ethereum/src/ethashdata.rs similarity index 100% rename from pallets/types/src/ethashdata.rs rename to primitives/ethereum/src/ethashdata.rs diff --git a/pallets/types/src/ethashproof.rs b/primitives/ethereum/src/ethashproof.rs similarity index 100% rename from pallets/types/src/ethashproof.rs rename to primitives/ethereum/src/ethashproof.rs diff --git a/pallets/types/src/header.rs b/primitives/ethereum/src/header.rs similarity index 100% rename from pallets/types/src/header.rs rename to primitives/ethereum/src/header.rs diff --git a/primitives/ethereum/src/lib.rs b/primitives/ethereum/src/lib.rs new file mode 100644 index 00000000..de6b15e5 --- /dev/null +++ b/primitives/ethereum/src/lib.rs @@ -0,0 +1,36 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod beacon; +pub mod difficulty; +pub mod ethashdata; +pub mod ethashproof; +pub mod header; +pub mod log; +pub mod mpt; +pub mod network_config; +pub mod receipt; +#[cfg(feature = "std")] +pub mod serde_utils; +#[cfg(any(feature = "test", test))] +pub mod test_utils; + +use codec::Encode; +pub use ethereum_types::{Address as EthAddress, H128, H160, H256, H512, H64, U256}; +pub use header::Header; +pub use header::HeaderId; +pub use log::Log; +pub use receipt::Receipt; +use sp_std::prelude::*; + +pub type EVMChainId = U256; + +pub fn import_digest(network_id: &EVMChainId, header: &Header) -> Vec +where + EVMChainId: Encode, + Header: Encode, +{ + let mut digest = vec![]; + network_id.encode_to(&mut digest); + header.encode_to(&mut digest); + digest +} diff --git a/pallets/types/src/log.rs b/primitives/ethereum/src/log.rs similarity index 100% rename from pallets/types/src/log.rs rename to primitives/ethereum/src/log.rs diff --git a/pallets/types/src/mpt.rs b/primitives/ethereum/src/mpt.rs similarity index 100% rename from pallets/types/src/mpt.rs rename to primitives/ethereum/src/mpt.rs diff --git a/pallets/types/src/network_config.rs b/primitives/ethereum/src/network_config.rs similarity index 70% rename from pallets/types/src/network_config.rs rename to primitives/ethereum/src/network_config.rs index b7185007..216e4468 100644 --- a/pallets/types/src/network_config.rs +++ b/primitives/ethereum/src/network_config.rs @@ -29,6 +29,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ + beacon::config::BeaconConsensusConfig, difficulty::{ClassicForkConfig, ForkConfig}, EVMChainId, }; @@ -44,6 +45,7 @@ pub enum Consensus { Ethash { fork_config: ForkConfig }, Etchash { fork_config: ClassicForkConfig }, Clique { period: u64, epoch: u64 }, + Beacon(BeaconConsensusConfig), } impl Consensus { @@ -52,6 +54,7 @@ impl Consensus { Consensus::Clique { epoch, .. } => *epoch, Consensus::Ethash { fork_config } => fork_config.epoch_length(), Consensus::Etchash { fork_config } => fork_config.calc_epoch_length(block_number), + Consensus::Beacon(consensus) => consensus.config.epoch_length(), } } } @@ -60,61 +63,71 @@ impl Consensus { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum NetworkConfig { Mainnet, - Ropsten, Sepolia, - Rinkeby, Goerli, Classic, Mordor, + Local, Custom { chain_id: EVMChainId, consensus: Consensus, }, + // Keep it for tests + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + RopstenEthash, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + SepoliaEthash, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + MainnetEthash, } impl NetworkConfig { pub fn chain_id(&self) -> EVMChainId { match self { NetworkConfig::Mainnet => 1u32.into(), - NetworkConfig::Ropsten => 3u32.into(), NetworkConfig::Sepolia => 11155111u32.into(), - NetworkConfig::Rinkeby => 4u32.into(), NetworkConfig::Goerli => 5u32.into(), NetworkConfig::Classic => 61u32.into(), NetworkConfig::Mordor => 63u32.into(), + NetworkConfig::Local => 4224u32.into(), NetworkConfig::Custom { chain_id, .. } => *chain_id, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::RopstenEthash => 3u32.into(), + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::MainnetEthash => 1u32.into(), + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::SepoliaEthash => 11155111u32.into(), } } pub fn consensus(&self) -> Consensus { match self { - NetworkConfig::Mainnet => Consensus::Ethash { - fork_config: ForkConfig::mainnet(), - }, - NetworkConfig::Ropsten => Consensus::Ethash { - fork_config: ForkConfig::ropsten(), - }, - NetworkConfig::Sepolia => Consensus::Ethash { - fork_config: ForkConfig::sepolia(), - }, + NetworkConfig::Mainnet => Consensus::Beacon(BeaconConsensusConfig::mainnet()), + NetworkConfig::Goerli => Consensus::Beacon(BeaconConsensusConfig::goerli()), + NetworkConfig::Sepolia => Consensus::Beacon(BeaconConsensusConfig::sepolia()), + NetworkConfig::Local => Consensus::Beacon(BeaconConsensusConfig::local()), NetworkConfig::Classic => Consensus::Etchash { fork_config: ClassicForkConfig::classic(), }, NetworkConfig::Mordor => Consensus::Etchash { fork_config: ClassicForkConfig::mordor(), }, - NetworkConfig::Rinkeby => Consensus::Clique { - period: 15, - epoch: 30000, - }, - NetworkConfig::Goerli => Consensus::Clique { - period: 15, - epoch: 30000, - }, NetworkConfig::Custom { consensus: protocol, .. } => *protocol, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::RopstenEthash => Consensus::Ethash { + fork_config: ForkConfig::ropsten(), + }, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::MainnetEthash => Consensus::Ethash { + fork_config: ForkConfig::mainnet(), + }, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::SepoliaEthash => Consensus::Ethash { + fork_config: ForkConfig::sepolia(), + }, } } } diff --git a/pallets/types/src/receipt.rs b/primitives/ethereum/src/receipt.rs similarity index 100% rename from pallets/types/src/receipt.rs rename to primitives/ethereum/src/receipt.rs diff --git a/primitives/ethereum/src/serde_utils.rs b/primitives/ethereum/src/serde_utils.rs new file mode 100644 index 00000000..d54cc30e --- /dev/null +++ b/primitives/ethereum/src/serde_utils.rs @@ -0,0 +1,80 @@ +pub mod serde_fork_version { + use serde::Deserialize; + + use crate::beacon::ForkVersion; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let value = if value.len() > 1 && value[0..2] == *"0x" { + &value[2..] + } else { + &value + }; + let res = hex::decode(value).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) + })?; + let res = res.try_into().map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) + })?; + Ok(res) + } + + pub fn serialize(value: &[u8], serializer: S) -> Result { + serializer.serialize_str(&format!("0x{}", hex::encode(value))) + } +} + +pub mod serde_str { + use core::str::FromStr; + use serde::Deserialize; + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: TryFrom, + >::Error: std::fmt::Debug, + { + let value = String::deserialize(deserializer)?; + let res = u128::from_str(&value).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from string: {:?}", err)) + })?; + let res = T::try_from(res).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from string: {:?}", err)) + })?; + Ok(res) + } + + pub fn serialize( + value: &T, + serializer: S, + ) -> Result { + serializer.serialize_str(&value.to_string()) + } +} + +pub mod serde_hex { + use serde::Deserialize; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let value = if value.len() > 1 && value[0..2] == *"0x" { + &value[2..] + } else { + &value + }; + let res = hex::decode(value).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) + })?; + Ok(res) + } + + pub fn serialize(value: &[u8], serializer: S) -> Result { + serializer.serialize_str(&format!("0x{}", hex::encode(value))) + } +} diff --git a/pallets/types/src/test_utils.rs b/primitives/ethereum/src/test_utils.rs similarity index 97% rename from pallets/types/src/test_utils.rs rename to primitives/ethereum/src/test_utils.rs index ea232127..efd06184 100644 --- a/pallets/types/src/test_utils.rs +++ b/primitives/ethereum/src/test_utils.rs @@ -47,10 +47,9 @@ impl<'de> Deserialize<'de> for Hex { s = s[2..].to_string(); } if s.len() % 2 == 1 { - s.insert(0, '0'); + s.insert_str(0, "0"); } - let v: Vec = hex::FromHexIter::new(&s).map(|x| x.unwrap()).collect(); - Ok(Hex(v)) + Ok(Hex(hex::decode(&s).unwrap())) } } diff --git a/pallets/types/tests/fixtures/11090290.json b/primitives/ethereum/tests/fixtures/11090290.json similarity index 100% rename from pallets/types/tests/fixtures/11090290.json rename to primitives/ethereum/tests/fixtures/11090290.json diff --git a/pallets/types/tests/fixtures/11550000.json b/primitives/ethereum/tests/fixtures/11550000.json similarity index 100% rename from pallets/types/tests/fixtures/11550000.json rename to primitives/ethereum/tests/fixtures/11550000.json diff --git a/pallets/types/tests/fixtures/3.json b/primitives/ethereum/tests/fixtures/3.json similarity index 100% rename from pallets/types/tests/fixtures/3.json rename to primitives/ethereum/tests/fixtures/3.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacier.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierMinus1.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierMinus1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierMinus1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierMinus1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json diff --git a/pallets/types/tests/fixtures/tests/difficultyByzantium.json b/primitives/ethereum/tests/fixtures/tests/difficultyByzantium.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyByzantium.json rename to primitives/ethereum/tests/fixtures/tests/difficultyByzantium.json diff --git a/pallets/types/tests/fixtures/tests/difficultyConstantinople.json b/primitives/ethereum/tests/fixtures/tests/difficultyConstantinople.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyConstantinople.json rename to primitives/ethereum/tests/fixtures/tests/difficultyConstantinople.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384_random.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384_random.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384_random_to20M.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random_to20M.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384_random_to20M.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random_to20M.json diff --git a/pallets/types/tests/fixtures/tests/difficultyFrontier.json b/primitives/ethereum/tests/fixtures/tests/difficultyFrontier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyFrontier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyFrontier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacier.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierMinus1.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierMinus1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierMinus1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierMinus1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json diff --git a/pallets/types/tests/fixtures/tests/difficultyHomestead.json b/primitives/ethereum/tests/fixtures/tests/difficultyHomestead.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyHomestead.json rename to primitives/ethereum/tests/fixtures/tests/difficultyHomestead.json diff --git a/pallets/types/tests/fixtures/tests/difficultyMainNetwork.json b/primitives/ethereum/tests/fixtures/tests/difficultyMainNetwork.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyMainNetwork.json rename to primitives/ethereum/tests/fixtures/tests/difficultyMainNetwork.json diff --git a/pallets/types/tests/fixtures/tests/difficultyRopsten.json b/primitives/ethereum/tests/fixtures/tests/difficultyRopsten.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyRopsten.json rename to primitives/ethereum/tests/fixtures/tests/difficultyRopsten.json From b161a89a61c2bd10945aea5f30663fc81247e66e Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 10 Mar 2023 12:53:45 +0300 Subject: [PATCH 2/5] Add beacon light client --- Cargo.lock | 911 ++++++++++--- pallets/ethereum-beacon-client/Cargo.toml | 69 + pallets/ethereum-beacon-client/src/lib.rs | 992 ++++++++++++++ pallets/ethereum-beacon-client/src/weights.rs | 54 + pallets/types/Cargo.toml | 2 - primitives/beacon/Cargo.toml | 44 + primitives/beacon/src/aggregate_and_proof.rs | 93 ++ primitives/beacon/src/application_domain.rs | 18 + primitives/beacon/src/attestation.rs | 120 ++ primitives/beacon/src/attestation_data.rs | 48 + primitives/beacon/src/attestation_duty.rs | 19 + primitives/beacon/src/attester_slashing.rs | 32 + primitives/beacon/src/beacon_block.rs | 726 +++++++++++ primitives/beacon/src/beacon_block_body.rs | 427 ++++++ primitives/beacon/src/beacon_block_header.rs | 64 + primitives/beacon/src/beacon_committee.rs | 27 + primitives/beacon/src/beacon_state.rs | 136 ++ .../beacon/src/bls_to_execution_change.rs | 51 + primitives/beacon/src/builder_bid.rs | 144 ++ primitives/beacon/src/chain_spec.rs | 1159 +++++++++++++++++ primitives/beacon/src/checkpoint.rs | 31 + primitives/beacon/src/config_and_preset.rs | 106 ++ primitives/beacon/src/consts.rs | 24 + .../beacon/src/contribution_and_proof.rs | 73 ++ primitives/beacon/src/deposit.rs | 31 + primitives/beacon/src/deposit_data.rs | 56 + primitives/beacon/src/deposit_message.rs | 33 + .../beacon/src/deposit_tree_snapshot.rs | 77 ++ primitives/beacon/src/enr_fork_id.rs | 33 + primitives/beacon/src/eth1_data.rs | 32 + primitives/beacon/src/eth_spec.rs | 389 ++++++ primitives/beacon/src/execution_block_hash.rs | 109 ++ .../beacon/src/execution_block_header.rs | 76 ++ primitives/beacon/src/execution_payload.rs | 167 +++ .../beacon/src/execution_payload_header.rs | 254 ++++ primitives/beacon/src/fork.rs | 45 + primitives/beacon/src/fork_data.rs | 32 + primitives/beacon/src/fork_name.rs | 174 +++ .../beacon/src/fork_versioned_response.rs | 91 ++ primitives/beacon/src/graffiti.rs | 173 +++ primitives/beacon/src/historical_batch.rs | 30 + primitives/beacon/src/historical_summary.rs | 31 + primitives/beacon/src/indexed_attestation.rs | 105 ++ primitives/beacon/src/int_to_bytes.rs | 78 ++ primitives/beacon/src/lib.rs | 235 ++++ .../beacon/src/light_client_bootstrap.rs | 31 + .../src/light_client_finality_update.rs | 35 + .../src/light_client_optimistic_update.rs | 30 + primitives/beacon/src/light_client_update.rs | 83 ++ primitives/beacon/src/participation_flags.rs | 103 ++ primitives/beacon/src/participation_list.rs | 17 + primitives/beacon/src/payload.rs | 924 +++++++++++++ primitives/beacon/src/pending_attestation.rs | 33 + primitives/beacon/src/preset.rs | 208 +++ .../beacon/src/proposer_preparation_data.rs | 15 + primitives/beacon/src/proposer_slashing.rs | 37 + primitives/beacon/src/relative_epoch.rs | 72 + primitives/beacon/src/safe_arith.rs | 132 ++ primitives/beacon/src/selection_proof.rs | 93 ++ primitives/beacon/src/shuffling_id.rs | 45 + .../beacon/src/signed_aggregate_and_proof.rs | 75 ++ primitives/beacon/src/signed_beacon_block.rs | 454 +++++++ .../beacon/src/signed_beacon_block_header.rs | 53 + .../src/signed_bls_to_execution_change.rs | 27 + .../src/signed_contribution_and_proof.rs | 73 ++ .../beacon/src/signed_voluntary_exit.rs | 30 + primitives/beacon/src/signing_data.rs | 36 + primitives/beacon/src/slot_data.rs | 13 + primitives/beacon/src/slot_epoch.rs | 167 +++ primitives/beacon/src/slot_epoch_macros.rs | 324 +++++ primitives/beacon/src/subnet_id.rs | 125 ++ primitives/beacon/src/sync_aggregate.rs | 97 ++ .../src/sync_aggregator_selection_data.rs | 29 + primitives/beacon/src/sync_committee.rs | 103 ++ .../beacon/src/sync_committee_contribution.rs | 119 ++ .../beacon/src/sync_committee_message.rs | 61 + .../beacon/src/sync_committee_subscription.rs | 29 + primitives/beacon/src/sync_duty.rs | 83 ++ primitives/beacon/src/sync_selection_proof.rs | 106 ++ primitives/beacon/src/sync_subnet_id.rs | 97 ++ primitives/beacon/src/validator.rs | 123 ++ .../beacon/src/validator_registration_data.rs | 37 + .../beacon/src/validator_subscription.rs | 34 + primitives/beacon/src/voluntary_exit.rs | 55 + primitives/beacon/src/withdrawal.rs | 31 + primitives/ethereum/Cargo.toml | 3 +- .../src/{beacon/config.rs => beacon.rs} | 140 +- primitives/ethereum/src/beacon/mod.rs | 401 ------ primitives/ethereum/src/difficulty.rs | 10 +- primitives/ethereum/src/header.rs | 13 +- primitives/ethereum/src/network_config.rs | 22 +- 91 files changed, 11733 insertions(+), 716 deletions(-) create mode 100644 pallets/ethereum-beacon-client/Cargo.toml create mode 100644 pallets/ethereum-beacon-client/src/lib.rs create mode 100644 pallets/ethereum-beacon-client/src/weights.rs create mode 100644 primitives/beacon/Cargo.toml create mode 100644 primitives/beacon/src/aggregate_and_proof.rs create mode 100644 primitives/beacon/src/application_domain.rs create mode 100644 primitives/beacon/src/attestation.rs create mode 100644 primitives/beacon/src/attestation_data.rs create mode 100644 primitives/beacon/src/attestation_duty.rs create mode 100644 primitives/beacon/src/attester_slashing.rs create mode 100644 primitives/beacon/src/beacon_block.rs create mode 100644 primitives/beacon/src/beacon_block_body.rs create mode 100644 primitives/beacon/src/beacon_block_header.rs create mode 100644 primitives/beacon/src/beacon_committee.rs create mode 100644 primitives/beacon/src/beacon_state.rs create mode 100644 primitives/beacon/src/bls_to_execution_change.rs create mode 100644 primitives/beacon/src/builder_bid.rs create mode 100644 primitives/beacon/src/chain_spec.rs create mode 100644 primitives/beacon/src/checkpoint.rs create mode 100644 primitives/beacon/src/config_and_preset.rs create mode 100644 primitives/beacon/src/consts.rs create mode 100644 primitives/beacon/src/contribution_and_proof.rs create mode 100644 primitives/beacon/src/deposit.rs create mode 100644 primitives/beacon/src/deposit_data.rs create mode 100644 primitives/beacon/src/deposit_message.rs create mode 100644 primitives/beacon/src/deposit_tree_snapshot.rs create mode 100644 primitives/beacon/src/enr_fork_id.rs create mode 100644 primitives/beacon/src/eth1_data.rs create mode 100644 primitives/beacon/src/eth_spec.rs create mode 100644 primitives/beacon/src/execution_block_hash.rs create mode 100644 primitives/beacon/src/execution_block_header.rs create mode 100644 primitives/beacon/src/execution_payload.rs create mode 100644 primitives/beacon/src/execution_payload_header.rs create mode 100644 primitives/beacon/src/fork.rs create mode 100644 primitives/beacon/src/fork_data.rs create mode 100644 primitives/beacon/src/fork_name.rs create mode 100644 primitives/beacon/src/fork_versioned_response.rs create mode 100644 primitives/beacon/src/graffiti.rs create mode 100644 primitives/beacon/src/historical_batch.rs create mode 100644 primitives/beacon/src/historical_summary.rs create mode 100644 primitives/beacon/src/indexed_attestation.rs create mode 100644 primitives/beacon/src/int_to_bytes.rs create mode 100644 primitives/beacon/src/lib.rs create mode 100644 primitives/beacon/src/light_client_bootstrap.rs create mode 100644 primitives/beacon/src/light_client_finality_update.rs create mode 100644 primitives/beacon/src/light_client_optimistic_update.rs create mode 100644 primitives/beacon/src/light_client_update.rs create mode 100644 primitives/beacon/src/participation_flags.rs create mode 100644 primitives/beacon/src/participation_list.rs create mode 100644 primitives/beacon/src/payload.rs create mode 100644 primitives/beacon/src/pending_attestation.rs create mode 100644 primitives/beacon/src/preset.rs create mode 100644 primitives/beacon/src/proposer_preparation_data.rs create mode 100644 primitives/beacon/src/proposer_slashing.rs create mode 100644 primitives/beacon/src/relative_epoch.rs create mode 100644 primitives/beacon/src/safe_arith.rs create mode 100644 primitives/beacon/src/selection_proof.rs create mode 100644 primitives/beacon/src/shuffling_id.rs create mode 100644 primitives/beacon/src/signed_aggregate_and_proof.rs create mode 100644 primitives/beacon/src/signed_beacon_block.rs create mode 100644 primitives/beacon/src/signed_beacon_block_header.rs create mode 100644 primitives/beacon/src/signed_bls_to_execution_change.rs create mode 100644 primitives/beacon/src/signed_contribution_and_proof.rs create mode 100644 primitives/beacon/src/signed_voluntary_exit.rs create mode 100644 primitives/beacon/src/signing_data.rs create mode 100644 primitives/beacon/src/slot_data.rs create mode 100644 primitives/beacon/src/slot_epoch.rs create mode 100644 primitives/beacon/src/slot_epoch_macros.rs create mode 100644 primitives/beacon/src/subnet_id.rs create mode 100644 primitives/beacon/src/sync_aggregate.rs create mode 100644 primitives/beacon/src/sync_aggregator_selection_data.rs create mode 100644 primitives/beacon/src/sync_committee.rs create mode 100644 primitives/beacon/src/sync_committee_contribution.rs create mode 100644 primitives/beacon/src/sync_committee_message.rs create mode 100644 primitives/beacon/src/sync_committee_subscription.rs create mode 100644 primitives/beacon/src/sync_duty.rs create mode 100644 primitives/beacon/src/sync_selection_proof.rs create mode 100644 primitives/beacon/src/sync_subnet_id.rs create mode 100644 primitives/beacon/src/validator.rs create mode 100644 primitives/beacon/src/validator_registration_data.rs create mode 100644 primitives/beacon/src/validator_subscription.rs create mode 100644 primitives/beacon/src/voluntary_exit.rs create mode 100644 primitives/beacon/src/withdrawal.rs rename primitives/ethereum/src/{beacon/config.rs => beacon.rs} (51%) delete mode 100644 primitives/ethereum/src/beacon/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b7bb60e3..b9455438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -47,6 +47,11 @@ dependencies = [ "memchr", ] +[[package]] +name = "amcl" +version = "0.3.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -67,9 +72,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "approx" @@ -106,9 +111,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" dependencies = [ "proc-macro2", "quote", @@ -123,9 +128,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -156,9 +161,66 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "beacon" +version = "0.1.0" +dependencies = [ + "bls", + "bytes", + "derivative", + "eth2_hashing", + "eth2_serde_utils", + "eth2_ssz", + "eth2_ssz_derive", + "eth2_ssz_types", + "ethereum-types", + "hex", + "itertools", + "lazy_static", + "maplit", + "parity-scale-codec", + "scale-info", + "serde", + "serde-big-array", + "serde_json", + "serde_with", + "serde_yaml", + "superstruct", + "tempfile", + "tree_hash", + "tree_hash_derive", +] + +[[package]] +name = "beacon-light-client" +version = "0.1.0" +dependencies = [ + "beacon", + "bls", + "bridge-types", + "eth2_ssz_types", + "ethereum-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "pallet-timestamp", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", + "tree_hash", +] [[package]] name = "beef" @@ -269,9 +331,9 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest 0.10.6", ] @@ -299,9 +361,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array 0.14.6", ] @@ -315,6 +377,39 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "blst", + "eth2_hashing", + "eth2_serde_utils", + "eth2_ssz", + "ethereum-types", + "hex", + "milagro_bls", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "serde", + "tree_hash", + "zeroize", +] + +[[package]] +name = "blst" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a30d0edd9dd1c60ddb42b80341c7852f6f985279a5c1a83659dcb65899dec99" +dependencies = [ + "cc", + "glob", + "threadpool", + "which", + "zeroize", +] + [[package]] name = "bridge-common" version = "0.1.0" @@ -345,7 +440,6 @@ dependencies = [ "ethereum-types", "frame-support", "frame-system", - "getrandom 0.2.8", "hex", "libsecp256k1", "parity-scale-codec", @@ -363,18 +457,19 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-slice-cast" @@ -396,15 +491,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-expr" @@ -430,6 +525,7 @@ dependencies = [ "iana-time-zone", "num-integer", "num-traits", + "serde", "winapi", ] @@ -550,9 +646,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" dependencies = [ "cc", "cxxbridge-flags", @@ -562,9 +658,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ "cc", "codespan-reporting", @@ -577,21 +673,91 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn", +] + [[package]] name = "der" version = "0.5.1" @@ -647,7 +813,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -700,9 +866,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" @@ -732,9 +898,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -760,6 +926,84 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "eth2_hashing" +version = "0.3.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "lazy_static", + "sha2 0.10.6", +] + +[[package]] +name = "eth2_serde_utils" +version = "0.1.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "ethereum-types", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "eth2_ssz" +version = "0.4.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "ethereum-types", + "itertools", + "smallvec", +] + +[[package]] +name = "eth2_ssz_derive" +version = "0.3.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "eth2_ssz_types" +version = "0.2.2" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "derivative", + "eth2_serde_utils", + "eth2_ssz", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "tree_hash", + "typenum", +] + [[package]] name = "ethabi" version = "17.2.0" @@ -807,7 +1051,6 @@ dependencies = [ "ethbloom", "ethereum-types", "frame-support", - "getrandom 0.2.8", "hex", "hex-literal", "libsecp256k1", @@ -847,6 +1090,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.11.1" @@ -1004,9 +1256,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1019,9 +1271,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1029,15 +1281,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1047,15 +1299,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1064,15 +1316,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-timer" @@ -1082,9 +1334,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1137,23 +1389,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -1175,9 +1431,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -1218,15 +1474,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -1276,9 +1532,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1310,9 +1566,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -1356,6 +1612,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "impl-codec" version = "0.6.0" @@ -1404,6 +1666,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -1413,6 +1684,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "io-lifetimes" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1424,15 +1705,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1580,6 +1861,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "leaf-provider" @@ -1635,9 +1919,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libm" @@ -1695,9 +1979,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -1712,6 +1996,12 @@ dependencies = [ "statrs", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1740,6 +2030,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchers" version = "0.0.1" @@ -1793,25 +2089,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "milagro_bls" +version = "1.5.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" +dependencies = [ + "amcl", + "rand 0.8.5", + "zeroize", +] + [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1862,9 +2168,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] @@ -1913,9 +2219,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -1923,18 +2229,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -2053,9 +2359,9 @@ checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] name = "parity-scale-codec" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -2068,9 +2374,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2122,22 +2428,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -2222,13 +2528,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] @@ -2257,18 +2562,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -2387,18 +2692,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" +checksum = "a9af2cf09ef80e610097515e80095b7f76660a92743c4185aff5406cd5ce3dd5" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" +checksum = "9c501201393982e275433bc55de7d6ae6f00e7699cd5572c5b57581cd69c881b" dependencies = [ "proc-macro2", "quote", @@ -2407,9 +2712,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -2470,23 +2775,37 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustix" +version = "0.36.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scale-info" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" dependencies = [ "bitvec", "cfg-if", @@ -2498,9 +2817,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2540,9 +2859,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sec1" @@ -2559,9 +2878,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "secp256k1-sys", ] @@ -2586,18 +2905,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" dependencies = [ "proc-macro2", "quote", @@ -2606,15 +2934,55 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_with" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea48c9627169d206b35905699f513f513c303ab9d964a59b44fdcf66c1d1ab7" +dependencies = [ + "base64", + "chrono", + "hex", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6b7e52858f9f06c25e1c566bbb4ab428200cb3b30053ea09dc50837de7538b" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -2707,9 +3075,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2722,9 +3090,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -3265,6 +3633,12 @@ dependencies = [ "sp-std", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.5.4" @@ -3277,9 +3651,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.36.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d92659e7d18d82b803824a9ba5a6022cff101c3491d027c1c1d8d30e749284" +checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d" dependencies = [ "Inflector", "num-format", @@ -3309,6 +3683,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.24.1" @@ -3426,11 +3806,25 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "superstruct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4e1f478a7728f8855d7e620e9a152cf8932c6614f86564c886f9b8141f3201" +dependencies = [ + "darling 0.13.4", + "itertools", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -3455,11 +3849,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -3488,18 +3895,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -3508,13 +3915,39 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -3554,15 +3987,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.23.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -3573,7 +4006,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -3589,9 +4022,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -3600,9 +4033,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -3614,12 +4047,20 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.9" +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ - "serde", + "indexmap", + "toml_datetime", + "winnow", ] [[package]] @@ -3714,6 +4155,26 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tree_hash" +version = "0.4.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "eth2_hashing", + "ethereum-types", + "smallvec", +] + +[[package]] +name = "tree_hash_derive" +version = "0.4.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "darling 0.13.4", + "quote", + "syn", +] + [[package]] name = "trie-db" version = "0.24.0" @@ -3738,15 +4199,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tt-call" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" [[package]] name = "twox-hash" @@ -3789,9 +4250,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -3814,6 +4275,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + [[package]] name = "valuable" version = "0.1.0" @@ -3850,9 +4317,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3860,9 +4327,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -3875,9 +4342,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -3887,9 +4354,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3897,9 +4364,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -3910,15 +4377,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wasm-bindgen-test" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d" +checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3930,9 +4397,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7" +checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" dependencies = [ "proc-macro2", "quote", @@ -3973,14 +4440,25 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4027,47 +4505,80 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winnow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] [[package]] name = "wyz" diff --git a/pallets/ethereum-beacon-client/Cargo.toml b/pallets/ethereum-beacon-client/Cargo.toml new file mode 100644 index 00000000..da6d40f8 --- /dev/null +++ b/pallets/ethereum-beacon-client/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "beacon-light-client" +authors = ["Anonymous"] +description = "" +version = "0.1.0" +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +license = "Unlicense" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.137", optional = true } +codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = [ "derive" ] } +scale-info = { version = "2.2.0", default-features = false, features = [ "derive" ] } +rlp = { version = "0.5", default-features = false } +hex-literal = { version = "0.3.4", optional = true } + +frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +beacon = { path = "../../primitives/beacon", default-features = false } +ethereum-primitives = { path = "../../primitives/ethereum", default-features = false } +bridge-types = { path = "../types", default-features = false } +tree_hash = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +eth2_ssz_types = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.2", default-features = false } +bls = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.0", default-features = false, features = ["milagro"] } + +[dev-dependencies] +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } +serde_json = "1.0.68" +hex-literal = { version = "0.3.4" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + 'frame-benchmarking?/std', + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "pallet-timestamp/std", + "beacon/std", + "ethereum-primitives/std", + "tree_hash/std", + "bridge-types/std", + "eth2_ssz_types/std", + "bls/std" +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal" +] + +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/ethereum-beacon-client/src/lib.rs b/pallets/ethereum-beacon-client/src/lib.rs new file mode 100644 index 00000000..17b52783 --- /dev/null +++ b/pallets/ethereum-beacon-client/src/lib.rs @@ -0,0 +1,992 @@ +//! # Ethereum Beacon Client +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod weights; + +pub const DOMAIN_SYNC_COMMITTEE: u32 = 7; + +pub use weights::WeightInfo; + +use frame_support::{dispatch::DispatchResult, log, traits::UnixTime, transactional}; +use frame_system::ensure_signed; +use sp_core::H256; +use sp_io::hashing::sha2_256; +use sp_std::prelude::*; + +pub use pallet::*; + +#[derive(codec::Decode, codec::Encode, codec::MaxEncodedLen, scale_info::TypeInfo)] +pub struct ExecutionHeaderState { + beacon_block_root: H256, + beacon_slot: beacon::Slot, + block_hash: H256, + block_number: u64, +} + +#[derive(codec::Decode, codec::Encode, codec::MaxEncodedLen, scale_info::TypeInfo)] +pub struct FinalizedHeaderState { + import_time: u64, + beacon_block_root: H256, + beacon_slot: beacon::Slot, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use beacon::{ + light_client_bootstrap::LightClientBootstrap, + light_client_update::{ + LightClientUpdate, CURRENT_SYNC_COMMITTEE_INDEX, CURRENT_SYNC_COMMITTEE_PROOF_LEN, + FINALIZED_ROOT_INDEX, FINALIZED_ROOT_PROOF_LEN, NEXT_SYNC_COMMITTEE_INDEX, + NEXT_SYNC_COMMITTEE_PROOF_LEN, + }, + BeaconBlockHeader, BlindedBeaconBlock, ChainSpec, Epoch, EthSpec, ExecPayload, + ExecutionPayloadHeader, Hash256, LightClientFinalityUpdate, LightClientOptimisticUpdate, + Slot, SyncAggregate, SyncCommittee, + }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::DispatchError; + use tree_hash::TreeHash; + + use bridge_types::{beacon::ForkVersion, network_config::NetworkConfig, traits::Verifier}; + use bridge_types::{ + types::{Message, Proof}, + EVMChainId, + }; + use ethereum_primitives::{Log, Receipt}; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type EthSpec: EthSpec; + type TimeProvider: UnixTime; + type WeightInfo: WeightInfo; + type WeakSubjectivityPeriodSeconds: Get; + type MaxFinalizedHeaderSlotArray: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + BeaconHeaderImported { block_hash: H256, slot: Slot }, + ExecutionHeaderImported { block_hash: H256, block_number: u64 }, + SyncCommitteeUpdated { period: u64 }, + } + + #[pallet::error] + pub enum Error { + SyncCommitteeMissing, + SyncCommitteeParticipantsNotSupermajority, + InvalidHeaderMerkleProof, + InvalidSyncCommitteeMerkleProof, + SignatureVerificationFailed, + HeaderNotFinalized, + MissingHeader, + InvalidProof, + DecodeFailed, + BridgeBlocked, + InvalidSyncCommitteeHeaderUpdate, + InvalidSyncCommitteePeriodUpdateWithGap, + InvalidSyncCommitteePeriodUpdateWithDuplication, + InvalidFinalizedHeaderUpdate, + InvalidFinalizedPeriodUpdate, + InvalidExecutionHeaderUpdate, + FinalizedBeaconHeaderSlotsExceeded, + WrongBlockBodyHashTreeRoot, + ArithError, + NetworkNotInitialized, + InvalidPublicKeyBytes, + WrongConsensus, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::storage] + pub(super) type FinalizedBeaconHeaders = + StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, BeaconBlockHeader, OptionQuery>; + + #[pallet::storage] + pub(super) type FinalizedBeaconHeaderSlots = StorageMap< + _, + Identity, + EVMChainId, + BoundedVec, + OptionQuery, + >; + + #[pallet::storage] + pub(super) type FinalizedBeaconHeadersBlockRoot = + StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, H256, OptionQuery>; + + #[pallet::storage] + pub(super) type ExecutionHeaders = StorageDoubleMap< + _, + Identity, + EVMChainId, + Identity, + H256, + ExecutionPayloadHeader, + OptionQuery, + >; + + /// Current sync committee corresponding to the active header. + /// TODO prune older sync committees than xxx + #[pallet::storage] + pub(super) type SyncCommittees = StorageDoubleMap< + _, + Identity, + EVMChainId, + Identity, + u64, + SyncCommittee, + OptionQuery, + >; + + #[pallet::storage] + pub(super) type ValidatorsRoot = + StorageMap<_, Identity, EVMChainId, H256, OptionQuery>; + + #[pallet::storage] + pub(super) type LatestFinalizedHeaderState = + StorageMap<_, Identity, EVMChainId, FinalizedHeaderState, OptionQuery>; + + #[pallet::storage] + pub(super) type LatestExecutionHeaderState = + StorageMap<_, Identity, EVMChainId, ExecutionHeaderState, OptionQuery>; + + #[pallet::storage] + pub(super) type LatestSyncCommitteePeriod = + StorageMap<_, Identity, EVMChainId, u64, OptionQuery>; + + #[pallet::storage] + pub(super) type NetworkConfigs = + StorageMap<_, Identity, EVMChainId, NetworkConfig, OptionQuery>; + + #[pallet::storage] + pub(super) type Blocked = StorageMap<_, Identity, EVMChainId, bool, ValueQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::weight(T::WeightInfo::initialize())] + #[transactional] + pub fn initialize( + origin: OriginFor, + chain_id: EVMChainId, + bootstrap: LightClientBootstrap, + validators_root: H256, + ) -> DispatchResult { + ensure_root(origin)?; + + if let Err(err) = Self::initial_sync(chain_id, bootstrap, validators_root) { + log::error!( + target: "ethereum-beacon-client", + "💫 Sync committee period update failed with error {:?}", + err + ); + return Err(err); + } + Self::check_network_config(chain_id)?; + + log::info!( + target: "ethereum-beacon-client", + "💫 Network initialized", + ); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::sync_committee_period_update())] + #[transactional] + pub fn sync_committee_period_update( + origin: OriginFor, + chain_id: EVMChainId, + update: LightClientUpdate, + ) -> DispatchResult { + let _sender = ensure_signed(origin)?; + + Self::check_bridge_blocked_state(chain_id)?; + Self::check_network_config(chain_id)?; + + let signature_epoch = update.signature_slot.epoch_with_spec::(); + let sync_committee_period = signature_epoch + .sync_committee_period_with_spec::() + .map_err(|_| Error::::ArithError)?; + log::info!( + target: "ethereum-beacon-client", + "💫 Received sync committee update for period {}. Applying update", + sync_committee_period + ); + + if let Err(err) = Self::process_sync_committee_period_update(chain_id, update) { + log::error!( + target: "ethereum-beacon-client", + "💫 Sync committee period update failed with error {:?}", + err + ); + return Err(err); + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Sync committee period update for period {} succeeded.", + sync_committee_period + ); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::import_finalized_header())] + #[transactional] + pub fn import_finalized_header( + origin: OriginFor, + chain_id: EVMChainId, + finalized_header_update: LightClientFinalityUpdate, + ) -> DispatchResult { + let _sender = ensure_signed(origin)?; + + Self::check_bridge_blocked_state(chain_id)?; + Self::check_network_config(chain_id)?; + + let slot = finalized_header_update.finalized_header.slot; + + log::info!( + target: "ethereum-beacon-client", + "💫 Received finalized header for slot {}.", + slot + ); + + if let Err(err) = Self::process_finalized_header(chain_id, finalized_header_update) { + log::error!( + target: "ethereum-beacon-client", + "💫 Finalized header update failed with error {:?}", + err + ); + return Err(err); + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Stored finalized beacon header at slot {}.", + slot + ); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::import_execution_header())] + #[transactional] + pub fn import_execution_header( + origin: OriginFor, + chain_id: EVMChainId, + update: LightClientOptimisticUpdate, + block: BlindedBeaconBlock, + ) -> DispatchResult { + let _sender = ensure_signed(origin)?; + + Self::check_bridge_blocked_state(chain_id)?; + Self::check_network_config(chain_id)?; + + let slot = update.attested_header.slot; + let block_hash = update.attested_header.body_root; + + log::info!( + target: "ethereum-beacon-client", + "💫 Received header update for slot {}.", + slot + ); + + if let Err(err) = Self::process_header(chain_id, update, block) { + log::error!( + target: "ethereum-beacon-client", + "💫 Header update failed with error {:?}", + err + ); + return Err(err); + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Stored execution header {} at beacon slot {}.", + block_hash, + slot + ); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::unblock_bridge())] + #[transactional] + pub fn unblock_bridge(origin: OriginFor, chain_id: EVMChainId) -> DispatchResult { + let _sender = ensure_root(origin)?; + + >::set(chain_id, false); + + log::info!(target: "ethereum-beacon-client","💫 syncing bridge from governance provided checkpoint."); + + Ok(()) + } + } + + impl Pallet { + fn process_initial_sync( + chain_id: EVMChainId, + initial_sync: LightClientBootstrap, + validators_root: H256, + ) -> DispatchResult { + // initial_sync + Self::verify_sync_committee( + &initial_sync.current_sync_committee, + initial_sync.current_sync_committee_branch.into(), + initial_sync.header.state_root, + CURRENT_SYNC_COMMITTEE_PROOF_LEN, + CURRENT_SYNC_COMMITTEE_INDEX, + )?; + + let period = Self::compute_current_sync_period(initial_sync.header.slot)?; + + let block_root = initial_sync.header.tree_hash_root(); + + Self::store_sync_committee(chain_id, period, initial_sync.current_sync_committee); + Self::store_validators_root(chain_id, validators_root); + Self::store_finalized_header(chain_id, block_root, initial_sync.header)?; + Ok(()) + } + + fn process_sync_committee_period_update( + chain_id: EVMChainId, + update: LightClientUpdate, + ) -> DispatchResult { + ensure!( + update.signature_slot > update.attested_header.slot + && update.attested_header.slot >= update.finalized_header.slot, + Error::::InvalidSyncCommitteeHeaderUpdate + ); + Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; + Self::verify_sync_committee( + &update.next_sync_committee, + update.next_sync_committee_branch.into(), + update.attested_header.state_root, + NEXT_SYNC_COMMITTEE_PROOF_LEN, + NEXT_SYNC_COMMITTEE_INDEX, + )?; + + let block_root = update.finalized_header.tree_hash_root(); + Self::verify_header( + block_root, + update.finality_branch.into(), + update.attested_header.state_root, + FINALIZED_ROOT_PROOF_LEN, + FINALIZED_ROOT_INDEX, + )?; + + let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; + let latest_committee_period = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; + ensure!( + >::contains_key(chain_id, current_period), + Error::::SyncCommitteeMissing + ); + let next_period = current_period + 1; + ensure!( + !>::contains_key(chain_id, next_period), + Error::::InvalidSyncCommitteePeriodUpdateWithDuplication + ); + ensure!( + (next_period == latest_committee_period + 1), + Error::::InvalidSyncCommitteePeriodUpdateWithGap + ); + + let current_sync_committee = + Self::get_sync_committee_for_period(chain_id, current_period)?; + let validators_root = + >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + + Self::verify_signed_header( + chain_id, + update.sync_aggregate, + current_sync_committee, + update.attested_header, + validators_root, + update.signature_slot, + )?; + + Self::store_sync_committee(chain_id, next_period, update.next_sync_committee); + Self::store_finalized_header(chain_id, block_root, update.finalized_header)?; + + Ok(()) + } + + fn process_finalized_header( + chain_id: EVMChainId, + update: LightClientFinalityUpdate, + ) -> DispatchResult { + let last_finalized_header = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; + ensure!( + update.signature_slot > update.attested_header.slot + && update.attested_header.slot >= update.finalized_header.slot + && update.finalized_header.slot > last_finalized_header.beacon_slot, + Error::::InvalidFinalizedHeaderUpdate + ); + let import_time = last_finalized_header.import_time; + let weak_subjectivity_period_check = + import_time + T::WeakSubjectivityPeriodSeconds::get(); + let time: u64 = T::TimeProvider::now().as_secs(); + + log::info!( + target: "ethereum-beacon-client", + "💫 Checking weak subjectivity period. Current time is :{:?} Weak subjectivity period check: {:?}.", + time, + weak_subjectivity_period_check + ); + + if time > weak_subjectivity_period_check { + log::info!(target: "ethereum-beacon-client","💫 Weak subjectivity period exceeded, blocking bridge.",); + >::insert(chain_id, true); + return Err(Error::::BridgeBlocked.into()); + } + + Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; + + let block_root = update.finalized_header.tree_hash_root(); + + Self::verify_header( + block_root, + update.finality_branch.into(), + update.attested_header.state_root, + FINALIZED_ROOT_PROOF_LEN, + FINALIZED_ROOT_INDEX, + )?; + + let last_finalized_period = + Self::compute_current_sync_period(last_finalized_header.beacon_slot)?; + let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; + ensure!( + (current_period == last_finalized_period + || current_period == last_finalized_period + 1), + Error::::InvalidFinalizedPeriodUpdate + ); + let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; + + let validators_root = + >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + Self::verify_signed_header( + chain_id, + update.sync_aggregate, + sync_committee, + update.attested_header, + validators_root, + update.signature_slot, + )?; + + Self::store_finalized_header(chain_id, block_root, update.finalized_header)?; + + Ok(()) + } + + fn body_tree_root_hash(block: &BlindedBeaconBlock) -> H256 { + match block { + BlindedBeaconBlock::Base(block) => block.body.tree_hash_root(), + BlindedBeaconBlock::Altair(block) => block.body.tree_hash_root(), + BlindedBeaconBlock::Merge(block) => block.body.tree_hash_root(), + BlindedBeaconBlock::Capella(block) => block.body.tree_hash_root(), + } + } + + fn process_header( + chain_id: EVMChainId, + update: LightClientOptimisticUpdate, + block: BlindedBeaconBlock, + ) -> DispatchResult { + let last_finalized_header = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; + let latest_finalized_header_slot = last_finalized_header.beacon_slot; + let block_slot = update.attested_header.slot; + ensure!( + block_slot <= latest_finalized_header_slot, + Error::::HeaderNotFinalized + ); + + let execution_header_state = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; + let execution_payload = block + .body() + .execution_payload() + .map_err(|_| Error::::ArithError)? + .to_execution_payload_header(); + ensure!( + execution_payload.block_number() > execution_header_state.block_number, + Error::::InvalidExecutionHeaderUpdate + ); + let body_root_hash = Self::body_tree_root_hash(&block); + + ensure!( + body_root_hash == update.attested_header.body_root, + Error::::WrongBlockBodyHashTreeRoot + ); + + let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; + let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; + + let validators_root = + >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + Self::verify_signed_header( + chain_id, + update.sync_aggregate, + sync_committee, + update.attested_header, + validators_root, + update.signature_slot, + )?; + + Self::store_execution_header(chain_id, execution_payload, block_slot, body_root_hash); + + Ok(()) + } + + fn check_bridge_blocked_state(chain_id: EVMChainId) -> DispatchResult { + if >::get(chain_id) { + return Err(Error::::BridgeBlocked.into()); + } + + Ok(()) + } + + fn check_network_config(chain_id: EVMChainId) -> DispatchResult { + let network_config = + NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + match network_config.consensus() { + ethereum_primitives::network_config::Consensus::Beacon(_config) => Ok(()), + _ => Err(Error::::WrongConsensus.into()), + } + } + + pub(super) fn verify_signed_header( + chain_id: EVMChainId, + sync_aggregate: SyncAggregate, + sync_committee: SyncCommittee, + header: BeaconBlockHeader, + validators_root: H256, + signature_slot: Slot, + ) -> DispatchResult { + let mut participant_pubkeys: Vec = Vec::new(); + // Gathers all the pubkeys of the sync committee members that participated in signing + // the header. + for (bit, pubkey) in sync_aggregate + .sync_committee_bits + .iter() + .zip(sync_committee.pubkeys.iter()) + { + if bit { + participant_pubkeys.push( + pubkey + .decompress() + .map_err(|_| Error::::InvalidPublicKeyBytes)?, + ); + } + } + + let fork = Self::compute_fork_version( + chain_id, + signature_slot.epoch_with_spec::(), + )?; + // Domains are used for for seeds, for signatures, and for selecting aggregators. + let domain = ChainSpec::compute_domain_with_constant( + DOMAIN_SYNC_COMMITTEE, + fork, + validators_root, + ); + // Hash tree root of SigningData - object root + domain + let signing_root = Self::compute_signing_root(header, domain)?; + + // Verify sync committee aggregate signature. + if !sync_aggregate + .sync_committee_signature + .fast_aggregate_verify( + signing_root, + &participant_pubkeys.iter().collect::>(), + ) + { + return Err(Error::::SignatureVerificationFailed.into()); + } + Ok(()) + } + + pub(super) fn compute_signing_root( + beacon_header: BeaconBlockHeader, + domain: Hash256, + ) -> Result { + let beacon_header_root = beacon_header.tree_hash_root(); + let hash_root = beacon::SigningData { + domain, + object_root: beacon_header_root, + } + .tree_hash_root(); + Ok(hash_root) + } + + fn verify_sync_committee( + sync_committee: &SyncCommittee, + sync_committee_branch: Vec, + header_state_root: H256, + depth: usize, + index: usize, + ) -> DispatchResult { + let sync_committee_root = sync_committee.tree_hash_root(); + + ensure!( + Self::is_valid_merkle_branch( + sync_committee_root, + sync_committee_branch, + depth, + index, + header_state_root + ), + Error::::InvalidSyncCommitteeMerkleProof + ); + + Ok(()) + } + + fn verify_header( + block_root: H256, + proof_branch: Vec, + attested_header_state_root: H256, + depth: usize, + index: usize, + ) -> DispatchResult { + ensure!( + Self::is_valid_merkle_branch( + block_root, + proof_branch, + depth, + index, + attested_header_state_root + ), + Error::::InvalidHeaderMerkleProof + ); + + Ok(()) + } + + fn store_sync_committee( + chain_id: EVMChainId, + period: u64, + sync_committee: SyncCommittee, + ) { + >::insert(chain_id, period, sync_committee); + + log::trace!( + target: "ethereum-beacon-client", + "💫 Updated latest sync committee period stored to {}.", + period + ); + + >::insert(chain_id, period); + + Self::deposit_event(Event::SyncCommitteeUpdated { period }); + } + + fn store_finalized_header( + chain_id: EVMChainId, + block_root: Hash256, + header: BeaconBlockHeader, + ) -> DispatchResult { + let slot = header.slot; + + >::insert(chain_id, block_root, header); + Self::add_finalized_header_slot(chain_id, slot)?; + + log::info!( + target: "ethereum-beacon-client", + "💫 Updated latest finalized block root {} at slot {}.", + block_root, + slot + ); + + LatestFinalizedHeaderState::::insert( + chain_id, + FinalizedHeaderState { + import_time: T::TimeProvider::now().as_secs(), + beacon_block_root: block_root, + beacon_slot: slot, + }, + ); + + Self::deposit_event(Event::BeaconHeaderImported { + block_hash: block_root, + slot, + }); + + Ok(()) + } + + fn add_finalized_header_slot(chain_id: EVMChainId, slot: Slot) -> DispatchResult { + >::try_mutate(chain_id, |b_vec| { + let b_vec = b_vec.get_or_insert(Default::default()); + if b_vec.len() as u32 == T::MaxFinalizedHeaderSlotArray::get() { + b_vec.remove(0); + } + b_vec.try_push(slot) + }) + .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; + + Ok(()) + } + + fn store_execution_header( + chain_id: EVMChainId, + header: ExecutionPayloadHeader, + beacon_slot: Slot, + beacon_block_root: H256, + ) { + let block_number = header.block_number(); + let block_hash = header.block_hash().into_root(); + + >::insert(chain_id, block_hash, header); + + log::trace!( + target: "ethereum-beacon-client", + "💫 Updated latest execution block at {} to number {}.", + block_hash, + block_number + ); + + LatestExecutionHeaderState::::insert( + chain_id, + ExecutionHeaderState { + beacon_block_root, + beacon_slot, + block_hash, + block_number, + }, + ); + + Self::deposit_event(Event::ExecutionHeaderImported { + block_hash, + block_number, + }); + } + + fn store_validators_root(chain_id: EVMChainId, validators_root: H256) { + >::insert(chain_id, validators_root); + } + + pub(super) fn compute_current_sync_period(slot: Slot) -> Result> { + let period = slot + .epoch_with_spec::() + .sync_committee_period_with_spec::() + .map_err(|_| Error::::ArithError)?; + Ok(period) + } + + pub(super) fn is_valid_merkle_branch( + leaf: H256, + branch: Vec, + depth: usize, + index: usize, + root: H256, + ) -> bool { + if branch.len() != depth { + log::error!(target: "ethereum-beacon-client", "Merkle proof branch length doesn't match depth."); + + return false; + } + let mut value = leaf; + if leaf.as_bytes().len() < 32 as usize { + log::error!(target: "ethereum-beacon-client", "Merkle proof leaf not 32 bytes."); + + return false; + } + for i in 0..depth { + if branch[i as usize].as_bytes().len() < 32 as usize { + log::error!(target: "ethereum-beacon-client", "Merkle proof branch not 32 bytes."); + + return false; + } + if (index / 2usize.pow(i as u32) % 2) == 0 { + // left node + let mut data = [0u8; 64]; + data[0..32].copy_from_slice(&(value.0)); + data[32..64].copy_from_slice(&(branch[i as usize].0)); + value = sha2_256(&data).into(); + } else { + let mut data = [0u8; 64]; // right node + data[0..32].copy_from_slice(&(branch[i as usize].0)); + data[32..64].copy_from_slice(&(value.0)); + value = sha2_256(&data).into(); + } + } + + return value == root; + } + + pub(super) fn sync_committee_participation_is_supermajority( + sync_agg: &SyncAggregate, + ) -> DispatchResult { + let sync_committee_sum = sync_agg.sync_committee_bits.num_set_bits(); + let sync_committee_len = sync_agg.sync_committee_bits.len(); + ensure!( + (sync_committee_sum * 3 >= sync_committee_len * 2), + Error::::SyncCommitteeParticipantsNotSupermajority + ); + + Ok(()) + } + + pub(super) fn get_sync_committee_for_period( + chain_id: EVMChainId, + period: u64, + ) -> Result, DispatchError> { + let sync_committee = >::get(chain_id, period); + + if let Some(sync_committee) = sync_committee { + Ok(sync_committee) + } else { + log::error!(target: "ethereum-beacon-client", "💫 Sync committee for period {} missing", period); + return Err(Error::::SyncCommitteeMissing.into()); + } + } + + pub(super) fn compute_fork_version( + chain_id: EVMChainId, + epoch: Epoch, + ) -> Result { + let network_config = + NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + match network_config.consensus() { + ethereum_primitives::network_config::Consensus::Beacon(config) => { + Ok(config.fork_version_from_epoch(epoch.as_u64())) + } + _ => Err(Error::::WrongConsensus.into()), + } + } + + pub(super) fn initial_sync( + chain_id: EVMChainId, + initial_sync: LightClientBootstrap, + validators_root: H256, + ) -> DispatchResult { + log::info!( + target: "ethereum-beacon-client", + "💫 Received initial sync, starting processing.", + ); + + if let Err(err) = Self::process_initial_sync(chain_id, initial_sync, validators_root) { + log::error!( + target: "ethereum-beacon-client", + "Initial sync failed with error {:?}", + err + ); + return Err(err); + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Initial sync processing succeeded.", + ); + + Ok(()) + } + + // Verifies that the receipt encoded in proof.data is included + // in the block given by proof.block_hash. Inclusion is only + // recognized if the block has been finalized. + fn verify_receipt_inclusion( + stored_header: ExecutionPayloadHeader, + proof: &Proof, + ) -> Result { + let result = ethereum_primitives::Header::check_receipt_proof_with_root( + stored_header.receipts_root(), + &proof.data, + ) + .ok_or(Error::::InvalidProof)?; + + match result { + Ok(receipt) => Ok(receipt), + Err(err) => { + log::trace!( + target: "ethereum-beacon-client", + "💫 Failed to decode transaction receipt: {}", + err + ); + Err(Error::::InvalidProof.into()) + } + } + } + } + + impl Verifier for Pallet { + type Result = (Log, u64); + /// Verify a message by verifying the existence of the corresponding + /// Ethereum log in a block. Returns the log if successful. + fn verify(chain_id: EVMChainId, message: &Message) -> Result<(Log, u64), DispatchError> { + log::info!( + target: "ethereum-beacon-client", + "💫 Verifying message with block hash {}", + message.proof.block_hash, + ); + + let stored_header = >::get(chain_id, message.proof.block_hash) + .ok_or(Error::::MissingHeader)?; + + let block_number = stored_header.block_number(); + + let receipt = match Self::verify_receipt_inclusion(stored_header, &message.proof) { + Ok(receipt) => receipt, + Err(err) => { + log::error!( + target: "ethereum-beacon-client", + "💫 Verify receipt inclusion failed for block {}: {:?}", + message.proof.block_hash, + err + ); + return Err(err); + } + }; + + log::trace!( + target: "ethereum-beacon-client", + "💫 Verified receipt inclusion for transaction at index {} in block {}", + message.proof.tx_index, message.proof.block_hash, + ); + + let log = match rlp::decode(&message.data) { + Ok(log) => log, + Err(err) => { + log::error!( + target: "ethereum-beacon-client", + "💫 RLP log decoded failed {}: {:?}", + message.proof.block_hash, + err + ); + return Err(Error::::DecodeFailed.into()); + } + }; + + if !receipt.contains_log(&log) { + log::error!( + target: "ethereum-beacon-client", + "💫 Event log not found in receipt for transaction at index {} in block {}", + message.proof.tx_index, message.proof.block_hash, + ); + return Err(Error::::InvalidProof.into()); + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Receipt verification successful for {}", + message.proof.block_hash, + ); + + Ok((log, block_number)) + } + } +} diff --git a/pallets/ethereum-beacon-client/src/weights.rs b/pallets/ethereum-beacon-client/src/weights.rs new file mode 100644 index 00000000..8947f7e5 --- /dev/null +++ b/pallets/ethereum-beacon-client/src/weights.rs @@ -0,0 +1,54 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for ethereum_beacon_client. +pub trait WeightInfo { + fn initialize() -> Weight; + fn sync_committee_period_update() -> Weight; + fn import_finalized_header() -> Weight; + fn import_execution_header() -> Weight; + fn unblock_bridge() -> Weight; +} + +/// Weights for ethereum_beacon_client using the Snowbridge node and recommended hardware. +pub struct SnowbridgeWeight(PhantomData); +impl WeightInfo for SnowbridgeWeight { + fn initialize() -> Weight { + Default::default() + } + fn sync_committee_period_update() -> Weight { + Default::default() + } + fn import_finalized_header() -> Weight { + Default::default() + } + fn import_execution_header() -> Weight { + Default::default() + } + fn unblock_bridge() -> Weight { + Default::default() + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn initialize() -> Weight { + Default::default() + } + fn sync_committee_period_update() -> Weight { + Default::default() + } + fn import_finalized_header() -> Weight { + Default::default() + } + fn import_execution_header() -> Weight { + Default::default() + } + fn unblock_bridge() -> Weight { + Default::default() + } +} diff --git a/pallets/types/Cargo.toml b/pallets/types/Cargo.toml index 51e30a2c..e1001daa 100644 --- a/pallets/types/Cargo.toml +++ b/pallets/types/Cargo.toml @@ -22,7 +22,6 @@ hex = { version = "0.4", default-features = false } libsecp256k1 = { version = "0.7", default-features = false } rlp = { version = "0.5", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } -getrandom = { version = "0.2.8", features = ["js"], default-features = false } frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } @@ -66,7 +65,6 @@ std = [ "beefy-primitives/std", "ethereum-primitives/std", "xcm/std", - "getrandom/std", ] runtime-benchmarks = [ diff --git a/primitives/beacon/Cargo.toml b/primitives/beacon/Cargo.toml new file mode 100644 index 00000000..5cfc41b9 --- /dev/null +++ b/primitives/beacon/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "beacon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +eth2_ssz = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +eth2_ssz_derive = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.3.1" } +superstruct = "0.7.0" +tree_hash = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +tree_hash_derive = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.0" } +codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2", default-features = false, features = ["derive"] } +bls = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.0", default-features = false, features = ["milagro"] } +eth2_serde_utils = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.1.1", default-features = false } +serde = { version = "1.0.152", features = ["derive"], default-features = false } +ethereum-types = { version = "0.14.1", default-features = false, features = [ + "codec", + "rlp", + "serialize", +] } +derivative = { version = "2.2.0", features = ["use_core"] } +bytes = { version = "1.4.0", default-features = false } +eth2_ssz_types = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.2", default-features = false } +serde_json = { version = "1.0.94", optional = true } +serde-big-array = "0.5.1" +serde_with = { version = "2.2.0", default-features = false, features = ["alloc", "macros"] } +eth2_hashing = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.3.0", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +maplit = "1.0.2" + +[dev-dependencies] +itertools = "0.10.5" +serde_yaml = "0.9.19" +tempfile = "3.4.0" + +[features] +default = ["std"] +std = ["eth2_ssz/std", "tree_hash/std", "codec/std", "scale-info/std", "bls/std", "eth2_serde_utils/std", "ethereum-types/std", "bytes/std", "eth2_ssz_types/std", "serde_json", "eth2_hashing/std"] diff --git a/primitives/beacon/src/aggregate_and_proof.rs b/primitives/beacon/src/aggregate_and_proof.rs new file mode 100644 index 00000000..51b11d16 --- /dev/null +++ b/primitives/beacon/src/aggregate_and_proof.rs @@ -0,0 +1,93 @@ +use super::{ + Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, SelectionProof, + Signature, SignedRoot, +}; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A Validators aggregate attestation and selection proof. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct AggregateAndProof { + /// The index of the validator that created the attestation. + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub aggregator_index: u64, + /// The aggregate attestation. + pub aggregate: Attestation, + /// A proof provided by the validator that permits them to publish on the + /// `beacon_aggregate_and_proof` gossipsub topic. + pub selection_proof: Signature, +} + +impl AggregateAndProof { + /// Produces a new `AggregateAndProof` with a `selection_proof` generated by signing + /// `aggregate.data.slot` with `secret_key`. + /// + /// If `selection_proof.is_none()` it will be computed locally. + pub fn from_aggregate( + aggregator_index: u64, + aggregate: Attestation, + selection_proof: Option, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let selection_proof = selection_proof + .unwrap_or_else(|| { + SelectionProof::new::( + aggregate.data.slot, + secret_key, + fork, + genesis_validators_root, + spec, + ) + }) + .into(); + + Self { + aggregator_index, + aggregate, + selection_proof, + } + } + + /// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`. + pub fn is_valid_selection_proof( + &self, + validator_pubkey: &PublicKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> bool { + let target_epoch = self.aggregate.data.slot.epoch(T::slots_per_epoch()); + let domain = spec.get_domain( + target_epoch, + Domain::SelectionProof, + fork, + genesis_validators_root, + ); + let message = self.aggregate.data.slot.signing_root(domain); + self.selection_proof.verify(validator_pubkey, message) + } +} + +impl SignedRoot for AggregateAndProof {} diff --git a/primitives/beacon/src/application_domain.rs b/primitives/beacon/src/application_domain.rs new file mode 100644 index 00000000..4d27bb25 --- /dev/null +++ b/primitives/beacon/src/application_domain.rs @@ -0,0 +1,18 @@ +/// This value is an application index of 0 with the bitmask applied (so it's equivalent to the bit mask). +/// Little endian hex: 0x00000001, Binary: 1000000000000000000000000 +pub const APPLICATION_DOMAIN_BUILDER: u32 = 16777216; +#[cfg(not(feature = "std"))] +use crate::prelude::*; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum ApplicationDomain { + Builder, +} + +impl ApplicationDomain { + pub fn get_domain_constant(&self) -> u32 { + match self { + ApplicationDomain::Builder => APPLICATION_DOMAIN_BUILDER, + } + } +} diff --git a/primitives/beacon/src/attestation.rs b/primitives/beacon/src/attestation.rs new file mode 100644 index 00000000..3909a1ac --- /dev/null +++ b/primitives/beacon/src/attestation.rs @@ -0,0 +1,120 @@ +use crate::prelude::*; +use crate::safe_arith::ArithError; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +use crate::slot_data::SlotData; +use crate::{Hash256, Slot}; + +use super::{ + AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey, + Signature, SignedRoot, +}; + +#[derive(Debug, PartialEq)] +pub enum Error { + SszTypesError(ssz_types::Error), + AlreadySigned(usize), + SubnetCountIsZero(ArithError), +} + +/// Details an attestation that can be slashable. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct Attestation { + pub aggregation_bits: BitList, + pub data: AttestationData, + pub signature: AggregateSignature, +} + +impl Attestation { + /// Are the aggregation bitfields of these attestations disjoint? + pub fn signers_disjoint_from(&self, other: &Self) -> bool { + self.aggregation_bits + .intersection(&other.aggregation_bits) + .is_zero() + } + + /// Aggregate another Attestation into this one. + /// + /// The aggregation bitfields must be disjoint, and the data must be the same. + pub fn aggregate(&mut self, other: &Self) { + debug_assert_eq!(self.data, other.data); + debug_assert!(self.signers_disjoint_from(other)); + + self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); + self.signature.add_assign_aggregate(&other.signature); + } + + /// Signs `self`, setting the `committee_position`'th bit of `aggregation_bits` to `true`. + /// + /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. + pub fn sign( + &mut self, + secret_key: &SecretKey, + committee_position: usize, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let domain = spec.get_domain( + self.data.target.epoch, + Domain::BeaconAttester, + fork, + genesis_validators_root, + ); + let message = self.data.signing_root(domain); + + self.add_signature(&secret_key.sign(message), committee_position) + } + + /// Adds `signature` to `self` and sets the `committee_position`'th bit of `aggregation_bits` to `true`. + /// + /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. + pub fn add_signature( + &mut self, + signature: &Signature, + committee_position: usize, + ) -> Result<(), Error> { + if self + .aggregation_bits + .get(committee_position) + .map_err(Error::SszTypesError)? + { + Err(Error::AlreadySigned(committee_position)) + } else { + self.aggregation_bits + .set(committee_position, true) + .map_err(Error::SszTypesError)?; + + self.signature.add_assign(signature); + + Ok(()) + } + } +} + +impl SlotData for Attestation { + fn get_slot(&self) -> Slot { + self.data.slot + } +} diff --git a/primitives/beacon/src/attestation_data.rs b/primitives/beacon/src/attestation_data.rs new file mode 100644 index 00000000..3d0e9cda --- /dev/null +++ b/primitives/beacon/src/attestation_data.rs @@ -0,0 +1,48 @@ +use crate::{Checkpoint, Hash256, SignedRoot, Slot}; + +use crate::prelude::*; +use crate::slot_data::SlotData; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data upon which an attestation is based. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + Hash, + Encode, + Decode, + TreeHash, + Default, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct AttestationData { + pub slot: Slot, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub index: u64, + + // LMD GHOST vote + pub beacon_block_root: Hash256, + + // FFG Vote + pub source: Checkpoint, + pub target: Checkpoint, +} + +impl SignedRoot for AttestationData {} + +impl SlotData for AttestationData { + fn get_slot(&self) -> Slot { + self.slot + } +} diff --git a/primitives/beacon/src/attestation_duty.rs b/primitives/beacon/src/attestation_duty.rs new file mode 100644 index 00000000..5e62a27b --- /dev/null +++ b/primitives/beacon/src/attestation_duty.rs @@ -0,0 +1,19 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize)] +pub struct AttestationDuty { + /// The slot during which the attester must attest. + pub slot: Slot, + /// The index of this committee within the committees in `slot`. + pub index: CommitteeIndex, + /// The position of the attester within the committee. + pub committee_position: usize, + /// The total number of attesters in the committee. + pub committee_len: usize, + /// The committee count at `attestation_slot`. + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub committees_at_slot: u64, +} diff --git a/primitives/beacon/src/attester_slashing.rs b/primitives/beacon/src/attester_slashing.rs new file mode 100644 index 00000000..4bac6be9 --- /dev/null +++ b/primitives/beacon/src/attester_slashing.rs @@ -0,0 +1,32 @@ +use crate::{EthSpec, IndexedAttestation}; + +use crate::prelude::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Two conflicting attestations. +/// +/// Spec v0.12.1 +#[derive( + Derivative, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Eq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct AttesterSlashing { + pub attestation_1: IndexedAttestation, + pub attestation_2: IndexedAttestation, +} diff --git a/primitives/beacon/src/beacon_block.rs b/primitives/beacon/src/beacon_block.rs new file mode 100644 index 00000000..a116f037 --- /dev/null +++ b/primitives/beacon/src/beacon_block.rs @@ -0,0 +1,726 @@ +use crate::beacon_block_body::{ + BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyMerge, BeaconBlockBodyRef, + BeaconBlockBodyRefMut, +}; +use crate::prelude::*; +use crate::*; +use bls::Signature; +use core::marker::PhantomData; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError}; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +/// A block of the `BeaconChain`. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: AbstractExecPayload")), + serde( + bound = "T: EthSpec, Payload: AbstractExecPayload", + deny_unknown_fields + ), + scale_info(skip_type_params(T)) + ), + ref_attributes( + derive(Debug, PartialEq, TreeHash), + tree_hash(enum_behaviour = "transparent") + ), + map_ref_into(BeaconBlockBodyRef, BeaconBlock), + map_ref_mut_into(BeaconBlockBodyRefMut) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct BeaconBlock = FullPayload> { + #[superstruct(getter(copy))] + pub slot: Slot, + #[superstruct(getter(copy))] + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_index: u64, + #[superstruct(getter(copy))] + pub parent_root: Hash256, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(only(Base), partial_getter(rename = "body_base"))] + pub body: BeaconBlockBodyBase, + #[superstruct(only(Altair), partial_getter(rename = "body_altair"))] + pub body: BeaconBlockBodyAltair, + #[superstruct(only(Merge), partial_getter(rename = "body_merge"))] + pub body: BeaconBlockBodyMerge, + #[superstruct(only(Capella), partial_getter(rename = "body_capella"))] + pub body: BeaconBlockBodyCapella, +} + +pub type BlindedBeaconBlock = BeaconBlock>; + +impl> SignedRoot for BeaconBlock {} +impl<'a, T: EthSpec, Payload: AbstractExecPayload> SignedRoot + for BeaconBlockRef<'a, T, Payload> +{ +} + +/// Empty block trait for each block variant to implement. +pub trait EmptyBlock { + /// Returns an empty block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self; +} + +impl> BeaconBlock { + /// Returns an empty block to be used during genesis. + pub fn empty(spec: &ChainSpec) -> Self { + map_fork_name!( + spec.fork_name_at_epoch(T::genesis_epoch()), + Self, + EmptyBlock::empty(spec) + ) + } + + /// Custom SSZ decoder that takes a `ChainSpec` as context. + pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { + let slot_len = ::ssz_fixed_len(); + let slot_bytes = bytes + .get(0..slot_len) + .ok_or(DecodeError::InvalidByteLength { + len: bytes.len(), + expected: slot_len, + })?; + + let slot = Slot::from_ssz_bytes(slot_bytes)?; + let fork_at_slot = spec.fork_name_at_slot::(slot); + + Ok(map_fork_name!( + fork_at_slot, + Self, + <_>::from_ssz_bytes(bytes)? + )) + } + + /// Try decoding each beacon block variant in sequence. + /// + /// This is *not* recommended unless you really have no idea what variant the block should be. + /// Usually it's better to prefer `from_ssz_bytes` which will decode the correct variant based + /// on the fork slot. + pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { + BeaconBlockCapella::from_ssz_bytes(bytes) + .map(BeaconBlock::Capella) + .or_else(|_| BeaconBlockMerge::from_ssz_bytes(bytes).map(BeaconBlock::Merge)) + .or_else(|_| BeaconBlockAltair::from_ssz_bytes(bytes).map(BeaconBlock::Altair)) + .or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base)) + } + + /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. + pub fn body(&self) -> BeaconBlockBodyRef<'_, T, Payload> { + self.to_ref().body() + } + + /// Convenience accessor for the `body` as a `BeaconBlockBodyRefMut`. + pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T, Payload> { + self.to_mut().body_mut() + } + + /// Returns the epoch corresponding to `self.slot()`. + pub fn epoch(&self) -> Epoch { + self.slot().epoch(T::slots_per_epoch()) + } + + /// Returns the `tree_hash_root` of the block. + pub fn canonical_root(&self) -> Hash256 { + self.tree_hash_root() + } + + /// Returns a full `BeaconBlockHeader` of this block. + /// + /// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block + /// when you want to have the block _and_ the header. + /// + /// Note: performs a full tree-hash of `self.body`. + pub fn block_header(&self) -> BeaconBlockHeader { + self.to_ref().block_header() + } + + /// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`. + pub fn temporary_block_header(&self) -> BeaconBlockHeader { + self.to_ref().temporary_block_header() + } + + /// Return the tree hash root of the block's body. + pub fn body_root(&self) -> Hash256 { + self.to_ref().body_root() + } + + /// Signs `self`, producing a `SignedBeaconBlock`. + pub fn sign( + self, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedBeaconBlock { + let domain = spec.get_domain( + self.epoch(), + Domain::BeaconProposer, + fork, + genesis_validators_root, + ); + let message = self.signing_root(domain); + let signature = secret_key.sign(message); + SignedBeaconBlock::from_block(self, signature) + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, T, Payload> { + /// Returns the name of the fork pertaining to `self`. + /// + /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork + /// dictated by `self.slot()`. + pub fn fork_name(&self, spec: &ChainSpec) -> Result { + let fork_at_slot = spec.fork_name_at_slot::(self.slot()); + let object_fork = match self { + BeaconBlockRef::Base { .. } => ForkName::Base, + BeaconBlockRef::Altair { .. } => ForkName::Altair, + BeaconBlockRef::Merge { .. } => ForkName::Merge, + BeaconBlockRef::Capella { .. } => ForkName::Capella, + }; + + if fork_at_slot == object_fork { + Ok(object_fork) + } else { + Err(InconsistentFork { + fork_at_slot, + object_fork, + }) + } + } + + /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. + pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> { + map_beacon_block_ref_into_beacon_block_body_ref!(&'a _, *self, |block, cons| cons( + &block.body + )) + } + + /// Return the tree hash root of the block's body. + pub fn body_root(&self) -> Hash256 { + map_beacon_block_ref!(&'a _, *self, |block, cons| { + let _: Self = cons(block); + block.body.tree_hash_root() + }) + } + + /// Returns the epoch corresponding to `self.slot()`. + pub fn epoch(&self) -> Epoch { + self.slot().epoch(T::slots_per_epoch()) + } + + /// Returns a full `BeaconBlockHeader` of this block. + pub fn block_header(&self) -> BeaconBlockHeader { + BeaconBlockHeader { + slot: self.slot(), + proposer_index: self.proposer_index(), + parent_root: self.parent_root(), + state_root: self.state_root(), + body_root: self.body_root(), + } + } + + /// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`. + pub fn temporary_block_header(self) -> BeaconBlockHeader { + BeaconBlockHeader { + state_root: Hash256::zero(), + ..self.block_header() + } + } + + /// Extracts a reference to an execution payload from a block, returning an error if the block + /// is pre-merge. + pub fn execution_payload(&self) -> Result, Error> { + self.body().execution_payload() + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRefMut<'a, T, Payload> { + /// Convert a mutable reference to a beacon block to a mutable ref to its body. + pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T, Payload> { + map_beacon_block_ref_mut_into_beacon_block_body_ref_mut!(&'a _, self, |block, cons| cons( + &mut block.body + )) + } +} + +impl> EmptyBlock for BeaconBlockBase { + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockBase { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyBase { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + _phantom: PhantomData, + }, + } + } +} + +impl> BeaconBlockBase { + /// Return a block where the block has maximum size. + pub fn full(spec: &ChainSpec) -> Self { + let header = BeaconBlockHeader { + slot: Slot::new(1), + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body_root: Hash256::zero(), + }; + + let signed_header = SignedBeaconBlockHeader { + message: header, + signature: Signature::empty(), + }; + let indexed_attestation: IndexedAttestation = IndexedAttestation { + attesting_indices: VariableList::new(vec![ + 0_u64; + T::MaxValidatorsPerCommittee::to_usize() + ]) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + }; + + let deposit_data = DepositData { + pubkey: PublicKeyBytes::empty(), + withdrawal_credentials: Hash256::zero(), + amount: 0, + signature: SignatureBytes::empty(), + }; + let proposer_slashing = ProposerSlashing { + signed_header_1: signed_header.clone(), + signed_header_2: signed_header, + }; + + let attester_slashing = AttesterSlashing { + attestation_1: indexed_attestation.clone(), + attestation_2: indexed_attestation, + }; + + let attestation: Attestation = Attestation { + aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize()) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + }; + + let deposit = Deposit { + proof: FixedVector::from_elem(Hash256::zero()), + data: deposit_data, + }; + + let voluntary_exit = VoluntaryExit { + epoch: Epoch::new(1), + validator_index: 1, + }; + + let signed_voluntary_exit = SignedVoluntaryExit { + message: voluntary_exit, + signature: Signature::empty(), + }; + + let mut block = BeaconBlockBase::::empty(spec); + for _ in 0..T::MaxProposerSlashings::to_usize() { + block + .body + .proposer_slashings + .push(proposer_slashing.clone()) + .unwrap(); + } + for _ in 0..T::MaxDeposits::to_usize() { + block.body.deposits.push(deposit.clone()).unwrap(); + } + for _ in 0..T::MaxVoluntaryExits::to_usize() { + block + .body + .voluntary_exits + .push(signed_voluntary_exit.clone()) + .unwrap(); + } + for _ in 0..T::MaxAttesterSlashings::to_usize() { + block + .body + .attester_slashings + .push(attester_slashing.clone()) + .unwrap(); + } + + for _ in 0..T::MaxAttestations::to_usize() { + block.body.attestations.push(attestation.clone()).unwrap(); + } + block + } +} + +impl> EmptyBlock for BeaconBlockAltair { + /// Returns an empty Altair block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockAltair { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyAltair { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + sync_aggregate: SyncAggregate::empty(), + _phantom: PhantomData, + }, + } + } +} + +impl> BeaconBlockAltair { + /// Return an Altair block where the block has maximum size. + pub fn full(spec: &ChainSpec) -> Self { + let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); + let sync_aggregate = SyncAggregate { + sync_committee_signature: AggregateSignature::empty(), + sync_committee_bits: BitVector::default(), + }; + BeaconBlockAltair { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyAltair { + proposer_slashings: base_block.body.proposer_slashings, + attester_slashings: base_block.body.attester_slashings, + attestations: base_block.body.attestations, + deposits: base_block.body.deposits, + voluntary_exits: base_block.body.voluntary_exits, + sync_aggregate, + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + _phantom: PhantomData, + }, + } + } +} + +impl> EmptyBlock for BeaconBlockMerge { + /// Returns an empty Merge block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockMerge { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyMerge { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + sync_aggregate: SyncAggregate::empty(), + execution_payload: Payload::Merge::default(), + }, + } + } +} + +impl> BeaconBlockCapella { + /// Return a Capella block where the block has maximum size. + pub fn full(spec: &ChainSpec) -> Self { + let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); + let bls_to_execution_changes = vec![ + SignedBlsToExecutionChange { + message: BlsToExecutionChange { + validator_index: 0, + from_bls_pubkey: PublicKeyBytes::empty(), + to_execution_address: Address::zero(), + }, + signature: Signature::empty() + }; + T::max_bls_to_execution_changes() + ] + .into(); + let sync_aggregate = SyncAggregate { + sync_committee_signature: AggregateSignature::empty(), + sync_committee_bits: BitVector::default(), + }; + BeaconBlockCapella { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyCapella { + proposer_slashings: base_block.body.proposer_slashings, + attester_slashings: base_block.body.attester_slashings, + attestations: base_block.body.attestations, + deposits: base_block.body.deposits, + voluntary_exits: base_block.body.voluntary_exits, + bls_to_execution_changes, + sync_aggregate, + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + execution_payload: Payload::Capella::default(), + }, + } + } +} + +impl> EmptyBlock for BeaconBlockCapella { + /// Returns an empty Capella block to be used during genesis. + fn empty(spec: &ChainSpec) -> Self { + BeaconBlockCapella { + slot: spec.genesis_slot, + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body: BeaconBlockBodyCapella { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + sync_aggregate: SyncAggregate::empty(), + execution_payload: Payload::Capella::default(), + bls_to_execution_changes: VariableList::empty(), + }, + } + } +} + +// We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. +impl From>> + for BeaconBlockBase> +{ + fn from(block: BeaconBlockBase>) -> Self { + let BeaconBlockBase { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockBase { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + +impl From>> + for BeaconBlockAltair> +{ + fn from(block: BeaconBlockAltair>) -> Self { + let BeaconBlockAltair { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockAltair { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + +// We can convert blocks with payloads to blocks without payloads, and an optional payload. +macro_rules! impl_from { + ($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>, $body_expr:expr) => { + impl From<$ty_name<$($from_params),*>> + for ($ty_name<$($to_params),*>, Option>) + { + #[allow(clippy::redundant_closure_call)] + fn from(block: $ty_name<$($from_params),*>) -> Self { + let $ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + let (body, payload) = ($body_expr)(body); + + ($ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + }, payload.map(Into::into)) + } + } + } +} + +impl_from!(BeaconBlockBase, >, >, |body: BeaconBlockBodyBase<_, _>| body.into()); +impl_from!(BeaconBlockAltair, >, >, |body: BeaconBlockBodyAltair<_, _>| body.into()); +impl_from!(BeaconBlockMerge, >, >, |body: BeaconBlockBodyMerge<_, _>| body.into()); +impl_from!(BeaconBlockCapella, >, >, |body: BeaconBlockBodyCapella<_, _>| body.into()); + +// We can clone blocks with payloads to blocks without payloads, without cloning the payload. +macro_rules! impl_clone_as_blinded { + ($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>) => { + impl $ty_name<$($from_params),*> + { + pub fn clone_as_blinded(&self) -> $ty_name<$($to_params),*> { + let $ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + } = self; + + $ty_name { + slot: *slot, + proposer_index: *proposer_index, + parent_root: *parent_root, + state_root: *state_root, + body: body.clone_as_blinded(), + } + } + } + } +} + +impl_clone_as_blinded!(BeaconBlockBase, >, >); +impl_clone_as_blinded!(BeaconBlockAltair, >, >); +impl_clone_as_blinded!(BeaconBlockMerge, >, >); +impl_clone_as_blinded!(BeaconBlockCapella, >, >); + +// A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the +// execution payload. +impl<'a, E: EthSpec> From>> + for BeaconBlock> +{ + fn from( + full_block: BeaconBlockRef<'a, E, FullPayload>, + ) -> BeaconBlock> { + map_beacon_block_ref_into_beacon_block!(&'a _, full_block, |inner, cons| { + cons(inner.clone_as_blinded()) + }) + } +} + +impl From>> + for ( + BeaconBlock>, + Option>, + ) +{ + fn from(block: BeaconBlock>) -> Self { + map_beacon_block!(block, |inner, cons| { + let (block, payload) = inner.into(); + (cons(block), payload) + }) + } +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for BeaconBlock +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + Ok(map_fork_name!( + fork_name, + Self, + serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!( + "BeaconBlock failed to deserialize: {:?}", + e + )))? + )) + } +} diff --git a/primitives/beacon/src/beacon_block_body.rs b/primitives/beacon/src/beacon_block_body.rs new file mode 100644 index 00000000..32628916 --- /dev/null +++ b/primitives/beacon/src/beacon_block_body.rs @@ -0,0 +1,427 @@ +use crate::prelude::*; +use crate::*; +use core::marker::PhantomData; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use superstruct::superstruct; +use tree_hash_derive::TreeHash; + +/// The body of a `BeaconChain` block, containing operations. +/// +/// This *superstruct* abstracts over the hard-fork. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: AbstractExecPayload")), + serde( + bound = "T: EthSpec, Payload: AbstractExecPayload", + deny_unknown_fields + ), + scale_info(skip_type_params(T)) + ), + cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] +#[scale_info(skip_type_params(T))] +pub struct BeaconBlockBody = FullPayload> { + pub randao_reveal: Signature, + pub eth1_data: Eth1Data, + pub graffiti: Graffiti, + pub proposer_slashings: VariableList, + pub attester_slashings: VariableList, T::MaxAttesterSlashings>, + pub attestations: VariableList, T::MaxAttestations>, + pub deposits: VariableList, + pub voluntary_exits: VariableList, + #[superstruct(only(Altair, Merge, Capella))] + pub sync_aggregate: SyncAggregate, + // We flatten the execution payload so that serde can use the name of the inner type, + // either `execution_payload` for full payloads, or `execution_payload_header` for blinded + // payloads. + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + #[serde(flatten)] + pub execution_payload: Payload::Merge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + #[serde(flatten)] + pub execution_payload: Payload::Capella, + #[superstruct(only(Capella))] + pub bls_to_execution_changes: + VariableList, + #[superstruct(only(Base, Altair))] + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + pub _phantom: PhantomData, +} + +impl> BeaconBlockBody { + pub fn execution_payload(&self) -> Result, Error> { + self.to_ref().execution_payload() + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, T, Payload> { + pub fn execution_payload(&self) -> Result, Error> { + match self { + Self::Base(_) | Self::Altair(_) => Err(Error::IncorrectStateVariant), + Self::Merge(body) => Ok(Payload::Ref::from(&body.execution_payload)), + Self::Capella(body) => Ok(Payload::Ref::from(&body.execution_payload)), + } + } +} + +impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> { + /// Get the fork_name of this object + pub fn fork_name(self) -> ForkName { + match self { + BeaconBlockBodyRef::Base { .. } => ForkName::Base, + BeaconBlockBodyRef::Altair { .. } => ForkName::Altair, + BeaconBlockBodyRef::Merge { .. } => ForkName::Merge, + BeaconBlockBodyRef::Capella { .. } => ForkName::Capella, + } + } +} + +// We can convert pre-Bellatrix block bodies without payloads into block bodies "with" payloads. +impl From>> + for BeaconBlockBodyBase> +{ + fn from(body: BeaconBlockBodyBase>) -> Self { + let BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom, + } = body; + + BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom: PhantomData, + } + } +} + +impl From>> + for BeaconBlockBodyAltair> +{ + fn from(body: BeaconBlockBodyAltair>) -> Self { + let BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom, + } = body; + + BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom: PhantomData, + } + } +} + +// Likewise bodies with payloads can be transformed into bodies without. +impl From>> + for ( + BeaconBlockBodyBase>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyBase>) -> Self { + let BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom, + } = body; + + ( + BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom: PhantomData, + }, + None, + ) + } +} + +impl From>> + for ( + BeaconBlockBodyAltair>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyAltair>) -> Self { + let BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom, + } = body; + + ( + BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom: PhantomData, + }, + None, + ) + } +} + +impl From>> + for ( + BeaconBlockBodyMerge>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyMerge>) -> Self { + let BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + } = body; + + ( + BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadMerge { + execution_payload_header: From::from(&execution_payload), + }, + }, + Some(execution_payload), + ) + } +} + +impl From>> + for ( + BeaconBlockBodyCapella>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyCapella>) -> Self { + let BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + } = body; + + ( + BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadCapella { + execution_payload_header: From::from(&execution_payload), + }, + bls_to_execution_changes, + }, + Some(execution_payload), + ) + } +} + +// We can clone a full block into a blinded block, without cloning the payload. +impl BeaconBlockBodyBase> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase> { + let (block_body, _payload) = self.clone().into(); + block_body + } +} + +impl BeaconBlockBodyAltair> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyAltair> { + let (block_body, _payload) = self.clone().into(); + block_body + } +} + +impl BeaconBlockBodyMerge> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyMerge> { + let BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + } = self; + + BeaconBlockBodyMerge { + randao_reveal: randao_reveal.clone(), + eth1_data: eth1_data.clone(), + graffiti: *graffiti, + proposer_slashings: proposer_slashings.clone(), + attester_slashings: attester_slashings.clone(), + attestations: attestations.clone(), + deposits: deposits.clone(), + voluntary_exits: voluntary_exits.clone(), + sync_aggregate: sync_aggregate.clone(), + execution_payload: BlindedPayloadMerge { + execution_payload_header: execution_payload.into(), + }, + } + } +} + +impl BeaconBlockBodyCapella> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyCapella> { + let BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + } = self; + + BeaconBlockBodyCapella { + randao_reveal: randao_reveal.clone(), + eth1_data: eth1_data.clone(), + graffiti: *graffiti, + proposer_slashings: proposer_slashings.clone(), + attester_slashings: attester_slashings.clone(), + attestations: attestations.clone(), + deposits: deposits.clone(), + voluntary_exits: voluntary_exits.clone(), + sync_aggregate: sync_aggregate.clone(), + execution_payload: BlindedPayloadCapella { + execution_payload_header: execution_payload.into(), + }, + bls_to_execution_changes: bls_to_execution_changes.clone(), + } + } +} + +impl From>> + for ( + BeaconBlockBody>, + Option>, + ) +{ + fn from(body: BeaconBlockBody>) -> Self { + map_beacon_block_body!(body, |inner, cons| { + let (block, payload) = inner.into(); + (cons(block), payload.map(Into::into)) + }) + } +} diff --git a/primitives/beacon/src/beacon_block_header.rs b/primitives/beacon/src/beacon_block_header.rs new file mode 100644 index 00000000..43bed94c --- /dev/null +++ b/primitives/beacon/src/beacon_block_header.rs @@ -0,0 +1,64 @@ +use crate::*; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +/// A header of a `BeaconBlock`. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct BeaconBlockHeader { + pub slot: Slot, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_index: u64, + pub parent_root: Hash256, + pub state_root: Hash256, + pub body_root: Hash256, +} + +impl SignedRoot for BeaconBlockHeader {} + +impl BeaconBlockHeader { + /// Returns the `tree_hash_root` of the header. + /// + /// Spec v0.12.1 + pub fn canonical_root(&self) -> Hash256 { + Hash256::from_slice(&self.tree_hash_root()[..]) + } + + /// Signs `self`, producing a `SignedBeaconBlockHeader`. + pub fn sign( + self, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedBeaconBlockHeader { + let epoch = self.slot.epoch(E::slots_per_epoch()); + let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork, genesis_validators_root); + let message = self.signing_root(domain); + let signature = secret_key.sign(message); + SignedBeaconBlockHeader { + message: self, + signature, + } + } +} diff --git a/primitives/beacon/src/beacon_committee.rs b/primitives/beacon/src/beacon_committee.rs new file mode 100644 index 00000000..32f436ca --- /dev/null +++ b/primitives/beacon/src/beacon_committee.rs @@ -0,0 +1,27 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::*; + +#[derive(Default, Clone, Debug, PartialEq)] +pub struct BeaconCommittee<'a> { + pub slot: Slot, + pub index: CommitteeIndex, + pub committee: &'a [usize], +} + +impl<'a> BeaconCommittee<'a> { + pub fn into_owned(self) -> OwnedBeaconCommittee { + OwnedBeaconCommittee { + slot: self.slot, + index: self.index, + committee: self.committee.to_vec(), + } + } +} + +#[derive(Default, Clone, Debug, PartialEq)] +pub struct OwnedBeaconCommittee { + pub slot: Slot, + pub index: CommitteeIndex, + pub committee: Vec, +} diff --git a/primitives/beacon/src/beacon_state.rs b/primitives/beacon/src/beacon_state.rs new file mode 100644 index 00000000..a55f99f4 --- /dev/null +++ b/primitives/beacon/src/beacon_state.rs @@ -0,0 +1,136 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::*; + +use core::fmt; +use core::hash::Hash; + +// #[macro_use] +// mod committee_cache; +// mod clone_config; +// mod exit_cache; +// mod iter; +// mod pubkey_cache; +// mod tests; +// mod tree_hash_cache; + +pub const CACHED_EPOCHS: usize = 3; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + /// A state for a different hard-fork was required -- a severe logic error. + IncorrectStateVariant, + EpochOutOfBounds, + SlotOutOfBounds, + UnknownValidator(usize), + UnableToDetermineProducer, + InvalidBitfield, + ValidatorIsWithdrawable, + ValidatorIsInactive { + val_index: usize, + }, + UnableToShuffle, + ShuffleIndexOutOfBounds(usize), + IsAggregatorOutOfBounds, + BlockRootsOutOfBounds(usize), + StateRootsOutOfBounds(usize), + SlashingsOutOfBounds(usize), + BalancesOutOfBounds(usize), + RandaoMixesOutOfBounds(usize), + CommitteeCachesOutOfBounds(usize), + ParticipationOutOfBounds(usize), + InactivityScoresOutOfBounds(usize), + TooManyValidators, + InsufficientValidators, + InsufficientRandaoMixes, + InsufficientBlockRoots, + InsufficientIndexRoots, + InsufficientAttestations, + InsufficientCommittees, + InsufficientStateRoots, + NoCommittee { + slot: Slot, + index: CommitteeIndex, + }, + ZeroSlotsPerEpoch, + PubkeyCacheInconsistent, + PubkeyCacheIncomplete { + cache_len: usize, + registry_len: usize, + }, + PreviousCommitteeCacheUninitialized, + CurrentCommitteeCacheUninitialized, + TotalActiveBalanceCacheUninitialized, + TotalActiveBalanceCacheInconsistent { + initialized_epoch: Epoch, + current_epoch: Epoch, + }, + RelativeEpochError(RelativeEpochError), + ExitCacheUninitialized, + CommitteeCacheUninitialized(Option), + SyncCommitteeCacheUninitialized, + BlsError(bls::Error), + SszTypesError(ssz_types::Error), + TreeHashCacheNotInitialized, + NonLinearTreeHashCacheHistory, + TreeHashCacheSkippedSlot { + cache: Slot, + state: Slot, + }, + TreeHashError(tree_hash::Error), + InvalidValidatorPubkey(ssz::DecodeError), + ValidatorRegistryShrunk, + TreeHashCacheInconsistent, + InvalidDepositState { + deposit_count: u64, + deposit_index: u64, + }, + /// Attestation slipped through block processing with a non-matching source. + IncorrectAttestationSource, + /// An arithmetic operation occurred which would have overflowed or divided by 0. + /// + /// This represents a serious bug in either the spec or Lighthouse! + ArithError(ArithError), + MissingBeaconBlock(SignedBeaconBlockHash), + MissingBeaconState(BeaconStateHash), + PayloadConversionLogicFlaw, + SyncCommitteeNotKnown { + current_epoch: Epoch, + epoch: Epoch, + }, + IndexNotSupported(usize), +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub struct BeaconStateHash(Hash256); + +impl fmt::Debug for BeaconStateHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BeaconStateHash({:?})", self.0) + } +} + +impl fmt::Display for BeaconStateHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for BeaconStateHash { + fn from(hash: Hash256) -> BeaconStateHash { + BeaconStateHash(hash) + } +} + +impl From for Hash256 { + fn from(beacon_state_hash: BeaconStateHash) -> Hash256 { + beacon_state_hash.0 + } +} + +impl From for Error { + fn from(value: ArithError) -> Self { + Self::ArithError(value) + } +} diff --git a/primitives/beacon/src/bls_to_execution_change.rs b/primitives/beacon/src/bls_to_execution_change.rs new file mode 100644 index 00000000..7e931de7 --- /dev/null +++ b/primitives/beacon/src/bls_to_execution_change.rs @@ -0,0 +1,51 @@ +use crate::prelude::*; +use crate::*; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct BlsToExecutionChange { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + pub from_bls_pubkey: PublicKeyBytes, + pub to_execution_address: Address, +} + +impl SignedRoot for BlsToExecutionChange {} + +impl BlsToExecutionChange { + pub fn sign( + self, + secret_key: &SecretKey, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedBlsToExecutionChange { + let domain = spec.compute_domain( + Domain::BlsToExecutionChange, + spec.genesis_fork_version, + genesis_validators_root, + ); + let message = self.signing_root(domain); + SignedBlsToExecutionChange { + message: self, + signature: secret_key.sign(message), + } + } +} diff --git a/primitives/beacon/src/builder_bid.rs b/primitives/beacon/src/builder_bid.rs new file mode 100644 index 00000000..fb2f0e11 --- /dev/null +++ b/primitives/beacon/src/builder_bid.rs @@ -0,0 +1,144 @@ +use crate::prelude::*; +use crate::{ + AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot, + Uint256, +}; +#[cfg(feature = "std")] +use crate::{ForkName, ForkVersionDeserialize}; +use bls::PublicKeyBytes; +use bls::Signature; +use core::marker::PhantomData; +use serde::{Deserialize as De, Deserializer, Serialize as Ser, Serializer}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DeserializeAs, SerializeAs}; +use tree_hash_derive::TreeHash; + +#[serde_as] +#[derive( + PartialEq, + Debug, + Serialize, + Deserialize, + TreeHash, + Clone, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "E: EthSpec, Payload: ExecPayload")] +#[scale_info(skip_type_params(T))] +pub struct BuilderBid> { + #[serde_as(as = "BlindedPayloadAsHeader")] + pub header: Payload, + #[serde(with = "eth2_serde_utils::quoted_u256")] + pub value: Uint256, + pub pubkey: PublicKeyBytes, + #[serde(skip)] + #[tree_hash(skip_hashing)] + _phantom_data: PhantomData, +} + +impl> SignedRoot for BuilderBid {} + +/// Validator registration, for use in interacting with servers implementing the builder API. +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +#[serde(bound = "E: EthSpec, Payload: ExecPayload")] +pub struct SignedBuilderBid> { + pub message: BuilderBid, + pub signature: Signature, +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for BuilderBid +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + let convert_err = |_| { + serde::de::Error::custom( + "BuilderBid failed to deserialize: unable to convert payload header to payload", + ) + }; + + #[derive(Deserialize)] + struct Helper { + header: serde_json::Value, + #[serde(with = "eth2_serde_utils::quoted_u256")] + value: Uint256, + pubkey: PublicKeyBytes, + } + let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + let payload_header = + ExecutionPayloadHeader::deserialize_by_fork::<'de, D>(helper.header, fork_name)?; + + Ok(Self { + header: Payload::try_from(payload_header).map_err(convert_err)?, + value: helper.value, + pubkey: helper.pubkey, + _phantom_data: Default::default(), + }) + } +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for SignedBuilderBid +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + #[derive(Deserialize)] + struct Helper { + pub message: serde_json::Value, + pub signature: Signature, + } + let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + + Ok(Self { + message: BuilderBid::deserialize_by_fork::<'de, D>(helper.message, fork_name)?, + signature: helper.signature, + }) + } +} + +struct BlindedPayloadAsHeader(PhantomData); + +impl> SerializeAs for BlindedPayloadAsHeader { + fn serialize_as(source: &Payload, serializer: S) -> Result + where + S: Serializer, + { + source.to_execution_payload_header().serialize(serializer) + } +} + +impl<'de, E: EthSpec, Payload: AbstractExecPayload> DeserializeAs<'de, Payload> + for BlindedPayloadAsHeader +{ + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let payload_header = ExecutionPayloadHeader::deserialize(deserializer)?; + Payload::try_from(payload_header) + .map_err(|_| serde::de::Error::custom("unable to convert payload header to payload")) + } +} + +impl> SignedBuilderBid { + pub fn verify_signature(&self, spec: &ChainSpec) -> bool { + self.message + .pubkey + .decompress() + .map(|pubkey| { + let domain = spec.get_builder_domain(); + let message = self.message.signing_root(domain); + self.signature.verify(&pubkey, message) + }) + .unwrap_or(false) + } +} diff --git a/primitives/beacon/src/chain_spec.rs b/primitives/beacon/src/chain_spec.rs new file mode 100644 index 00000000..4dc9b365 --- /dev/null +++ b/primitives/beacon/src/chain_spec.rs @@ -0,0 +1,1159 @@ +use crate::application_domain::{ApplicationDomain, APPLICATION_DOMAIN_BUILDER}; +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::*; +use eth2_serde_utils::quoted_u64::MaybeQuoted; +use int_to_bytes::int_to_bytes4; +use serde::Deserialize; +use serde::{Deserializer, Serialize, Serializer}; + +use tree_hash::TreeHash; + +/// Each of the BLS signature domains. +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Domain { + BlsToExecutionChange, + BeaconProposer, + BeaconAttester, + BlobsSideCar, + Randao, + Deposit, + VoluntaryExit, + SelectionProof, + AggregateAndProof, + SyncCommittee, + ContributionAndProof, + SyncCommitteeSelectionProof, + ApplicationMask(ApplicationDomain), +} + +/// Lighthouse's internal configuration struct. +/// +/// Contains a mixture of "preset" and "config" values w.r.t to the EF definitions. +#[derive(PartialEq, Debug, Clone)] +pub struct ChainSpec { + /* + * Config name + */ + pub config_name: Option, + + /* + * Constants + */ + pub genesis_slot: Slot, + pub far_future_epoch: Epoch, + pub base_rewards_per_epoch: u64, + pub deposit_contract_tree_depth: u64, + + /* + * Misc + */ + pub max_committees_per_slot: usize, + pub target_committee_size: usize, + pub min_per_epoch_churn_limit: u64, + pub churn_limit_quotient: u64, + pub shuffle_round_count: u8, + pub min_genesis_active_validator_count: u64, + pub min_genesis_time: u64, + pub hysteresis_quotient: u64, + pub hysteresis_downward_multiplier: u64, + pub hysteresis_upward_multiplier: u64, + pub proportional_slashing_multiplier: u64, + + /* + * Gwei values + */ + pub min_deposit_amount: u64, + pub max_effective_balance: u64, + pub ejection_balance: u64, + pub effective_balance_increment: u64, + + /* + * Initial Values + */ + pub genesis_fork_version: [u8; 4], + pub bls_withdrawal_prefix_byte: u8, + pub eth1_address_withdrawal_prefix_byte: u8, + + /* + * Time parameters + */ + pub genesis_delay: u64, + pub seconds_per_slot: u64, + pub min_attestation_inclusion_delay: u64, + pub min_seed_lookahead: Epoch, + pub max_seed_lookahead: Epoch, + pub min_epochs_to_inactivity_penalty: u64, + pub min_validator_withdrawability_delay: Epoch, + pub shard_committee_period: u64, + + /* + * Reward and penalty quotients + */ + pub base_reward_factor: u64, + pub whistleblower_reward_quotient: u64, + pub proposer_reward_quotient: u64, + pub inactivity_penalty_quotient: u64, + pub min_slashing_penalty_quotient: u64, + + /* + * Signature domains + */ + pub(crate) domain_beacon_proposer: u32, + pub(crate) domain_beacon_attester: u32, + pub(crate) domain_blobs_sidecar: u32, + pub(crate) domain_randao: u32, + pub(crate) domain_deposit: u32, + pub(crate) domain_voluntary_exit: u32, + pub(crate) domain_selection_proof: u32, + pub(crate) domain_aggregate_and_proof: u32, + + /* + * Fork choice + */ + pub safe_slots_to_update_justified: u64, + pub proposer_score_boost: Option, + + /* + * Eth1 + */ + pub eth1_follow_distance: u64, + pub seconds_per_eth1_block: u64, + pub deposit_chain_id: u64, + pub deposit_network_id: u64, + pub deposit_contract_address: Address, + + /* + * Altair hard fork params + */ + pub inactivity_penalty_quotient_altair: u64, + pub min_slashing_penalty_quotient_altair: u64, + pub proportional_slashing_multiplier_altair: u64, + pub epochs_per_sync_committee_period: Epoch, + pub inactivity_score_bias: u64, + pub inactivity_score_recovery_rate: u64, + pub min_sync_committee_participants: u64, + pub(crate) domain_sync_committee: u32, + pub(crate) domain_sync_committee_selection_proof: u32, + pub(crate) domain_contribution_and_proof: u32, + pub altair_fork_version: [u8; 4], + /// The Altair fork epoch is optional, with `None` representing "Altair never happens". + pub altair_fork_epoch: Option, + + /* + * Merge hard fork params + */ + pub inactivity_penalty_quotient_bellatrix: u64, + pub min_slashing_penalty_quotient_bellatrix: u64, + pub proportional_slashing_multiplier_bellatrix: u64, + pub bellatrix_fork_version: [u8; 4], + /// The Merge fork epoch is optional, with `None` representing "Merge never happens". + pub bellatrix_fork_epoch: Option, + pub terminal_total_difficulty: Uint256, + pub terminal_block_hash: ExecutionBlockHash, + pub terminal_block_hash_activation_epoch: Epoch, + pub safe_slots_to_import_optimistically: u64, + + /* + * Capella hard fork params + */ + pub capella_fork_version: [u8; 4], + /// The Capella fork epoch is optional, with `None` representing "Capella never happens". + pub capella_fork_epoch: Option, + pub max_validators_per_withdrawals_sweep: u64, + + /* + * Networking + */ + pub boot_nodes: Vec, + pub network_id: u8, + pub attestation_propagation_slot_range: u64, + pub maximum_gossip_clock_disparity_millis: u64, + pub target_aggregators_per_committee: u64, + pub attestation_subnet_count: u64, + pub random_subnets_per_validator: u64, + pub epochs_per_random_subnet_subscription: u64, + pub subnets_per_node: u8, + pub epochs_per_subnet_subscription: u64, + attestation_subnet_extra_bits: u8, + + /* + * Application params + */ + pub(crate) domain_application_mask: u32, + + /* + * Capella params + */ + pub(crate) domain_bls_to_execution_change: u32, +} + +impl ChainSpec { + /// Construct a `ChainSpec` from a standard config. + pub fn from_config(config: &Config) -> Option { + let spec = T::default_spec(); + config.apply_to_chain_spec::(&spec) + } + + /// Returns an `EnrForkId` for the given `slot`. + pub fn enr_fork_id( + &self, + slot: Slot, + genesis_validators_root: Hash256, + ) -> EnrForkId { + EnrForkId { + fork_digest: self.fork_digest::(slot, genesis_validators_root), + next_fork_version: self.next_fork_version::(slot), + next_fork_epoch: self + .next_fork_epoch::(slot) + .map(|(_, e)| e) + .unwrap_or(self.far_future_epoch), + } + } + + /// Returns the `ForkDigest` for the given slot. + /// + /// If `self.altair_fork_epoch == None`, then this function returns the genesis fork digest + /// otherwise, returns the fork digest based on the slot. + pub fn fork_digest(&self, slot: Slot, genesis_validators_root: Hash256) -> [u8; 4] { + let fork_name = self.fork_name_at_slot::(slot); + Self::compute_fork_digest( + self.fork_version_for_name(fork_name), + genesis_validators_root, + ) + } + + /// Returns the `next_fork_version`. + /// + /// `next_fork_version = current_fork_version` if no future fork is planned, + pub fn next_fork_version(&self, slot: Slot) -> [u8; 4] { + match self.next_fork_epoch::(slot) { + Some((fork, _)) => self.fork_version_for_name(fork), + None => self.fork_version_for_name(self.fork_name_at_slot::(slot)), + } + } + + /// Returns the epoch of the next scheduled fork along with its corresponding `ForkName`. + /// + /// If no future forks are scheduled, this function returns `None`. + pub fn next_fork_epoch(&self, slot: Slot) -> Option<(ForkName, Epoch)> { + let current_fork_name = self.fork_name_at_slot::(slot); + let next_fork_name = current_fork_name.next_fork()?; + let fork_epoch = self.fork_epoch(next_fork_name)?; + Some((next_fork_name, fork_epoch)) + } + + /// Returns the name of the fork which is active at `slot`. + pub fn fork_name_at_slot(&self, slot: Slot) -> ForkName { + self.fork_name_at_epoch(slot.epoch(E::slots_per_epoch())) + } + + /// Returns the name of the fork which is active at `epoch`. + pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName { + match self.capella_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella, + _ => match self.bellatrix_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Merge, + _ => match self.altair_fork_epoch { + Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair, + _ => ForkName::Base, + }, + }, + } + } + + /// Returns the fork version for a named fork. + pub fn fork_version_for_name(&self, fork_name: ForkName) -> [u8; 4] { + match fork_name { + ForkName::Base => self.genesis_fork_version, + ForkName::Altair => self.altair_fork_version, + ForkName::Merge => self.bellatrix_fork_version, + ForkName::Capella => self.capella_fork_version, + } + } + + /// For a given fork name, return the epoch at which it activates. + pub fn fork_epoch(&self, fork_name: ForkName) -> Option { + match fork_name { + ForkName::Base => Some(Epoch::new(0)), + ForkName::Altair => self.altair_fork_epoch, + ForkName::Merge => self.bellatrix_fork_epoch, + ForkName::Capella => self.capella_fork_epoch, + } + } + + /// Returns a full `Fork` struct for a given epoch. + pub fn fork_at_epoch(&self, epoch: Epoch) -> Fork { + let current_fork_name = self.fork_name_at_epoch(epoch); + let previous_fork_name = current_fork_name.previous_fork().unwrap_or(ForkName::Base); + let epoch = self + .fork_epoch(current_fork_name) + .unwrap_or_else(|| Epoch::new(0)); + + Fork { + previous_version: self.fork_version_for_name(previous_fork_name), + current_version: self.fork_version_for_name(current_fork_name), + epoch, + } + } + + /// Returns a full `Fork` struct for a given `ForkName` or `None` if the fork does not yet have + /// an activation epoch. + pub fn fork_for_name(&self, fork_name: ForkName) -> Option { + let previous_fork_name = fork_name.previous_fork().unwrap_or(ForkName::Base); + let epoch = self.fork_epoch(fork_name)?; + + Some(Fork { + previous_version: self.fork_version_for_name(previous_fork_name), + current_version: self.fork_version_for_name(fork_name), + epoch, + }) + } + + /// Get the domain number, unmodified by the fork. + /// + /// Spec v0.12.1 + pub fn get_domain_constant(&self, domain: Domain) -> u32 { + match domain { + Domain::BeaconProposer => self.domain_beacon_proposer, + Domain::BeaconAttester => self.domain_beacon_attester, + Domain::BlobsSideCar => self.domain_blobs_sidecar, + Domain::Randao => self.domain_randao, + Domain::Deposit => self.domain_deposit, + Domain::VoluntaryExit => self.domain_voluntary_exit, + Domain::SelectionProof => self.domain_selection_proof, + Domain::AggregateAndProof => self.domain_aggregate_and_proof, + Domain::SyncCommittee => self.domain_sync_committee, + Domain::ContributionAndProof => self.domain_contribution_and_proof, + Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, + Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(), + Domain::BlsToExecutionChange => self.domain_bls_to_execution_change, + } + } + + /// Get the domain that represents the fork meta and signature domain. + /// + /// Spec v0.12.1 + pub fn get_domain( + &self, + epoch: Epoch, + domain: Domain, + fork: &Fork, + genesis_validators_root: Hash256, + ) -> Hash256 { + let fork_version = fork.get_fork_version(epoch); + self.compute_domain(domain, fork_version, genesis_validators_root) + } + + /// Get the domain for a deposit signature. + /// + /// Deposits are valid across forks, thus the deposit domain is computed + /// with the genesis fork version. + /// + /// Spec v0.12.1 + pub fn get_deposit_domain(&self) -> Hash256 { + self.compute_domain(Domain::Deposit, self.genesis_fork_version, Hash256::zero()) + } + + // This should be updated to include the current fork and the genesis validators root, but discussion is ongoing: + // + // https://github.com/ethereum/builder-specs/issues/14 + pub fn get_builder_domain(&self) -> Hash256 { + self.compute_domain( + Domain::ApplicationMask(ApplicationDomain::Builder), + self.genesis_fork_version, + Hash256::zero(), + ) + } + + /// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`. + /// + /// This is used primarily in signature domains to avoid collisions across forks/chains. + /// + /// Spec v0.12.1 + pub fn compute_fork_data_root( + current_version: [u8; 4], + genesis_validators_root: Hash256, + ) -> Hash256 { + ForkData { + current_version, + genesis_validators_root, + } + .tree_hash_root() + } + + /// Return the 4-byte fork digest for the `current_version` and `genesis_validators_root`. + /// + /// This is a digest primarily used for domain separation on the p2p layer. + /// 4-bytes suffices for practical separation of forks/chains. + pub fn compute_fork_digest( + current_version: [u8; 4], + genesis_validators_root: Hash256, + ) -> [u8; 4] { + let mut result = [0; 4]; + let root = Self::compute_fork_data_root(current_version, genesis_validators_root); + result.copy_from_slice( + root.as_bytes() + .get(0..4) + .expect("root hash is at least 4 bytes"), + ); + result + } + + /// Compute a domain by applying the given `fork_version`. + pub fn compute_domain( + &self, + domain: Domain, + fork_version: [u8; 4], + genesis_validators_root: Hash256, + ) -> Hash256 { + let domain_constant = self.get_domain_constant(domain); + Self::compute_domain_with_constant(domain_constant, fork_version, genesis_validators_root) + } + + pub fn compute_domain_with_constant( + domain_constant: u32, + fork_version: [u8; 4], + genesis_validators_root: Hash256, + ) -> Hash256 { + let mut domain = [0; 32]; + domain[0..4].copy_from_slice(&int_to_bytes4(domain_constant)); + domain[4..].copy_from_slice( + Self::compute_fork_data_root(fork_version, genesis_validators_root) + .as_bytes() + .get(..28) + .expect("fork has is 32 bytes so first 28 bytes should exist"), + ); + + Hash256::from(domain) + } + + #[allow(clippy::integer_arithmetic)] + pub const fn attestation_subnet_prefix_bits(&self) -> u32 { + // maybe use log2 when stable https://github.com/rust-lang/rust/issues/70887 + + // NOTE: this line is here simply to guarantee that if self.attestation_subnet_count type + // is changed, a compiler warning will be raised. This code depends on the type being u64. + let attestation_subnet_count: u64 = self.attestation_subnet_count; + let attestation_subnet_count_bits = if attestation_subnet_count == 0 { + 0 + } else { + 63 - attestation_subnet_count.leading_zeros() + }; + + self.attestation_subnet_extra_bits as u32 + attestation_subnet_count_bits + } + + /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. + pub fn mainnet() -> Self { + Self { + /* + * Config name + */ + config_name: Some("mainnet".to_string()), + /* + * Constants + */ + genesis_slot: Slot::new(0), + far_future_epoch: Epoch::new(u64::MAX), + base_rewards_per_epoch: 4, + deposit_contract_tree_depth: 32, + + /* + * Misc + */ + max_committees_per_slot: 64, + target_committee_size: 128, + min_per_epoch_churn_limit: 4, + churn_limit_quotient: 65_536, + shuffle_round_count: 90, + min_genesis_active_validator_count: 16_384, + min_genesis_time: 1606824000, // Dec 1, 2020 + hysteresis_quotient: 4, + hysteresis_downward_multiplier: 1, + hysteresis_upward_multiplier: 5, + + /* + * Gwei values + */ + min_deposit_amount: option_wrapper(|| { + u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + max_effective_balance: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + ejection_balance: option_wrapper(|| { + u64::checked_pow(2, 4)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + effective_balance_increment: option_wrapper(|| { + u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + + /* + * Initial Values + */ + genesis_fork_version: [0; 4], + bls_withdrawal_prefix_byte: 0x00, + eth1_address_withdrawal_prefix_byte: 0x01, + + /* + * Time parameters + */ + genesis_delay: 604800, // 7 days + seconds_per_slot: 12, + min_attestation_inclusion_delay: 1, + min_seed_lookahead: Epoch::new(1), + max_seed_lookahead: Epoch::new(4), + min_epochs_to_inactivity_penalty: 4, + min_validator_withdrawability_delay: Epoch::new(256), + shard_committee_period: 256, + + /* + * Reward and penalty quotients + */ + base_reward_factor: 64, + whistleblower_reward_quotient: 512, + proposer_reward_quotient: 8, + inactivity_penalty_quotient: u64::checked_pow(2, 26).expect("pow does not overflow"), + min_slashing_penalty_quotient: 128, + proportional_slashing_multiplier: 1, + + /* + * Signature domains + */ + domain_beacon_proposer: 0, + domain_beacon_attester: 1, + domain_randao: 2, + domain_deposit: 3, + domain_voluntary_exit: 4, + domain_selection_proof: 5, + domain_aggregate_and_proof: 6, + domain_blobs_sidecar: 10, // 0x0a000000 + + /* + * Fork choice + */ + safe_slots_to_update_justified: 8, + proposer_score_boost: Some(40), + + /* + * Eth1 + */ + eth1_follow_distance: 2048, + seconds_per_eth1_block: 14, + deposit_chain_id: 1, + deposit_network_id: 1, + deposit_contract_address: "00000000219ab540356cbb839cbe05303d7705fa" + .parse() + .expect("chain spec deposit contract address"), + + /* + * Altair hard fork params + */ + inactivity_penalty_quotient_altair: option_wrapper(|| { + u64::checked_pow(2, 24)?.checked_mul(3) + }) + .expect("calculation does not overflow"), + min_slashing_penalty_quotient_altair: u64::checked_pow(2, 6) + .expect("pow does not overflow"), + proportional_slashing_multiplier_altair: 2, + inactivity_score_bias: 4, + inactivity_score_recovery_rate: 16, + min_sync_committee_participants: 1, + epochs_per_sync_committee_period: Epoch::new(256), + domain_sync_committee: 7, + domain_sync_committee_selection_proof: 8, + domain_contribution_and_proof: 9, + altair_fork_version: [0x01, 0x00, 0x00, 0x00], + altair_fork_epoch: Some(Epoch::new(74240)), + + /* + * Merge hard fork params + */ + inactivity_penalty_quotient_bellatrix: u64::checked_pow(2, 24) + .expect("pow does not overflow"), + min_slashing_penalty_quotient_bellatrix: u64::checked_pow(2, 5) + .expect("pow does not overflow"), + proportional_slashing_multiplier_bellatrix: 3, + bellatrix_fork_version: [0x02, 0x00, 0x00, 0x00], + bellatrix_fork_epoch: Some(Epoch::new(144896)), + terminal_total_difficulty: Uint256::from_dec_str("58750000000000000000000") + .expect("terminal_total_difficulty is a valid integer"), + terminal_block_hash: ExecutionBlockHash::zero(), + terminal_block_hash_activation_epoch: Epoch::new(u64::MAX), + safe_slots_to_import_optimistically: 128u64, + + /* + * Capella hard fork params + */ + capella_fork_version: [0x03, 00, 00, 00], + capella_fork_epoch: None, + max_validators_per_withdrawals_sweep: 16384, + + /* + * Network specific + */ + boot_nodes: vec![], + network_id: 1, // mainnet network id + attestation_propagation_slot_range: 32, + attestation_subnet_count: 64, + random_subnets_per_validator: 1, + subnets_per_node: 1, + maximum_gossip_clock_disparity_millis: 500, + target_aggregators_per_committee: 16, + epochs_per_random_subnet_subscription: 256, + epochs_per_subnet_subscription: 256, + attestation_subnet_extra_bits: 6, + + /* + * Application specific + */ + domain_application_mask: APPLICATION_DOMAIN_BUILDER, + + /* + * Capella params + */ + domain_bls_to_execution_change: 10, + } + } + + /// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo. + pub fn minimal() -> Self { + // Note: bootnodes to be updated when static nodes exist. + let boot_nodes = vec![]; + + Self { + config_name: None, + max_committees_per_slot: 4, + target_committee_size: 4, + churn_limit_quotient: 32, + shuffle_round_count: 10, + min_genesis_active_validator_count: 64, + min_genesis_time: 1578009600, + eth1_follow_distance: 16, + genesis_fork_version: [0x00, 0x00, 0x00, 0x01], + shard_committee_period: 64, + genesis_delay: 300, + seconds_per_slot: 6, + inactivity_penalty_quotient: u64::checked_pow(2, 25).expect("pow does not overflow"), + min_slashing_penalty_quotient: 64, + proportional_slashing_multiplier: 2, + safe_slots_to_update_justified: 2, + // Altair + epochs_per_sync_committee_period: Epoch::new(8), + altair_fork_version: [0x01, 0x00, 0x00, 0x01], + altair_fork_epoch: None, + // Merge + bellatrix_fork_version: [0x02, 0x00, 0x00, 0x01], + bellatrix_fork_epoch: None, + terminal_total_difficulty: Uint256::MAX + .checked_sub(Uint256::from(2u64.pow(10))) + .expect("subtraction does not overflow") + // Add 1 since the spec declares `2**256 - 2**10` and we use + // `Uint256::MAX` which is `2*256- 1`. + .checked_add(Uint256::one()) + .expect("addition does not overflow"), + // Capella + capella_fork_version: [0x03, 0x00, 0x00, 0x01], + capella_fork_epoch: None, + max_validators_per_withdrawals_sweep: 16, + // Other + network_id: 2, // lighthouse testnet network id + deposit_chain_id: 5, + deposit_network_id: 5, + deposit_contract_address: "1234567890123456789012345678901234567890" + .parse() + .expect("minimal chain spec deposit address"), + boot_nodes, + ..ChainSpec::mainnet() + } + } + + /// Returns a `ChainSpec` compatible with the Gnosis Beacon Chain specification. + pub fn gnosis() -> Self { + Self { + config_name: Some("gnosis".to_string()), + /* + * Constants + */ + genesis_slot: Slot::new(0), + far_future_epoch: Epoch::new(u64::MAX), + base_rewards_per_epoch: 4, + deposit_contract_tree_depth: 32, + + /* + * Misc + */ + max_committees_per_slot: 64, + target_committee_size: 128, + min_per_epoch_churn_limit: 4, + churn_limit_quotient: 4_096, + shuffle_round_count: 90, + min_genesis_active_validator_count: 4_096, + min_genesis_time: 1638968400, // Dec 8, 2020 + hysteresis_quotient: 4, + hysteresis_downward_multiplier: 1, + hysteresis_upward_multiplier: 5, + + /* + * Gwei values + */ + min_deposit_amount: option_wrapper(|| { + u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + max_effective_balance: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + ejection_balance: option_wrapper(|| { + u64::checked_pow(2, 4)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + effective_balance_increment: option_wrapper(|| { + u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + + /* + * Initial Values + */ + genesis_fork_version: [0x00, 0x00, 0x00, 0x64], + bls_withdrawal_prefix_byte: 0x00, + eth1_address_withdrawal_prefix_byte: 0x01, + + /* + * Time parameters + */ + genesis_delay: 6000, // 100 minutes + seconds_per_slot: 5, + min_attestation_inclusion_delay: 1, + min_seed_lookahead: Epoch::new(1), + max_seed_lookahead: Epoch::new(4), + min_epochs_to_inactivity_penalty: 4, + min_validator_withdrawability_delay: Epoch::new(256), + shard_committee_period: 256, + + /* + * Reward and penalty quotients + */ + base_reward_factor: 25, + whistleblower_reward_quotient: 512, + proposer_reward_quotient: 8, + inactivity_penalty_quotient: u64::checked_pow(2, 26).expect("pow does not overflow"), + min_slashing_penalty_quotient: 128, + proportional_slashing_multiplier: 1, + + /* + * Signature domains + */ + domain_beacon_proposer: 0, + domain_beacon_attester: 1, + domain_randao: 2, + domain_deposit: 3, + domain_voluntary_exit: 4, + domain_selection_proof: 5, + domain_aggregate_and_proof: 6, + domain_blobs_sidecar: 10, + + /* + * Fork choice + */ + safe_slots_to_update_justified: 8, + proposer_score_boost: Some(40), + + /* + * Eth1 + */ + eth1_follow_distance: 1024, + seconds_per_eth1_block: 6, + deposit_chain_id: 100, + deposit_network_id: 100, + deposit_contract_address: "0B98057eA310F4d31F2a452B414647007d1645d9" + .parse() + .expect("chain spec deposit contract address"), + + /* + * Altair hard fork params + */ + inactivity_penalty_quotient_altair: option_wrapper(|| { + u64::checked_pow(2, 24)?.checked_mul(3) + }) + .expect("calculation does not overflow"), + min_slashing_penalty_quotient_altair: u64::checked_pow(2, 6) + .expect("pow does not overflow"), + proportional_slashing_multiplier_altair: 2, + inactivity_score_bias: 4, + inactivity_score_recovery_rate: 16, + min_sync_committee_participants: 1, + epochs_per_sync_committee_period: Epoch::new(512), + domain_sync_committee: 7, + domain_sync_committee_selection_proof: 8, + domain_contribution_and_proof: 9, + altair_fork_version: [0x01, 0x00, 0x00, 0x64], + altair_fork_epoch: Some(Epoch::new(512)), + + /* + * Merge hard fork params + */ + inactivity_penalty_quotient_bellatrix: u64::checked_pow(2, 24) + .expect("pow does not overflow"), + min_slashing_penalty_quotient_bellatrix: u64::checked_pow(2, 5) + .expect("pow does not overflow"), + proportional_slashing_multiplier_bellatrix: 3, + bellatrix_fork_version: [0x02, 0x00, 0x00, 0x64], + bellatrix_fork_epoch: Some(Epoch::new(385536)), + terminal_total_difficulty: Uint256::from_dec_str( + "8626000000000000000000058750000000000000000000", + ) + .expect("terminal_total_difficulty is a valid integer"), + terminal_block_hash: ExecutionBlockHash::zero(), + terminal_block_hash_activation_epoch: Epoch::new(u64::MAX), + safe_slots_to_import_optimistically: 128u64, + + /* + * Capella hard fork params + */ + capella_fork_version: [0x03, 0x00, 0x00, 0x64], + capella_fork_epoch: None, + max_validators_per_withdrawals_sweep: 16384, + + /* + * Network specific + */ + boot_nodes: vec![], + network_id: 100, // Gnosis Chain network id + attestation_propagation_slot_range: 32, + attestation_subnet_count: 64, + random_subnets_per_validator: 1, + subnets_per_node: 1, + maximum_gossip_clock_disparity_millis: 500, + target_aggregators_per_committee: 16, + epochs_per_random_subnet_subscription: 256, + epochs_per_subnet_subscription: 256, + attestation_subnet_extra_bits: 6, + + /* + * Application specific + */ + domain_application_mask: APPLICATION_DOMAIN_BUILDER, + + /* + * Capella params + */ + domain_bls_to_execution_change: 10, + } + } +} + +impl Default for ChainSpec { + fn default() -> Self { + Self::mainnet() + } +} + +/// Exact implementation of the *config* object from the Ethereum spec (YAML/JSON). +/// +/// Fields relevant to hard forks after Altair should be optional so that we can continue +/// to parse Altair configs. This default approach turns out to be much simpler than trying to +/// make `Config` a superstruct because of the hassle of deserializing an untagged enum. +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub struct Config { + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub config_name: Option, + + #[serde(default)] + pub preset_base: String, + + #[serde(default = "default_terminal_total_difficulty")] + #[serde(with = "eth2_serde_utils::quoted_u256")] + pub terminal_total_difficulty: Uint256, + #[serde(default = "default_terminal_block_hash")] + pub terminal_block_hash: ExecutionBlockHash, + #[serde(default = "default_terminal_block_hash_activation_epoch")] + pub terminal_block_hash_activation_epoch: Epoch, + #[serde(default = "default_safe_slots_to_import_optimistically")] + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub safe_slots_to_import_optimistically: u64, + + #[serde(with = "eth2_serde_utils::quoted_u64")] + min_genesis_active_validator_count: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + min_genesis_time: u64, + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + genesis_fork_version: [u8; 4], + #[serde(with = "eth2_serde_utils::quoted_u64")] + genesis_delay: u64, + + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + altair_fork_version: [u8; 4], + #[serde(serialize_with = "serialize_fork_epoch")] + #[serde(deserialize_with = "deserialize_fork_epoch")] + pub altair_fork_epoch: Option>, + + #[serde(default = "default_bellatrix_fork_version")] + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + bellatrix_fork_version: [u8; 4], + #[serde(default)] + #[serde(serialize_with = "serialize_fork_epoch")] + #[serde(deserialize_with = "deserialize_fork_epoch")] + pub bellatrix_fork_epoch: Option>, + + #[serde(default = "default_capella_fork_version")] + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + capella_fork_version: [u8; 4], + #[serde(default)] + #[serde(serialize_with = "serialize_fork_epoch")] + #[serde(deserialize_with = "deserialize_fork_epoch")] + pub capella_fork_epoch: Option>, + + #[serde(with = "eth2_serde_utils::quoted_u64")] + seconds_per_slot: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + seconds_per_eth1_block: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + min_validator_withdrawability_delay: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + shard_committee_period: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + eth1_follow_distance: u64, + + #[serde(with = "eth2_serde_utils::quoted_u64")] + inactivity_score_bias: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + inactivity_score_recovery_rate: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + ejection_balance: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + min_per_epoch_churn_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + churn_limit_quotient: u64, + + #[serde(skip_serializing_if = "Option::is_none")] + proposer_score_boost: Option>, + + #[serde(with = "eth2_serde_utils::quoted_u64")] + deposit_chain_id: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + deposit_network_id: u64, + deposit_contract_address: Address, +} + +fn default_bellatrix_fork_version() -> [u8; 4] { + // This value shouldn't be used. + [0xff, 0xff, 0xff, 0xff] +} + +fn default_capella_fork_version() -> [u8; 4] { + // TODO: determine if the bellatrix example should be copied like this + [0xff, 0xff, 0xff, 0xff] +} + +/// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912). +/// +/// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16 +const fn default_terminal_total_difficulty() -> Uint256 { + ethereum_types::U256([ + 18446744073709550592, + 18446744073709551615, + 18446744073709551615, + 18446744073709551615, + ]) +} + +fn default_terminal_block_hash() -> ExecutionBlockHash { + ExecutionBlockHash::zero() +} + +fn default_terminal_block_hash_activation_epoch() -> Epoch { + Epoch::new(u64::MAX) +} + +fn default_safe_slots_to_import_optimistically() -> u64 { + 128u64 +} + +impl Default for Config { + fn default() -> Self { + let chain_spec = MainnetEthSpec::default_spec(); + Config::from_chain_spec::(&chain_spec) + } +} + +/// Util function to serialize a `None` fork epoch value +/// as `Epoch::max_value()`. +fn serialize_fork_epoch(val: &Option>, s: S) -> Result +where + S: Serializer, +{ + match val { + None => MaybeQuoted { + value: Epoch::max_value(), + } + .serialize(s), + Some(epoch) => epoch.serialize(s), + } +} + +/// Util function to deserialize a u64::max() fork epoch as `None`. +fn deserialize_fork_epoch<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let decoded: Option> = serde::de::Deserialize::deserialize(deserializer)?; + if let Some(fork_epoch) = decoded { + if fork_epoch.value != Epoch::max_value() { + return Ok(Some(fork_epoch)); + } + } + Ok(None) +} + +impl Config { + /// Maps `self` to an identifier for an `EthSpec` instance. + /// + /// Returns `None` if there is no match. + pub fn eth_spec_id(&self) -> Option { + match self.preset_base.as_str() { + "minimal" => Some(EthSpecId::Minimal), + "mainnet" => Some(EthSpecId::Mainnet), + "gnosis" => Some(EthSpecId::Gnosis), + _ => None, + } + } + + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + config_name: spec.config_name.clone(), + preset_base: T::spec_name().to_string(), + + terminal_total_difficulty: spec.terminal_total_difficulty, + terminal_block_hash: spec.terminal_block_hash, + terminal_block_hash_activation_epoch: spec.terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically: spec.safe_slots_to_import_optimistically, + + min_genesis_active_validator_count: spec.min_genesis_active_validator_count, + min_genesis_time: spec.min_genesis_time, + genesis_fork_version: spec.genesis_fork_version, + genesis_delay: spec.genesis_delay, + + altair_fork_version: spec.altair_fork_version, + altair_fork_epoch: spec + .altair_fork_epoch + .map(|epoch| MaybeQuoted { value: epoch }), + bellatrix_fork_version: spec.bellatrix_fork_version, + bellatrix_fork_epoch: spec + .bellatrix_fork_epoch + .map(|epoch| MaybeQuoted { value: epoch }), + capella_fork_version: spec.capella_fork_version, + capella_fork_epoch: spec + .capella_fork_epoch + .map(|epoch| MaybeQuoted { value: epoch }), + + seconds_per_slot: spec.seconds_per_slot, + seconds_per_eth1_block: spec.seconds_per_eth1_block, + min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay, + shard_committee_period: spec.shard_committee_period, + eth1_follow_distance: spec.eth1_follow_distance, + + inactivity_score_bias: spec.inactivity_score_bias, + inactivity_score_recovery_rate: spec.inactivity_score_recovery_rate, + ejection_balance: spec.ejection_balance, + churn_limit_quotient: spec.churn_limit_quotient, + min_per_epoch_churn_limit: spec.min_per_epoch_churn_limit, + + proposer_score_boost: spec.proposer_score_boost.map(|value| MaybeQuoted { value }), + + deposit_chain_id: spec.deposit_chain_id, + deposit_network_id: spec.deposit_network_id, + deposit_contract_address: spec.deposit_contract_address, + } + } + + pub fn apply_to_chain_spec(&self, chain_spec: &ChainSpec) -> Option { + // Pattern match here to avoid missing any fields. + let &Config { + ref config_name, + ref preset_base, + terminal_total_difficulty, + terminal_block_hash, + terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically, + min_genesis_active_validator_count, + min_genesis_time, + genesis_fork_version, + genesis_delay, + altair_fork_version, + altair_fork_epoch, + bellatrix_fork_epoch, + bellatrix_fork_version, + capella_fork_epoch, + capella_fork_version, + seconds_per_slot, + seconds_per_eth1_block, + min_validator_withdrawability_delay, + shard_committee_period, + eth1_follow_distance, + inactivity_score_bias, + inactivity_score_recovery_rate, + ejection_balance, + min_per_epoch_churn_limit, + churn_limit_quotient, + proposer_score_boost, + deposit_chain_id, + deposit_network_id, + deposit_contract_address, + } = self; + + if preset_base != T::spec_name().to_string().as_str() { + return None; + } + + Some(ChainSpec { + config_name: config_name.clone(), + min_genesis_active_validator_count, + min_genesis_time, + genesis_fork_version, + genesis_delay, + altair_fork_version, + altair_fork_epoch: altair_fork_epoch.map(|q| q.value), + bellatrix_fork_epoch: bellatrix_fork_epoch.map(|q| q.value), + bellatrix_fork_version, + capella_fork_epoch: capella_fork_epoch.map(|q| q.value), + capella_fork_version, + seconds_per_slot, + seconds_per_eth1_block, + min_validator_withdrawability_delay, + shard_committee_period, + eth1_follow_distance, + inactivity_score_bias, + inactivity_score_recovery_rate, + ejection_balance, + min_per_epoch_churn_limit, + churn_limit_quotient, + proposer_score_boost: proposer_score_boost.map(|q| q.value), + deposit_chain_id, + deposit_network_id, + deposit_contract_address, + terminal_total_difficulty, + terminal_block_hash, + terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically, + ..chain_spec.clone() + }) + } +} + +/// A simple wrapper to permit the in-line use of `?`. +fn option_wrapper(f: F) -> Option +where + F: Fn() -> Option, +{ + f() +} diff --git a/primitives/beacon/src/checkpoint.rs b/primitives/beacon/src/checkpoint.rs new file mode 100644 index 00000000..ec062cef --- /dev/null +++ b/primitives/beacon/src/checkpoint.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::{Epoch, Hash256}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Casper FFG checkpoint, used in attestations. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Default, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Checkpoint { + pub epoch: Epoch, + pub root: Hash256, +} diff --git a/primitives/beacon/src/config_and_preset.rs b/primitives/beacon/src/config_and_preset.rs new file mode 100644 index 00000000..82a22175 --- /dev/null +++ b/primitives/beacon/src/config_and_preset.rs @@ -0,0 +1,106 @@ +#[cfg(feature = "std")] +use crate::consts::altair; +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use serde_json::Value; +use superstruct::superstruct; + +/// Fusion of a runtime-config with the compile-time preset values. +/// +/// Mostly useful for the API. +#[superstruct( + variants(Bellatrix, Capella), + variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone)) +)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(untagged)] +pub struct ConfigAndPreset { + #[serde(flatten)] + pub config: Config, + + #[serde(flatten)] + pub base_preset: BasePreset, + #[serde(flatten)] + pub altair_preset: AltairPreset, + #[serde(flatten)] + pub bellatrix_preset: BellatrixPreset, + #[superstruct(only(Capella))] + #[serde(flatten)] + pub capella_preset: CapellaPreset, + /// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks. + #[serde(flatten)] + #[cfg(feature = "std")] + pub extra_fields: BTreeMap, +} + +impl ConfigAndPreset { + #[cfg(feature = "std")] + pub fn from_chain_spec(spec: &ChainSpec, fork_name: Option) -> Self { + let config = Config::from_chain_spec::(spec); + let base_preset = BasePreset::from_chain_spec::(spec); + let altair_preset = AltairPreset::from_chain_spec::(spec); + let bellatrix_preset = BellatrixPreset::from_chain_spec::(spec); + let extra_fields = get_extra_fields(spec); + + if spec.capella_fork_epoch.is_some() + || fork_name.is_none() + || fork_name == Some(ForkName::Capella) + { + let capella_preset = CapellaPreset::from_chain_spec::(spec); + + ConfigAndPreset::Capella(ConfigAndPresetCapella { + config, + base_preset, + altair_preset, + bellatrix_preset, + capella_preset, + extra_fields, + }) + } else { + ConfigAndPreset::Bellatrix(ConfigAndPresetBellatrix { + config, + base_preset, + altair_preset, + bellatrix_preset, + extra_fields, + }) + } + } +} + +/// Get a hashmap of constants to add to the `PresetAndConfig` +#[cfg(feature = "std")] +pub fn get_extra_fields(spec: &ChainSpec) -> BTreeMap { + let hex_string = |value: &[u8]| format!("0x{}", hex::encode(value)).into(); + let u32_hex = |v: u32| hex_string(&v.to_le_bytes()); + let u8_hex = |v: u8| hex_string(&v.to_le_bytes()); + maplit::btreemap! { + "bls_withdrawal_prefix".to_uppercase() => u8_hex(spec.bls_withdrawal_prefix_byte), + "domain_beacon_proposer".to_uppercase() => u32_hex(spec.domain_beacon_proposer), + "domain_beacon_attester".to_uppercase() => u32_hex(spec.domain_beacon_attester), + "domain_blobs_sidecar".to_uppercase() => u32_hex(spec.domain_blobs_sidecar), + "domain_randao".to_uppercase()=> u32_hex(spec.domain_randao), + "domain_deposit".to_uppercase()=> u32_hex(spec.domain_deposit), + "domain_voluntary_exit".to_uppercase() => u32_hex(spec.domain_voluntary_exit), + "domain_selection_proof".to_uppercase() => u32_hex(spec.domain_selection_proof), + "domain_aggregate_and_proof".to_uppercase() => u32_hex(spec.domain_aggregate_and_proof), + "domain_application_mask".to_uppercase()=> u32_hex(spec.domain_application_mask), + "target_aggregators_per_committee".to_uppercase() => + spec.target_aggregators_per_committee.to_string().into(), + "random_subnets_per_validator".to_uppercase() => + spec.random_subnets_per_validator.to_string().into(), + "epochs_per_random_subnet_subscription".to_uppercase() => + spec.epochs_per_random_subnet_subscription.to_string().into(), + "domain_contribution_and_proof".to_uppercase() => + u32_hex(spec.domain_contribution_and_proof), + "domain_sync_committee".to_uppercase() => u32_hex(spec.domain_sync_committee), + "domain_sync_committee_selection_proof".to_uppercase() => + u32_hex(spec.domain_sync_committee_selection_proof), + "sync_committee_subnet_count".to_uppercase() => + altair::SYNC_COMMITTEE_SUBNET_COUNT.to_string().into(), + "target_aggregators_per_sync_subcommittee".to_uppercase() => + altair::TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE.to_string().into(), + } +} diff --git a/primitives/beacon/src/consts.rs b/primitives/beacon/src/consts.rs new file mode 100644 index 00000000..a9377bc3 --- /dev/null +++ b/primitives/beacon/src/consts.rs @@ -0,0 +1,24 @@ +pub mod altair { + pub const TIMELY_SOURCE_FLAG_INDEX: usize = 0; + pub const TIMELY_TARGET_FLAG_INDEX: usize = 1; + pub const TIMELY_HEAD_FLAG_INDEX: usize = 2; + pub const TIMELY_SOURCE_WEIGHT: u64 = 14; + pub const TIMELY_TARGET_WEIGHT: u64 = 26; + pub const TIMELY_HEAD_WEIGHT: u64 = 14; + pub const SYNC_REWARD_WEIGHT: u64 = 2; + pub const PROPOSER_WEIGHT: u64 = 8; + pub const WEIGHT_DENOMINATOR: u64 = 64; + pub const SYNC_COMMITTEE_SUBNET_COUNT: u64 = 4; + pub const TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: u64 = 16; + + pub const PARTICIPATION_FLAG_WEIGHTS: [u64; NUM_FLAG_INDICES] = [ + TIMELY_SOURCE_WEIGHT, + TIMELY_TARGET_WEIGHT, + TIMELY_HEAD_WEIGHT, + ]; + + pub const NUM_FLAG_INDICES: usize = 3; +} +pub mod merge { + pub const INTERVALS_PER_SLOT: u64 = 3; +} diff --git a/primitives/beacon/src/contribution_and_proof.rs b/primitives/beacon/src/contribution_and_proof.rs new file mode 100644 index 00000000..b64b9f78 --- /dev/null +++ b/primitives/beacon/src/contribution_and_proof.rs @@ -0,0 +1,73 @@ +use super::{ + ChainSpec, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, SyncCommitteeContribution, + SyncSelectionProof, +}; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A Validators aggregate sync committee contribution and selection proof. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct ContributionAndProof { + /// The index of the validator that created the sync contribution. + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub aggregator_index: u64, + /// The aggregate contribution. + pub contribution: SyncCommitteeContribution, + /// A proof provided by the validator that permits them to publish on the + /// `sync_committee_contribution_and_proof` gossipsub topic. + pub selection_proof: Signature, +} + +impl ContributionAndProof { + /// Produces a new `ContributionAndProof` with a `selection_proof` generated by signing + /// `SyncAggregatorSelectionData` with `secret_key`. + /// + /// If `selection_proof.is_none()` it will be computed locally. + pub fn from_aggregate( + aggregator_index: u64, + contribution: SyncCommitteeContribution, + selection_proof: Option, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let selection_proof = selection_proof + .unwrap_or_else(|| { + SyncSelectionProof::new::( + contribution.slot, + contribution.subcommittee_index, + secret_key, + fork, + genesis_validators_root, + spec, + ) + }) + .into(); + + Self { + aggregator_index, + contribution, + selection_proof, + } + } +} + +impl SignedRoot for ContributionAndProof {} diff --git a/primitives/beacon/src/deposit.rs b/primitives/beacon/src/deposit.rs new file mode 100644 index 00000000..9c3c9f39 --- /dev/null +++ b/primitives/beacon/src/deposit.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::U33; +use tree_hash_derive::TreeHash; + +pub const DEPOSIT_TREE_DEPTH: usize = 32; + +/// A deposit to potentially become a beacon chain validator. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Deposit { + pub proof: FixedVector, + pub data: DepositData, +} diff --git a/primitives/beacon/src/deposit_data.rs b/primitives/beacon/src/deposit_data.rs new file mode 100644 index 00000000..498c4f63 --- /dev/null +++ b/primitives/beacon/src/deposit_data.rs @@ -0,0 +1,56 @@ +use crate::*; + +use crate::prelude::*; +use bls::{PublicKeyBytes, SignatureBytes}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data supplied by the user to the deposit contract. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct DepositData { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub amount: u64, + pub signature: SignatureBytes, +} + +impl DepositData { + /// Create a `DepositMessage` corresponding to this `DepositData`, for signature verification. + /// + /// Spec v0.12.1 + pub fn as_deposit_message(&self) -> DepositMessage { + DepositMessage { + pubkey: self.pubkey, + withdrawal_credentials: self.withdrawal_credentials, + amount: self.amount, + } + } + + /// Generate the signature for a given DepositData details. + /// + /// Spec v0.12.1 + pub fn create_signature(&self, secret_key: &SecretKey, spec: &ChainSpec) -> SignatureBytes { + let domain = spec.get_deposit_domain(); + let msg = self.as_deposit_message().signing_root(domain); + + SignatureBytes::from(secret_key.sign(msg)) + } +} diff --git a/primitives/beacon/src/deposit_message.rs b/primitives/beacon/src/deposit_message.rs new file mode 100644 index 00000000..0a5a8046 --- /dev/null +++ b/primitives/beacon/src/deposit_message.rs @@ -0,0 +1,33 @@ +use crate::*; + +use crate::prelude::*; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data supplied by the user to the deposit contract. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct DepositMessage { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub amount: u64, +} + +impl SignedRoot for DepositMessage {} diff --git a/primitives/beacon/src/deposit_tree_snapshot.rs b/primitives/beacon/src/deposit_tree_snapshot.rs new file mode 100644 index 00000000..630f7d0b --- /dev/null +++ b/primitives/beacon/src/deposit_tree_snapshot.rs @@ -0,0 +1,77 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::*; +use eth2_hashing::{hash32_concat, ZERO_HASHES}; +use int_to_bytes::int_to_bytes32; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use DEPOSIT_TREE_DEPTH; + +#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct FinalizedExecutionBlock { + pub deposit_root: Hash256, + pub deposit_count: u64, + pub block_hash: Hash256, + pub block_height: u64, +} + +impl From<&DepositTreeSnapshot> for FinalizedExecutionBlock { + fn from(snapshot: &DepositTreeSnapshot) -> Self { + Self { + deposit_root: snapshot.deposit_root, + deposit_count: snapshot.deposit_count, + block_hash: snapshot.execution_block_hash, + block_height: snapshot.execution_block_height, + } + } +} + +#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct DepositTreeSnapshot { + pub finalized: Vec, + pub deposit_root: Hash256, + pub deposit_count: u64, + pub execution_block_hash: Hash256, + pub execution_block_height: u64, +} + +impl Default for DepositTreeSnapshot { + fn default() -> Self { + let mut result = Self { + finalized: vec![], + deposit_root: Hash256::default(), + deposit_count: 0, + execution_block_hash: Hash256::zero(), + execution_block_height: 0, + }; + // properly set the empty deposit root + result.deposit_root = result.calculate_root().unwrap(); + result + } +} + +impl DepositTreeSnapshot { + // Calculates the deposit tree root from the hashes in the snapshot + pub fn calculate_root(&self) -> Option { + let mut size = self.deposit_count; + let mut index = self.finalized.len(); + let mut deposit_root = [0; 32]; + for height in 0..DEPOSIT_TREE_DEPTH { + deposit_root = if (size & 1) == 1 { + index = index.checked_sub(1)?; + hash32_concat(self.finalized.get(index)?.as_bytes(), &deposit_root) + } else { + hash32_concat(&deposit_root, ZERO_HASHES.get(height)?) + }; + size /= 2; + } + // add mix-in-length + deposit_root = hash32_concat(&deposit_root, &int_to_bytes32(self.deposit_count)); + + Some(Hash256::from_slice(&deposit_root)) + } + pub fn is_valid(&self) -> bool { + self.calculate_root() + .map_or(false, |calculated| self.deposit_root == calculated) + } +} diff --git a/primitives/beacon/src/enr_fork_id.rs b/primitives/beacon/src/enr_fork_id.rs new file mode 100644 index 00000000..c5ea3be1 --- /dev/null +++ b/primitives/beacon/src/enr_fork_id.rs @@ -0,0 +1,33 @@ +use crate::Epoch; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Specifies a fork which allows nodes to identify each other on the network. This fork is used in +/// a nodes local ENR. +/// +/// Spec v0.11 +#[derive( + Debug, + Clone, + PartialEq, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct EnrForkId { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub fork_digest: [u8; 4], + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub next_fork_version: [u8; 4], + pub next_fork_epoch: Epoch, +} diff --git a/primitives/beacon/src/eth1_data.rs b/primitives/beacon/src/eth1_data.rs new file mode 100644 index 00000000..927a7140 --- /dev/null +++ b/primitives/beacon/src/eth1_data.rs @@ -0,0 +1,32 @@ +use super::Hash256; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Contains data obtained from the Eth1 chain. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Clone, + Default, + Eq, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Eth1Data { + pub deposit_root: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub deposit_count: u64, + pub block_hash: Hash256, +} diff --git a/primitives/beacon/src/eth_spec.rs b/primitives/beacon/src/eth_spec.rs new file mode 100644 index 00000000..db5572e7 --- /dev/null +++ b/primitives/beacon/src/eth_spec.rs @@ -0,0 +1,389 @@ +use crate::*; + +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use core::fmt::{self, Debug}; +use core::str::FromStr; +use safe_arith::SafeArith; +use serde::{Deserialize, Serialize}; +use ssz_types::typenum::{ + bit::B0, UInt, Unsigned, U0, U1024, U1048576, U1073741824, U1099511627776, U128, U16, + U16777216, U2, U2048, U256, U32, U4, U4096, U512, U625, U64, U65536, U8, U8192, +}; + +pub type U5000 = UInt, B0>, B0>; // 625 * 8 = 5000 + +const MAINNET: &str = "mainnet"; +const MINIMAL: &str = "minimal"; +pub const GNOSIS: &str = "gnosis"; + +/// Used to identify one of the `EthSpec` instances defined here. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum EthSpecId { + Mainnet, + Minimal, + Gnosis, +} + +impl FromStr for EthSpecId { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + MAINNET => Ok(EthSpecId::Mainnet), + MINIMAL => Ok(EthSpecId::Minimal), + GNOSIS => Ok(EthSpecId::Gnosis), + _ => Err(format!("Unknown eth spec: {}", s)), + } + } +} + +impl fmt::Display for EthSpecId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + EthSpecId::Mainnet => MAINNET, + EthSpecId::Minimal => MINIMAL, + EthSpecId::Gnosis => GNOSIS, + }; + write!(f, "{}", s) + } +} + +pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Eq { + /* + * Constants + */ + type GenesisEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type JustificationBitsLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default; + type SubnetBitfieldLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default; + /* + * Misc + */ + type MaxValidatorsPerCommittee: Unsigned + Clone + Sync + Send + Debug + PartialEq + Eq; + /* + * Time parameters + */ + type SlotsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type SlotsPerHistoricalRoot: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * State list lengths + */ + type EpochsPerHistoricalVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * Max operations per block + */ + type MaxProposerSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxAttesterSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxDeposits: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxVoluntaryExits: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Altair + */ + type SyncCommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The number of `sync_committee` subnets. + type SyncCommitteeSubnetCount: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerSyncCommitteePeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Merge + */ + type MaxBytesPerTransaction: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxTransactionsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BytesPerLogsBloom: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type GasLimitDenominator: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MinGasLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxExtraDataBytes: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Capella + */ + type MaxBlsToExecutionChanges: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxWithdrawalsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * Derived values (set these CAREFULLY) + */ + /// The length of the `{previous,current}_epoch_attestations` lists. + /// + /// Must be set to `MaxAttestations * SlotsPerEpoch` + // NOTE: we could safely instantiate these by using type-level arithmetic, but doing + // so adds ~25s to the time required to type-check this crate + type MaxPendingAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The length of `eth1_data_votes`. + /// + /// Must be set to `EpochsPerEth1VotingPeriod * SlotsPerEpoch` + type SlotsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The size of `sync_subcommittees`. + /// + /// Must be set to `SyncCommitteeSize / SyncCommitteeSubnetCount`. + type SyncSubcommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + + fn default_spec() -> ChainSpec; + + fn spec_name() -> EthSpecId; + + fn genesis_epoch() -> Epoch { + Epoch::new(Self::GenesisEpoch::to_u64()) + } + + /// Return the number of committees per slot. + /// + /// Note: the number of committees per slot is constant in each epoch, and depends only on + /// the `active_validator_count` during the slot's epoch. + /// + /// Spec v0.12.1 + fn get_committee_count_per_slot( + active_validator_count: usize, + spec: &ChainSpec, + ) -> Result { + Self::get_committee_count_per_slot_with( + active_validator_count, + spec.max_committees_per_slot, + spec.target_committee_size, + ) + } + + fn get_committee_count_per_slot_with( + active_validator_count: usize, + max_committees_per_slot: usize, + target_committee_size: usize, + ) -> Result { + let slots_per_epoch = Self::SlotsPerEpoch::to_usize(); + + Ok(core::cmp::max( + 1, + core::cmp::min( + max_committees_per_slot, + active_validator_count + .safe_div(slots_per_epoch)? + .safe_div(target_committee_size)?, + ), + )) + } + + /// Returns the minimum number of validators required for this spec. + /// + /// This is the _absolute_ minimum, the number required to make the chain operate in the most + /// basic sense. This count is not required to provide any security guarantees regarding + /// decentralization, entropy, etc. + fn minimum_validator_count() -> usize { + Self::SlotsPerEpoch::to_usize() + } + + /// Returns the `SLOTS_PER_EPOCH` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_epoch() -> u64 { + Self::SlotsPerEpoch::to_u64() + } + + /// Returns the `SLOTS_PER_HISTORICAL_ROOT` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_historical_root() -> usize { + Self::SlotsPerHistoricalRoot::to_usize() + } + + /// Returns the `EPOCHS_PER_HISTORICAL_VECTOR` constant for this specification. + /// + /// Spec v0.12.1 + fn epochs_per_historical_vector() -> usize { + Self::EpochsPerHistoricalVector::to_usize() + } + + /// Returns the `SLOTS_PER_ETH1_VOTING_PERIOD` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_eth1_voting_period() -> usize { + Self::SlotsPerEth1VotingPeriod::to_usize() + } + + /// Returns the `SYNC_COMMITTEE_SIZE` constant for this specification. + fn sync_committee_size() -> usize { + Self::SyncCommitteeSize::to_usize() + } + + /// Returns the `SYNC_COMMITTEE_SIZE / SyncCommitteeSubnetCount`. + fn sync_subcommittee_size() -> usize { + Self::SyncSubcommitteeSize::to_usize() + } + + /// Returns the `MAX_BYTES_PER_TRANSACTION` constant for this specification. + fn max_bytes_per_transaction() -> usize { + Self::MaxBytesPerTransaction::to_usize() + } + + /// Returns the `MAX_TRANSACTIONS_PER_PAYLOAD` constant for this specification. + fn max_transactions_per_payload() -> usize { + Self::MaxTransactionsPerPayload::to_usize() + } + + /// Returns the `MAX_EXTRA_DATA_BYTES` constant for this specification. + fn max_extra_data_bytes() -> usize { + Self::MaxExtraDataBytes::to_usize() + } + + /// Returns the `BYTES_PER_LOGS_BLOOM` constant for this specification. + fn bytes_per_logs_bloom() -> usize { + Self::BytesPerLogsBloom::to_usize() + } + + /// Returns the `MAX_BLS_TO_EXECUTION_CHANGES` constant for this specification. + fn max_bls_to_execution_changes() -> usize { + Self::MaxBlsToExecutionChanges::to_usize() + } + + /// Returns the `MAX_WITHDRAWALS_PER_PAYLOAD` constant for this specification. + fn max_withdrawals_per_payload() -> usize { + Self::MaxWithdrawalsPerPayload::to_usize() + } +} + +/// Macro to inherit some type values from another EthSpec. +#[macro_export] +macro_rules! params_from_eth_spec { + ($spec_ty:ty { $($ty_name:ident),+ }) => { + $(type $ty_name = <$spec_ty as EthSpec>::$ty_name;)+ + } +} + +/// Ethereum Foundation specifications. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct MainnetEthSpec; + +impl EthSpec for MainnetEthSpec { + type JustificationBitsLength = U4; + type SubnetBitfieldLength = U64; + type MaxValidatorsPerCommittee = U2048; + type GenesisEpoch = U0; + type SlotsPerEpoch = U32; + type EpochsPerEth1VotingPeriod = U64; + type SlotsPerHistoricalRoot = U8192; + type EpochsPerHistoricalVector = U65536; + type EpochsPerSlashingsVector = U8192; + type HistoricalRootsLimit = U16777216; + type ValidatorRegistryLimit = U1099511627776; + type MaxProposerSlashings = U16; + type MaxAttesterSlashings = U2; + type MaxAttestations = U128; + type MaxDeposits = U16; + type MaxVoluntaryExits = U16; + type SyncCommitteeSize = U512; + type SyncCommitteeSubnetCount = U4; + type MaxBytesPerTransaction = U1073741824; // 1,073,741,824 + type MaxTransactionsPerPayload = U1048576; // 1,048,576 + type BytesPerLogsBloom = U256; + type GasLimitDenominator = U1024; + type MinGasLimit = U5000; + type MaxExtraDataBytes = U32; + type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch + type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch + type MaxBlsToExecutionChanges = U16; + type MaxWithdrawalsPerPayload = U16; + type EpochsPerSyncCommitteePeriod = U256; + + fn default_spec() -> ChainSpec { + ChainSpec::mainnet() + } + + fn spec_name() -> EthSpecId { + EthSpecId::Mainnet + } +} + +/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct MinimalEthSpec; + +impl EthSpec for MinimalEthSpec { + type SlotsPerEpoch = U8; + type EpochsPerEth1VotingPeriod = U4; + type SlotsPerHistoricalRoot = U64; + type EpochsPerHistoricalVector = U64; + type EpochsPerSlashingsVector = U64; + type SyncCommitteeSize = U32; + type SyncSubcommitteeSize = U8; // 32 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch + type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch + type MaxWithdrawalsPerPayload = U4; + type EpochsPerSyncCommitteePeriod = U8; + + params_from_eth_spec!(MainnetEthSpec { + JustificationBitsLength, + SubnetBitfieldLength, + SyncCommitteeSubnetCount, + MaxValidatorsPerCommittee, + GenesisEpoch, + HistoricalRootsLimit, + ValidatorRegistryLimit, + MaxProposerSlashings, + MaxAttesterSlashings, + MaxAttestations, + MaxDeposits, + MaxVoluntaryExits, + MaxBytesPerTransaction, + MaxTransactionsPerPayload, + BytesPerLogsBloom, + GasLimitDenominator, + MinGasLimit, + MaxExtraDataBytes, + MaxBlsToExecutionChanges + }); + + fn default_spec() -> ChainSpec { + ChainSpec::minimal() + } + + fn spec_name() -> EthSpecId { + EthSpecId::Minimal + } +} + +/// Gnosis Beacon Chain specifications. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct GnosisEthSpec; + +impl EthSpec for GnosisEthSpec { + type JustificationBitsLength = U4; + type SubnetBitfieldLength = U64; + type MaxValidatorsPerCommittee = U2048; + type GenesisEpoch = U0; + type SlotsPerEpoch = U16; + type EpochsPerEth1VotingPeriod = U64; + type SlotsPerHistoricalRoot = U8192; + type EpochsPerHistoricalVector = U65536; + type EpochsPerSlashingsVector = U8192; + type HistoricalRootsLimit = U16777216; + type ValidatorRegistryLimit = U1099511627776; + type MaxProposerSlashings = U16; + type MaxAttesterSlashings = U2; + type MaxAttestations = U128; + type MaxDeposits = U16; + type MaxVoluntaryExits = U16; + type SyncCommitteeSize = U512; + type SyncCommitteeSubnetCount = U4; + type MaxBytesPerTransaction = U1073741824; // 1,073,741,824 + type MaxTransactionsPerPayload = U1048576; // 1,048,576 + type BytesPerLogsBloom = U256; + type GasLimitDenominator = U1024; + type MinGasLimit = U5000; + type MaxExtraDataBytes = U32; + type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U2048; // 128 max attestations * 16 slots per epoch + type SlotsPerEth1VotingPeriod = U1024; // 64 epochs * 16 slots per epoch + type MaxBlsToExecutionChanges = U16; + type MaxWithdrawalsPerPayload = U16; + type EpochsPerSyncCommitteePeriod = U512; + + fn default_spec() -> ChainSpec { + ChainSpec::gnosis() + } + + fn spec_name() -> EthSpecId { + EthSpecId::Gnosis + } +} diff --git a/primitives/beacon/src/execution_block_hash.rs b/primitives/beacon/src/execution_block_hash.rs new file mode 100644 index 00000000..e105265d --- /dev/null +++ b/primitives/beacon/src/execution_block_hash.rs @@ -0,0 +1,109 @@ +use crate::prelude::*; +use crate::Hash256; +use core::fmt; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; + +#[derive( + Default, + Clone, + Copy, + Serialize, + Deserialize, + Eq, + PartialEq, + Hash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(Debug = "transparent")] +#[serde(transparent)] +pub struct ExecutionBlockHash(Hash256); + +impl ExecutionBlockHash { + pub fn zero() -> Self { + Self(Hash256::zero()) + } + + pub fn repeat_byte(b: u8) -> Self { + Self(Hash256::repeat_byte(b)) + } + + pub fn from_root(root: Hash256) -> Self { + Self(root) + } + + pub fn into_root(self) -> Hash256 { + self.0 + } +} + +impl Encode for ExecutionBlockHash { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for ExecutionBlockHash { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Hash256::from_ssz_bytes(bytes).map(Self) + } +} + +impl tree_hash::TreeHash for ExecutionBlockHash { + fn tree_hash_type() -> tree_hash::TreeHashType { + Hash256::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + Hash256::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} + +impl core::str::FromStr for ExecutionBlockHash { + type Err = String; + + fn from_str(s: &str) -> Result { + Hash256::from_str(s) + .map(Self) + .map_err(|e| format!("{:?}", e)) + } +} + +impl fmt::Display for ExecutionBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/primitives/beacon/src/execution_block_header.rs b/primitives/beacon/src/execution_block_header.rs new file mode 100644 index 00000000..a2158c00 --- /dev/null +++ b/primitives/beacon/src/execution_block_header.rs @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Reth Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +use crate::prelude::*; +use crate::{Address, EthSpec, ExecutionPayloadRef, Hash256, Hash64, Uint256}; + +/// Execution block header as used for RLP encoding and Keccak hashing. +/// +/// Credit to Reth for the type definition. +#[derive(Debug, Clone, PartialEq, Eq, Hash, ScaleDecode, ScaleEncode, TypeInfo)] +pub struct ExecutionBlockHeader { + pub parent_hash: Hash256, + pub ommers_hash: Hash256, + pub beneficiary: Address, + pub state_root: Hash256, + pub transactions_root: Hash256, + pub receipts_root: Hash256, + pub logs_bloom: Vec, + pub difficulty: Uint256, + pub number: Uint256, + pub gas_limit: Uint256, + pub gas_used: Uint256, + pub timestamp: u64, + pub extra_data: Vec, + pub mix_hash: Hash256, + pub nonce: Hash64, + pub base_fee_per_gas: Uint256, + pub withdrawals_root: Option, +} + +impl ExecutionBlockHeader { + pub fn from_payload( + payload: ExecutionPayloadRef, + rlp_empty_list_root: Hash256, + rlp_transactions_root: Hash256, + rlp_withdrawals_root: Option, + ) -> Self { + // Most of these field mappings are defined in EIP-3675 except for `mixHash`, which is + // defined in EIP-4399. + ExecutionBlockHeader { + parent_hash: payload.parent_hash().into_root(), + ommers_hash: rlp_empty_list_root, + beneficiary: payload.fee_recipient(), + state_root: payload.state_root(), + transactions_root: rlp_transactions_root, + receipts_root: payload.receipts_root(), + logs_bloom: payload.logs_bloom().clone().into(), + difficulty: Uint256::zero(), + number: payload.block_number().into(), + gas_limit: payload.gas_limit().into(), + gas_used: payload.gas_used().into(), + timestamp: payload.timestamp(), + extra_data: payload.extra_data().clone().into(), + mix_hash: payload.prev_randao(), + nonce: Hash64::zero(), + base_fee_per_gas: payload.base_fee_per_gas(), + withdrawals_root: rlp_withdrawals_root, + } + } +} diff --git a/primitives/beacon/src/execution_payload.rs b/primitives/beacon/src/execution_payload.rs new file mode 100644 index 00000000..91fa4116 --- /dev/null +++ b/primitives/beacon/src/execution_payload.rs @@ -0,0 +1,167 @@ +use crate::prelude::*; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +pub type Transaction = VariableList; +pub type Transactions = VariableList< + Transaction<::MaxBytesPerTransaction>, + ::MaxTransactionsPerPayload, +>; + +pub type Withdrawals = VariableList::MaxWithdrawalsPerPayload>; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Default, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + map_into(FullPayload, BlindedPayload), + map_ref_into(ExecutionPayloadHeader) +)] +#[derive( + Debug, + Clone, + Serialize, + Encode, + Deserialize, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct ExecutionPayload { + #[superstruct(getter(copy))] + pub parent_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub fee_recipient: Address, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(getter(copy))] + pub receipts_root: Hash256, + #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] + pub logs_bloom: FixedVector, + #[superstruct(getter(copy))] + pub prev_randao: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub block_number: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_used: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + #[serde(with = "eth2_serde_utils::quoted_u256")] + #[superstruct(getter(copy))] + pub base_fee_per_gas: Uint256, + #[superstruct(getter(copy))] + pub block_hash: ExecutionBlockHash, + #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] + pub transactions: Transactions, + #[superstruct(only(Capella))] + pub withdrawals: Withdrawals, +} + +impl<'a, T: EthSpec> ExecutionPayloadRef<'a, T> { + // this emulates clone on a normal reference type + pub fn clone_from_ref(&self) -> ExecutionPayload { + map_execution_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecutionPayload { + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayload: {fork_name}", + ))), + ForkName::Merge => ExecutionPayloadMerge::from_ssz_bytes(bytes).map(Self::Merge), + ForkName::Capella => ExecutionPayloadCapella::from_ssz_bytes(bytes).map(Self::Capella), + } + } + + #[allow(clippy::integer_arithmetic)] + /// Returns the maximum size of an execution payload. + pub fn max_execution_payload_merge_size() -> usize { + // Fixed part + ExecutionPayloadMerge::::default().as_ssz_bytes().len() + // Max size of variable length `extra_data` field + + (T::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) + } + + #[allow(clippy::integer_arithmetic)] + /// Returns the maximum size of an execution payload. + pub fn max_execution_payload_capella_size() -> usize { + // Fixed part + ExecutionPayloadCapella::::default().as_ssz_bytes().len() + // Max size of variable length `extra_data` field + + (T::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) + // Max size of variable length `withdrawals` field + + (T::max_withdrawals_per_payload() * ::ssz_fixed_len()) + } +} + +#[cfg(feature = "std")] +impl ForkVersionDeserialize for ExecutionPayload { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!("ExecutionPayload failed to deserialize: {:?}", e)) + }; + + Ok(match fork_name { + ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Base | ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayload failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/execution_payload_header.rs b/primitives/beacon/src/execution_payload_header.rs new file mode 100644 index 00000000..7652d265 --- /dev/null +++ b/primitives/beacon/src/execution_payload_header.rs @@ -0,0 +1,254 @@ +use crate::prelude::*; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::Decode; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; +use BeaconStateError; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Default, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq, TreeHash), tree_hash(enum_behaviour = "transparent")), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct ExecutionPayloadHeader { + #[superstruct(getter(copy))] + pub parent_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub fee_recipient: Address, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(getter(copy))] + pub receipts_root: Hash256, + #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] + pub logs_bloom: FixedVector, + #[superstruct(getter(copy))] + pub prev_randao: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub block_number: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_used: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + #[serde(with = "eth2_serde_utils::quoted_u256")] + #[superstruct(getter(copy))] + pub base_fee_per_gas: Uint256, + #[superstruct(getter(copy))] + pub block_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub transactions_root: Hash256, + #[superstruct(only(Capella))] + #[superstruct(getter(copy))] + pub withdrawals_root: Hash256, +} + +impl ExecutionPayloadHeader { + pub fn transactions(&self) -> Option<&Transactions> { + None + } + + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayloadHeader: {fork_name}", + ))), + ForkName::Merge => ExecutionPayloadHeaderMerge::from_ssz_bytes(bytes).map(Self::Merge), + ForkName::Capella => { + ExecutionPayloadHeaderCapella::from_ssz_bytes(bytes).map(Self::Capella) + } + } + } +} + +impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> { + pub fn is_default_with_zero_roots(self) -> bool { + map_execution_payload_header_ref!(&'a _, self, |inner, cons| { + cons(inner); + *inner == Default::default() + }) + } +} + +impl ExecutionPayloadHeaderMerge { + pub fn upgrade_to_capella(&self) -> ExecutionPayloadHeaderCapella { + ExecutionPayloadHeaderCapella { + parent_hash: self.parent_hash, + fee_recipient: self.fee_recipient, + state_root: self.state_root, + receipts_root: self.receipts_root, + logs_bloom: self.logs_bloom.clone(), + prev_randao: self.prev_randao, + block_number: self.block_number, + gas_limit: self.gas_limit, + gas_used: self.gas_used, + timestamp: self.timestamp, + extra_data: self.extra_data.clone(), + base_fee_per_gas: self.base_fee_per_gas, + block_hash: self.block_hash, + transactions_root: self.transactions_root, + withdrawals_root: Hash256::zero(), + } + } +} + +impl<'a, T: EthSpec> From<&'a ExecutionPayloadMerge> for ExecutionPayloadHeaderMerge { + fn from(payload: &'a ExecutionPayloadMerge) -> Self { + Self { + 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.clone(), + 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.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + } + } +} +impl<'a, T: EthSpec> From<&'a ExecutionPayloadCapella> for ExecutionPayloadHeaderCapella { + fn from(payload: &'a ExecutionPayloadCapella) -> Self { + Self { + 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.clone(), + 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.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + withdrawals_root: payload.withdrawals.tree_hash_root(), + } + } +} + +// These impls are required to work around an inelegance in `to_execution_payload_header`. +// They only clone headers so they should be relatively cheap. +impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderMerge { + fn from(payload: &'a Self) -> Self { + payload.clone() + } +} + +impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderCapella { + fn from(payload: &'a Self) -> Self { + payload.clone() + } +} + +impl<'a, T: EthSpec> From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayloadRef<'a, T>) -> Self { + map_execution_payload_ref_into_execution_payload_header!( + &'a _, + payload, + |inner, cons| cons(inner.into()) + ) + } +} + +impl TryFrom> for ExecutionPayloadHeaderMerge { + type Error = BeaconStateError; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::Merge(execution_payload_header) => Ok(execution_payload_header), + _ => Err(BeaconStateError::IncorrectStateVariant), + } + } +} +impl TryFrom> for ExecutionPayloadHeaderCapella { + type Error = BeaconStateError; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::Capella(execution_payload_header) => { + Ok(execution_payload_header) + } + _ => Err(BeaconStateError::IncorrectStateVariant), + } + } +} + +#[cfg(feature = "std")] +impl ForkVersionDeserialize for ExecutionPayloadHeader { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Base | ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/fork.rs b/primitives/beacon/src/fork.rs new file mode 100644 index 00000000..779d8e33 --- /dev/null +++ b/primitives/beacon/src/fork.rs @@ -0,0 +1,45 @@ +use crate::Epoch; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Specifies a fork of the `BeaconChain`, to prevent replay attacks. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Fork { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub previous_version: [u8; 4], + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub current_version: [u8; 4], + pub epoch: Epoch, +} + +impl Fork { + /// Return the fork version of the given ``epoch``. + /// + /// Spec v0.12.1 + pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] { + if epoch < self.epoch { + return self.previous_version; + } + self.current_version + } +} diff --git a/primitives/beacon/src/fork_data.rs b/primitives/beacon/src/fork_data.rs new file mode 100644 index 00000000..3174faf3 --- /dev/null +++ b/primitives/beacon/src/fork_data.rs @@ -0,0 +1,32 @@ +use crate::{Hash256, SignedRoot}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Specifies a fork of the `BeaconChain`, to prevent replay attacks. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ForkData { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub current_version: [u8; 4], + pub genesis_validators_root: Hash256, +} + +impl SignedRoot for ForkData {} diff --git a/primitives/beacon/src/fork_name.rs b/primitives/beacon/src/fork_name.rs new file mode 100644 index 00000000..e640c22a --- /dev/null +++ b/primitives/beacon/src/fork_name.rs @@ -0,0 +1,174 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::{ChainSpec, Epoch}; +use core::convert::TryFrom; +use core::fmt::{self, Display, Formatter}; +use core::str::FromStr; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(try_from = "String")] +#[serde(into = "String")] +pub enum ForkName { + Base, + Altair, + Merge, + Capella, +} + +impl ForkName { + pub fn list_all() -> Vec { + vec![ + ForkName::Base, + ForkName::Altair, + ForkName::Merge, + ForkName::Capella, + ] + } + + /// Set the activation slots in the given `ChainSpec` so that the fork named by `self` + /// is the only fork in effect from genesis. + pub fn make_genesis_spec(&self, mut spec: ChainSpec) -> ChainSpec { + // Assumes GENESIS_EPOCH = 0, which is safe because it's a constant. + match self { + ForkName::Base => { + spec.altair_fork_epoch = None; + spec.bellatrix_fork_epoch = None; + spec.capella_fork_epoch = None; + spec + } + ForkName::Altair => { + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = None; + spec.capella_fork_epoch = None; + spec + } + ForkName::Merge => { + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = None; + spec + } + ForkName::Capella => { + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec + } + } + } + + /// Return the name of the fork immediately prior to the current one. + /// + /// If `self` is `ForkName::Base` then `Base` is returned. + pub fn previous_fork(self) -> Option { + match self { + ForkName::Base => None, + ForkName::Altair => Some(ForkName::Base), + ForkName::Merge => Some(ForkName::Altair), + ForkName::Capella => Some(ForkName::Merge), + } + } + + /// Return the name of the fork immediately after the current one. + /// + /// If `self` is the last known fork and has no successor, `None` is returned. + pub fn next_fork(self) -> Option { + match self { + ForkName::Base => Some(ForkName::Altair), + ForkName::Altair => Some(ForkName::Merge), + ForkName::Merge => Some(ForkName::Capella), + ForkName::Capella => None, + } + } +} + +/// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`. +/// +/// The `$body` expression is where the magic happens. The macro allows us to achieve polymorphism +/// in the return type, which is not usually possible in Rust without trait objects. +/// +/// E.g. you could call `map_fork_name!(fork, BeaconBlock, serde_json::from_str(s))` to decode +/// different `BeaconBlock` variants depending on the value of `fork`. Note how the type of the body +/// will change between `BeaconBlockBase` and `BeaconBlockAltair` depending on which branch is +/// taken, the important thing is that they are re-unified by injecting them back into the +/// `BeaconBlock` parent enum. +/// +/// If you would also like to extract additional data alongside the superstruct type, use +/// the more flexible `map_fork_name_with` macro. +#[macro_export] +macro_rules! map_fork_name { + ($fork_name:expr, $t:tt, $body:expr) => { + map_fork_name_with!($fork_name, $t, { ($body, ()) }).0 + }; +} + +/// Map a fork name into a tuple of `(t, extra)` where `t` is a superstruct type. +#[macro_export] +macro_rules! map_fork_name_with { + ($fork_name:expr, $t:tt, $body:block) => { + match $fork_name { + ForkName::Base => { + let (value, extra_data) = $body; + ($t::Base(value), extra_data) + } + ForkName::Altair => { + let (value, extra_data) = $body; + ($t::Altair(value), extra_data) + } + ForkName::Merge => { + let (value, extra_data) = $body; + ($t::Merge(value), extra_data) + } + ForkName::Capella => { + let (value, extra_data) = $body; + ($t::Capella(value), extra_data) + } + } + }; +} + +impl FromStr for ForkName { + type Err = String; + + fn from_str(fork_name: &str) -> Result { + Ok(match fork_name.to_lowercase().as_ref() { + "phase0" | "base" => ForkName::Base, + "altair" => ForkName::Altair, + "bellatrix" | "merge" => ForkName::Merge, + "capella" => ForkName::Capella, + _ => return Err(format!("unknown fork name: {}", fork_name)), + }) + } +} + +impl Display for ForkName { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ForkName::Base => Display::fmt(&"phase0", f), + ForkName::Altair => Display::fmt(&"altair", f), + ForkName::Merge => Display::fmt(&"bellatrix", f), + ForkName::Capella => Display::fmt(&"capella", f), + } + } +} + +impl From for String { + fn from(fork: ForkName) -> String { + fork.to_string() + } +} + +impl TryFrom for ForkName { + type Error = String; + + fn try_from(s: String) -> Result { + Self::from_str(&s) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InconsistentFork { + pub fork_at_slot: ForkName, + pub object_fork: ForkName, +} diff --git a/primitives/beacon/src/fork_versioned_response.rs b/primitives/beacon/src/fork_versioned_response.rs new file mode 100644 index 00000000..30d5c0c3 --- /dev/null +++ b/primitives/beacon/src/fork_versioned_response.rs @@ -0,0 +1,91 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::ForkName; +use serde::Serialize; +#[cfg(feature = "std")] +use serde::{de::DeserializeOwned, Deserialize, Deserializer}; +#[cfg(feature = "std")] +use serde_json::value::Value; + +// Deserialize is only implemented for types that implement ForkVersionDeserialize +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct ExecutionOptimisticForkVersionedResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + pub execution_optimistic: Option, + pub data: T, +} + +#[cfg(feature = "std")] +impl<'de, F> serde::Deserialize<'de> for ExecutionOptimisticForkVersionedResponse +where + F: ForkVersionDeserialize, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + version: Option, + execution_optimistic: Option, + data: serde_json::Value, + } + + let helper = Helper::deserialize(deserializer)?; + let data = match helper.version { + Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?, + None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?, + }; + + Ok(ExecutionOptimisticForkVersionedResponse { + version: helper.version, + execution_optimistic: helper.execution_optimistic, + data, + }) + } +} + +#[cfg(feature = "std")] +pub trait ForkVersionDeserialize: Sized + DeserializeOwned { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result; +} + +// Deserialize is only implemented for types that implement ForkVersionDeserialize +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct ForkVersionedResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + pub data: T, +} + +#[cfg(feature = "std")] +impl<'de, F> serde::Deserialize<'de> for ForkVersionedResponse +where + F: ForkVersionDeserialize, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + version: Option, + data: serde_json::Value, + } + + let helper = Helper::deserialize(deserializer)?; + let data = match helper.version { + Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?, + None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?, + }; + + Ok(ForkVersionedResponse { + version: helper.version, + data, + }) + } +} diff --git a/primitives/beacon/src/graffiti.rs b/primitives/beacon/src/graffiti.rs new file mode 100644 index 00000000..6631652e --- /dev/null +++ b/primitives/beacon/src/graffiti.rs @@ -0,0 +1,173 @@ +use crate::prelude::*; +use core::fmt; +use core::str::FromStr; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use ssz::{Decode, DecodeError, Encode}; +use tree_hash::{PackedEncoding, TreeHash}; + +pub const GRAFFITI_BYTES_LEN: usize = 32; + +/// The 32-byte `graffiti` field on a beacon block. +#[derive( + Default, + Debug, + PartialEq, + Hash, + Clone, + Copy, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Graffiti(#[serde(with = "serde_graffiti")] pub [u8; GRAFFITI_BYTES_LEN]); + +impl fmt::Display for Graffiti { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", eth2_serde_utils::hex::encode(self.0)) + } +} + +impl From<[u8; GRAFFITI_BYTES_LEN]> for Graffiti { + fn from(bytes: [u8; GRAFFITI_BYTES_LEN]) -> Self { + Self(bytes) + } +} + +impl Into<[u8; GRAFFITI_BYTES_LEN]> for Graffiti { + fn into(self) -> [u8; GRAFFITI_BYTES_LEN] { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Default)] +#[serde(transparent)] +pub struct GraffitiString(String); + +impl FromStr for GraffitiString { + type Err = String; + + fn from_str(s: &str) -> Result { + if s.as_bytes().len() > GRAFFITI_BYTES_LEN { + return Err(format!( + "Graffiti exceeds max length {}", + GRAFFITI_BYTES_LEN + )); + } + Ok(Self(s.to_string())) + } +} + +impl<'de> Deserialize<'de> for GraffitiString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = serde::Deserialize::deserialize(deserializer)?; + GraffitiString::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Into for GraffitiString { + fn into(self) -> Graffiti { + let graffiti_bytes = self.0.as_bytes(); + let mut graffiti = [0; GRAFFITI_BYTES_LEN]; + + let graffiti_len = core::cmp::min(graffiti_bytes.len(), GRAFFITI_BYTES_LEN); + + // Copy the provided bytes over. + // + // Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`. + graffiti + .get_mut(..graffiti_len) + .expect("graffiti_len <= GRAFFITI_BYTES_LEN") + .copy_from_slice(graffiti_bytes); + graffiti.into() + } +} + +pub mod serde_graffiti { + use super::*; + + pub fn serialize(bytes: &[u8; GRAFFITI_BYTES_LEN], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(ð2_serde_utils::hex::encode(bytes)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; GRAFFITI_BYTES_LEN], D::Error> + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + + let bytes = eth2_serde_utils::hex::decode(&s).map_err(D::Error::custom)?; + + if bytes.len() != GRAFFITI_BYTES_LEN { + return Err(D::Error::custom(format!( + "incorrect byte length {}, expected {}", + bytes.len(), + GRAFFITI_BYTES_LEN + ))); + } + + let mut array = [0; GRAFFITI_BYTES_LEN]; + array[..].copy_from_slice(&bytes); + + Ok(array) + } +} + +impl Encode for Graffiti { + fn is_ssz_fixed_len() -> bool { + <[u8; GRAFFITI_BYTES_LEN] as Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; GRAFFITI_BYTES_LEN] as Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for Graffiti { + fn is_ssz_fixed_len() -> bool { + <[u8; GRAFFITI_BYTES_LEN] as Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; GRAFFITI_BYTES_LEN] as Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + <[u8; GRAFFITI_BYTES_LEN]>::from_ssz_bytes(bytes).map(Self) + } +} + +impl TreeHash for Graffiti { + fn tree_hash_type() -> tree_hash::TreeHashType { + <[u8; GRAFFITI_BYTES_LEN]>::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + <[u8; GRAFFITI_BYTES_LEN]>::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} diff --git a/primitives/beacon/src/historical_batch.rs b/primitives/beacon/src/historical_batch.rs new file mode 100644 index 00000000..c8ac500b --- /dev/null +++ b/primitives/beacon/src/historical_batch.rs @@ -0,0 +1,30 @@ +use crate::*; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::FixedVector; +use tree_hash_derive::TreeHash; + +/// Historical block and state roots. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct HistoricalBatch { + pub block_roots: FixedVector, + pub state_roots: FixedVector, +} diff --git a/primitives/beacon/src/historical_summary.rs b/primitives/beacon/src/historical_summary.rs new file mode 100644 index 00000000..bf840703 --- /dev/null +++ b/primitives/beacon/src/historical_summary.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::Hash256; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch` +/// making the two hash_tree_root-compatible. This struct is introduced into the beacon state +/// in the Capella hard fork. +/// +/// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary +#[derive( + Debug, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Clone, + Copy, + Default, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct HistoricalSummary { + block_summary_root: Hash256, + state_summary_root: Hash256, +} diff --git a/primitives/beacon/src/indexed_attestation.rs b/primitives/beacon/src/indexed_attestation.rs new file mode 100644 index 00000000..7ac2efaf --- /dev/null +++ b/primitives/beacon/src/indexed_attestation.rs @@ -0,0 +1,105 @@ +use crate::prelude::*; +use crate::{AggregateSignature, AttestationData, EthSpec, VariableList}; +use core::hash::{Hash, Hasher}; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::Encode; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Details an attestation that can be slashable. +/// +/// To be included in an `AttesterSlashing`. +/// +/// Spec v0.12.1 +#[derive( + Derivative, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Eq)] // to satisfy Clippy's lint about `Hash` +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct IndexedAttestation { + /// Lists validator registry indices, not committee indices. + #[serde(with = "quoted_variable_list_u64")] + pub attesting_indices: VariableList, + pub data: AttestationData, + pub signature: AggregateSignature, +} + +impl IndexedAttestation { + /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. + /// + /// Spec v0.12.1 + pub fn is_double_vote(&self, other: &Self) -> bool { + self.data.target.epoch == other.data.target.epoch && self.data != other.data + } + + /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. + /// + /// Spec v0.12.1 + pub fn is_surround_vote(&self, other: &Self) -> bool { + self.data.source.epoch < other.data.source.epoch + && other.data.target.epoch < self.data.target.epoch + } +} + +/// Implementation of non-crypto-secure `Hash`, for use with `HashMap` and `HashSet`. +/// +/// Guarantees `att1 == att2 -> hash(att1) == hash(att2)`. +/// +/// Used in the operation pool. +impl Hash for IndexedAttestation { + fn hash(&self, state: &mut H) { + self.attesting_indices.hash(state); + self.data.hash(state); + self.signature.as_ssz_bytes().hash(state); + } +} + +/// Serialize a variable list of `u64` such that each int is quoted. Deserialize a variable +/// list supporting both quoted and un-quoted ints. +/// +/// E.g.,`["0", "1", "2"]` +mod quoted_variable_list_u64 { + use super::*; + use crate::Unsigned; + use eth2_serde_utils::quoted_u64_vec::{QuotedIntVecVisitor, QuotedIntWrapper}; + use serde::ser::SerializeSeq; + use serde::{Deserializer, Serializer}; + + pub fn serialize(value: &VariableList, serializer: S) -> Result + where + S: Serializer, + T: Unsigned, + { + let mut seq = serializer.serialize_seq(Some(value.len()))?; + for &int in value.iter() { + seq.serialize_element(&QuotedIntWrapper { int })?; + } + seq.end() + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: Unsigned, + { + deserializer + .deserialize_any(QuotedIntVecVisitor) + .and_then(|vec| { + VariableList::new(vec) + .map_err(|e| serde::de::Error::custom(format!("invalid length: {:?}", e))) + }) + } +} diff --git a/primitives/beacon/src/int_to_bytes.rs b/primitives/beacon/src/int_to_bytes.rs new file mode 100644 index 00000000..55b28807 --- /dev/null +++ b/primitives/beacon/src/int_to_bytes.rs @@ -0,0 +1,78 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use bytes::{BufMut, BytesMut}; + +/// Returns `int` as little-endian bytes with a length of 1. +pub fn int_to_bytes1(int: u8) -> Vec { + vec![int] +} + +/// Returns `int` as little-endian bytes with a length of 2. +pub fn int_to_bytes2(int: u16) -> Vec { + let mut bytes = BytesMut::with_capacity(2); + bytes.put_u16_le(int); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 3. +/// +/// An `Option` is returned as Rust does not support a native +/// `u24` type. +/// +/// The Eth 2.0 specification uses `int.to_bytes(2, 'little')`, which throws an error if `int` +/// doesn't fit within 3 bytes. The specification relies upon implicit asserts for some validity +/// conditions, so we ensure the calling function is aware of the error condition as opposed to +/// hiding it with a modulo. +pub fn int_to_bytes3(int: u32) -> Option> { + if int < 2_u32.pow(3 * 8) { + let mut bytes = BytesMut::with_capacity(4); + bytes.put_u32_le(int); + Some(bytes[0..3].to_vec()) + } else { + None + } +} + +/// Returns `int` as little-endian bytes with a length of 4. +pub fn int_to_bytes4(int: u32) -> [u8; 4] { + int.to_le_bytes() +} + +/// Returns `int` as little-endian bytes with a length of 8. +pub fn int_to_bytes8(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(8); + bytes.put_u64_le(int); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 32. +pub fn int_to_bytes32(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(32); + bytes.put_u64_le(int); + bytes.resize(32, 0); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 32. +pub fn int_to_fixed_bytes32(int: u64) -> [u8; 32] { + let mut bytes = [0; 32]; + let int_bytes = int.to_le_bytes(); + bytes[0..int_bytes.len()].copy_from_slice(&int_bytes); + bytes +} + +/// Returns `int` as little-endian bytes with a length of 48. +pub fn int_to_bytes48(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(48); + bytes.put_u64_le(int); + bytes.resize(48, 0); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 96. +pub fn int_to_bytes96(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(96); + bytes.put_u64_le(int); + bytes.resize(96, 0); + bytes.to_vec() +} diff --git a/primitives/beacon/src/lib.rs b/primitives/beacon/src/lib.rs new file mode 100644 index 00000000..58ccc9d3 --- /dev/null +++ b/primitives/beacon/src/lib.rs @@ -0,0 +1,235 @@ +//! Ethereum 2.0 types + +#![cfg_attr(not(feature = "std"), no_std)] +// Required for big type-level numbers +#![recursion_limit = "128"] +// Clippy lint set up +#![cfg_attr( + not(test), + deny( + clippy::integer_arithmetic, + clippy::disallowed_methods, + clippy::indexing_slicing + ) +)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +mod prelude { + pub use alloc::borrow::Cow; + pub use alloc::collections::{BTreeMap, BTreeSet}; + pub use alloc::format; + pub use alloc::string::String; + pub use alloc::string::ToString; + pub use alloc::{vec, vec::Vec}; + pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; + pub use core::fmt::Debug; + pub use core::hash::Hash; + pub use scale_info::TypeInfo; +} + +#[cfg(feature = "std")] +mod prelude { + pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; + pub use scale_info::TypeInfo; + pub use std::borrow::Cow; + pub use std::collections::{BTreeMap, BTreeSet}; +} + +pub mod aggregate_and_proof; +pub mod application_domain; +pub mod attestation; +pub mod attestation_data; +pub mod attestation_duty; +pub mod attester_slashing; +pub mod beacon_block; +pub mod beacon_block_body; +pub mod beacon_block_header; +pub mod beacon_committee; +pub mod beacon_state; +pub mod bls_to_execution_change; +pub mod builder_bid; +pub mod chain_spec; +pub mod checkpoint; +pub mod consts; +pub mod contribution_and_proof; +pub mod deposit; +pub mod deposit_data; +pub mod deposit_message; +pub mod deposit_tree_snapshot; +pub mod enr_fork_id; +pub mod eth1_data; +pub mod eth_spec; +pub mod execution_block_hash; +pub mod execution_payload; +pub mod execution_payload_header; +pub mod fork; +pub mod fork_data; +pub mod fork_name; +pub mod fork_versioned_response; +pub mod graffiti; +pub mod historical_batch; +pub mod historical_summary; +pub mod indexed_attestation; +pub mod light_client_bootstrap; +pub mod light_client_finality_update; +pub mod light_client_optimistic_update; +pub mod light_client_update; +pub mod pending_attestation; +pub mod proposer_preparation_data; +pub mod proposer_slashing; +pub mod relative_epoch; +pub mod selection_proof; +pub mod shuffling_id; +pub mod signed_aggregate_and_proof; +pub mod signed_beacon_block; +pub mod signed_beacon_block_header; +pub mod signed_bls_to_execution_change; +pub mod signed_contribution_and_proof; +pub mod signed_voluntary_exit; +pub mod signing_data; +pub mod sync_committee_subscription; +pub mod sync_duty; +pub mod validator; +pub mod validator_subscription; +pub mod voluntary_exit; +#[macro_use] +pub mod slot_epoch_macros; +pub mod config_and_preset; +pub mod execution_block_header; +pub mod int_to_bytes; +pub mod participation_flags; +pub mod participation_list; +pub mod payload; +pub mod preset; +pub mod slot_epoch; +pub mod subnet_id; +pub mod sync_aggregate; +pub mod sync_aggregator_selection_data; +pub mod sync_committee; +pub mod sync_committee_contribution; +pub mod sync_committee_message; +pub mod sync_selection_proof; +pub mod sync_subnet_id; +pub mod validator_registration_data; +pub mod withdrawal; + +pub mod slot_data; + +pub mod safe_arith; + +use ethereum_types::{H160, H256}; + +pub use crate::aggregate_and_proof::AggregateAndProof; +pub use crate::attestation::{Attestation, Error as AttestationError}; +pub use crate::attestation_data::AttestationData; +pub use crate::attestation_duty::AttestationDuty; +pub use crate::attester_slashing::AttesterSlashing; +pub use crate::beacon_block::{ + BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockMerge, + BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, EmptyBlock, +}; +pub use crate::beacon_block_body::{ + BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyCapella, + BeaconBlockBodyMerge, BeaconBlockBodyRef, BeaconBlockBodyRefMut, +}; +pub use crate::beacon_block_header::BeaconBlockHeader; +pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee}; +pub use crate::beacon_state::{Error as BeaconStateError, *}; +pub use crate::bls_to_execution_change::BlsToExecutionChange; +pub use crate::chain_spec::{ChainSpec, Config, Domain}; +pub use crate::checkpoint::Checkpoint; +pub use crate::config_and_preset::{ + ConfigAndPreset, ConfigAndPresetBellatrix, ConfigAndPresetCapella, +}; +pub use crate::contribution_and_proof::ContributionAndProof; +pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH}; +pub use crate::deposit_data::DepositData; +pub use crate::deposit_message::DepositMessage; +pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock}; +pub use crate::enr_fork_id::EnrForkId; +pub use crate::eth1_data::Eth1Data; +pub use crate::eth_spec::EthSpecId; +pub use crate::eth_spec::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; +pub use crate::execution_block_hash::ExecutionBlockHash; +pub use crate::execution_block_header::ExecutionBlockHeader; +pub use crate::execution_payload::{ + ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge, ExecutionPayloadRef, + Transaction, Transactions, Withdrawals, +}; +pub use crate::execution_payload_header::{ + ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderMerge, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, +}; +pub use crate::fork::Fork; +pub use crate::fork_data::ForkData; +pub use crate::fork_name::{ForkName, InconsistentFork}; +#[cfg(feature = "std")] +pub use crate::fork_versioned_response::ForkVersionDeserialize; +pub use crate::fork_versioned_response::{ + ExecutionOptimisticForkVersionedResponse, ForkVersionedResponse, +}; +pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; +pub use crate::historical_batch::HistoricalBatch; +pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; +pub use crate::participation_flags::ParticipationFlags; +pub use crate::participation_list::ParticipationList; +pub use crate::payload::{ + AbstractExecPayload, BlindedPayload, BlindedPayloadCapella, BlindedPayloadMerge, + BlindedPayloadRef, BlockType, ExecPayload, FullPayload, FullPayloadCapella, FullPayloadMerge, + FullPayloadRef, OwnedExecPayload, +}; +pub use crate::pending_attestation::PendingAttestation; +pub use crate::preset::{AltairPreset, BasePreset, BellatrixPreset, CapellaPreset}; +pub use crate::proposer_preparation_data::ProposerPreparationData; +pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; +pub use crate::selection_proof::SelectionProof; +pub use crate::shuffling_id::AttestationShufflingId; +pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof; +pub use crate::signed_beacon_block::{ + SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockCapella, + SignedBeaconBlockHash, SignedBeaconBlockMerge, SignedBlindedBeaconBlock, +}; +pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader; +pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; +pub use crate::signed_contribution_and_proof::SignedContributionAndProof; +pub use crate::signed_voluntary_exit::SignedVoluntaryExit; +pub use crate::signing_data::{SignedRoot, SigningData}; +pub use crate::slot_epoch::{Epoch, Slot}; +pub use crate::subnet_id::SubnetId; +pub use crate::sync_aggregate::SyncAggregate; +pub use crate::sync_aggregator_selection_data::SyncAggregatorSelectionData; +pub use crate::sync_committee::SyncCommittee; +pub use crate::sync_committee_contribution::{SyncCommitteeContribution, SyncContributionData}; +pub use crate::sync_committee_message::SyncCommitteeMessage; +pub use crate::sync_committee_subscription::SyncCommitteeSubscription; +pub use crate::sync_duty::SyncDuty; +pub use crate::sync_selection_proof::SyncSelectionProof; +pub use crate::sync_subnet_id::SyncSubnetId; +pub use crate::validator::Validator; +pub use crate::validator_registration_data::*; +pub use crate::validator_subscription::ValidatorSubscription; +pub use crate::voluntary_exit::VoluntaryExit; +pub use crate::withdrawal::Withdrawal; + +pub type CommitteeIndex = u64; +pub type Hash256 = H256; +pub type Uint256 = ethereum_types::U256; +pub type Address = H160; +pub type ForkVersion = [u8; 4]; +pub type BLSFieldElement = Uint256; +// pub type Blob = FixedVector::FieldElementsPerBlob>; +pub type VersionedHash = Hash256; +pub type Hash64 = ethereum_types::H64; + +pub use bls::{ + AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, + Signature, SignatureBytes, +}; +pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList}; +pub use superstruct::superstruct; diff --git a/primitives/beacon/src/light_client_bootstrap.rs b/primitives/beacon/src/light_client_bootstrap.rs new file mode 100644 index 00000000..5401db83 --- /dev/null +++ b/primitives/beacon/src/light_client_bootstrap.rs @@ -0,0 +1,31 @@ +use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::light_client_update::*; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// A LightClientBootstrap is the initializer we send over to lightclient nodes +/// that are trying to generate their basic storage when booting up. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct LightClientBootstrap { + /// Requested beacon block header. + pub header: BeaconBlockHeader, + /// The `SyncCommittee` used in the requested period. + pub current_sync_committee: SyncCommittee, + /// Merkle proof for sync committee + pub current_sync_committee_branch: FixedVector, +} diff --git a/primitives/beacon/src/light_client_finality_update.rs b/primitives/beacon/src/light_client_finality_update.rs new file mode 100644 index 00000000..270440b3 --- /dev/null +++ b/primitives/beacon/src/light_client_finality_update.rs @@ -0,0 +1,35 @@ +use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; +use crate::light_client_update::*; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that +/// signal a new finalized beacon block header for the light client sync protocol. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct LightClientFinalityUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). + pub finalized_header: BeaconBlockHeader, + /// Merkle proof attesting finalized header. + pub finality_branch: FixedVector, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} diff --git a/primitives/beacon/src/light_client_optimistic_update.rs b/primitives/beacon/src/light_client_optimistic_update.rs new file mode 100644 index 00000000..6623f9a8 --- /dev/null +++ b/primitives/beacon/src/light_client_optimistic_update.rs @@ -0,0 +1,30 @@ +use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// A LightClientOptimisticUpdate is the update we send on each slot, +/// it is based off the current unfinalized epoch is verified only against BLS signature. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct LightClientOptimisticUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} diff --git a/primitives/beacon/src/light_client_update.rs b/primitives/beacon/src/light_client_update.rs new file mode 100644 index 00000000..b094bb4c --- /dev/null +++ b/primitives/beacon/src/light_client_update.rs @@ -0,0 +1,83 @@ +use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; +use crate::beacon_state; +use crate::prelude::*; +use crate::safe_arith::ArithError; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::{U5, U6}; + +pub const FINALIZED_ROOT_INDEX: usize = 105; +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55; + +pub type FinalizedRootProofLen = U6; +pub type CurrentSyncCommitteeProofLen = U5; +pub type NextSyncCommitteeProofLen = U5; + +pub const FINALIZED_ROOT_PROOF_LEN: usize = 6; +pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + SszTypesError(ssz_types::Error), + BeaconStateError(beacon_state::Error), + ArithError(ArithError), + AltairForkNotActive, + NotEnoughSyncCommitteeParticipants, + MismatchingPeriods, + InvalidFinalizedBlock, +} + +impl From for Error { + fn from(e: ssz_types::Error) -> Error { + Error::SszTypesError(e) + } +} + +impl From for Error { + fn from(e: beacon_state::Error) -> Error { + Error::BeaconStateError(e) + } +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +/// A LightClientUpdate is the update we request solely to either complete the bootstraping process, +/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period +/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct LightClientUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// The `SyncCommittee` used in the next period. + pub next_sync_committee: SyncCommittee, + /// Merkle proof for next sync committee + pub next_sync_committee_branch: FixedVector, + /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). + pub finalized_header: BeaconBlockHeader, + /// Merkle proof attesting finalized header. + pub finality_branch: FixedVector, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} diff --git a/primitives/beacon/src/participation_flags.rs b/primitives/beacon/src/participation_flags.rs new file mode 100644 index 00000000..62094222 --- /dev/null +++ b/primitives/beacon/src/participation_flags.rs @@ -0,0 +1,103 @@ +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::{consts::altair::NUM_FLAG_INDICES, Hash256}; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; +use tree_hash::{PackedEncoding, TreeHash, TreeHashType}; + +#[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Deserialize, + Serialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct ParticipationFlags { + #[serde(with = "eth2_serde_utils::quoted_u8")] + bits: u8, +} + +impl ParticipationFlags { + pub fn add_flag(&mut self, flag_index: usize) -> Result<(), ArithError> { + if flag_index >= NUM_FLAG_INDICES { + return Err(ArithError::Overflow); + } + self.bits |= 1u8.safe_shl(flag_index as u32)?; + Ok(()) + } + + pub fn has_flag(&self, flag_index: usize) -> Result { + if flag_index >= NUM_FLAG_INDICES { + return Err(ArithError::Overflow); + } + let mask = 1u8.safe_shl(flag_index as u32)?; + Ok(self.bits & mask == mask) + } + + pub fn into_u8(self) -> u8 { + self.bits + } +} + +/// Decode implementation that transparently behaves like the inner `u8`. +impl Decode for ParticipationFlags { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + u8::from_ssz_bytes(bytes).map(|bits| Self { bits }) + } +} + +/// Encode implementation that transparently behaves like the inner `u8`. +impl Encode for ParticipationFlags { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.bits.ssz_append(buf); + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.bits.ssz_bytes_len() + } + + fn as_ssz_bytes(&self) -> Vec { + self.bits.as_ssz_bytes() + } +} + +impl TreeHash for ParticipationFlags { + fn tree_hash_type() -> TreeHashType { + u8::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + self.bits.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + u8::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + self.bits.tree_hash_root() + } +} diff --git a/primitives/beacon/src/participation_list.rs b/primitives/beacon/src/participation_list.rs new file mode 100644 index 00000000..1145f584 --- /dev/null +++ b/primitives/beacon/src/participation_list.rs @@ -0,0 +1,17 @@ +#![allow(clippy::integer_arithmetic)] + +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::{ParticipationFlags, Unsigned, VariableList}; + +/// Wrapper type allowing the implementation of `CachedTreeHash`. +#[derive(Debug)] +pub struct ParticipationList<'a, N: Unsigned> { + pub inner: &'a VariableList, +} + +impl<'a, N: Unsigned> ParticipationList<'a, N> { + pub fn new(inner: &'a VariableList) -> Self { + Self { inner } + } +} diff --git a/primitives/beacon/src/payload.rs b/primitives/beacon/src/payload.rs new file mode 100644 index 00000000..2a29ff1f --- /dev/null +++ b/primitives/beacon/src/payload.rs @@ -0,0 +1,924 @@ +use crate::prelude::*; +use crate::*; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::hash::Hash; +use derivative::Derivative; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive(Debug)] +pub enum BlockType { + Blinded, + Full, +} + +/// A trait representing behavior of an `ExecutionPayload` that either has a full list of transactions +/// or a transaction hash in it's place. +pub trait ExecPayload: Debug + Clone + PartialEq + Hash + TreeHash + Send { + fn block_type() -> BlockType; + + /// Convert the payload into a payload header. + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader; + + /// We provide a subset of field accessors, for the fields used in `consensus`. + /// + /// More fields can be added here if you wish. + fn parent_hash(&self) -> ExecutionBlockHash; + fn prev_randao(&self) -> Hash256; + fn block_number(&self) -> u64; + fn timestamp(&self) -> u64; + fn block_hash(&self) -> ExecutionBlockHash; + fn fee_recipient(&self) -> Address; + fn gas_limit(&self) -> u64; + fn transactions(&self) -> Option<&Transactions>; + /// fork-specific fields + fn withdrawals_root(&self) -> Result; + + /// Is this a default payload with 0x0 roots for transactions and withdrawals? + fn is_default_with_zero_roots(&self) -> bool; + + /// Is this a default payload with the hash of the empty list for transactions and withdrawals? + fn is_default_with_empty_roots(&self) -> bool; +} + +/// `ExecPayload` functionality the requires ownership. +pub trait OwnedExecPayload: + ExecPayload + Default + Serialize + DeserializeOwned + Encode + Decode + 'static +{ +} + +impl OwnedExecPayload for P where + P: ExecPayload + Default + Serialize + DeserializeOwned + Encode + Decode + 'static +{ +} + +pub trait AbstractExecPayload: + ExecPayload + + Sized + + From> + + TryFrom> + + TryInto + + TryInto +{ + type Ref<'a>: ExecPayload + Copy + From<&'a Self::Merge> + From<&'a Self::Capella>; + + type Merge: OwnedExecPayload + + Into + + for<'a> From>> + + TryFrom>; + type Capella: OwnedExecPayload + + Into + + for<'a> From>> + + TryFrom>; + + fn default_at_fork(fork_name: ForkName) -> Result; +} + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + ssz(struct_behaviour = "transparent"), + ), + ref_attributes( + derive(Debug, Derivative, TreeHash), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + tree_hash(enum_behaviour = "transparent"), + ), + map_into(ExecutionPayload), + map_ref_into(ExecutionPayloadRef), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct FullPayload { + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + pub execution_payload: ExecutionPayloadMerge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload: ExecutionPayloadCapella, +} + +impl From> for ExecutionPayload { + fn from(full_payload: FullPayload) -> Self { + map_full_payload_into_execution_payload!(full_payload, move |payload, cons| { + cons(payload.execution_payload) + }) + } +} + +impl<'a, T: EthSpec> From> for ExecutionPayload { + fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self { + map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| { + cons(payload); + payload.execution_payload.clone().into() + }) + } +} + +impl<'a, T: EthSpec> From> for FullPayload { + fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self { + map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecPayload for FullPayload { + fn block_type() -> BlockType { + BlockType::Full + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_full_payload_ref!(&'a _, self.to_ref(), move |inner, cons| { + cons(inner); + let exec_payload_ref: ExecutionPayloadRef<'a, T> = From::from(&inner.execution_payload); + ExecutionPayloadHeader::from(exec_payload_ref) + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.gas_limit + }) + } + + fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + Some(&payload.execution_payload.transactions) + }) + } + + fn withdrawals_root(&self) -> Result { + match self { + FullPayload::Merge(_) => Err(Error::IncorrectStateVariant), + FullPayload::Capella(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) + } + + fn is_default_with_empty_roots(&self) -> bool { + // For full payloads the empty/zero distinction does not exist. + self.is_default_with_zero_roots() + } +} + +impl FullPayload { + pub fn execution_payload(self) -> ExecutionPayload { + map_full_payload_into_execution_payload!(self, |inner, cons| { + cons(inner.execution_payload) + }) + } +} + +impl<'a, T: EthSpec> FullPayloadRef<'a, T> { + pub fn execution_payload_ref(self) -> ExecutionPayloadRef<'a, T> { + map_full_payload_ref_into_execution_payload_ref!(&'a _, self, |inner, cons| { + cons(&inner.execution_payload) + }) + } +} + +impl<'b, T: EthSpec> ExecPayload for FullPayloadRef<'b, T> { + fn block_type() -> BlockType { + BlockType::Full + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.to_execution_payload_header() + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.gas_limit + }) + } + + fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + Some(&payload.execution_payload.transactions) + }) + } + + fn withdrawals_root(&self) -> Result { + match self { + FullPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant), + FullPayloadRef::Capella(inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) + } + + fn is_default_with_empty_roots(&self) -> bool { + // For full payloads the empty/zero distinction does not exist. + self.is_default_with_zero_roots() + } +} + +impl AbstractExecPayload for FullPayload { + type Ref<'a> = FullPayloadRef<'a, T>; + type Merge = FullPayloadMerge; + type Capella = FullPayloadCapella; + + fn default_at_fork(fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant), + ForkName::Merge => Ok(FullPayloadMerge::default().into()), + ForkName::Capella => Ok(FullPayloadCapella::default().into()), + } + } +} + +impl From> for FullPayload { + fn from(execution_payload: ExecutionPayload) -> Self { + map_execution_payload_into_full_payload!(execution_payload, |inner, cons| { + cons(inner.into()) + }) + } +} + +impl TryFrom> for FullPayload { + type Error = (); + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(()) + } +} + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + ScaleDecode, + ScaleEncode, + TypeInfo, + MaxEncodedLen, + TreeHash, + Derivative, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + ssz(struct_behaviour = "transparent"), + scale_info(skip_type_params(T)) + ), + ref_attributes( + derive(Debug, Derivative, TreeHash), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + tree_hash(enum_behaviour = "transparent"), + ), + map_into(ExecutionPayloadHeader), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + TreeHash, + ScaleDecode, + ScaleEncode, + TypeInfo, + MaxEncodedLen, + Derivative, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[tree_hash(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct BlindedPayload { + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + pub execution_payload_header: ExecutionPayloadHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload_header: ExecutionPayloadHeaderCapella, +} + +impl<'a, T: EthSpec> From> for BlindedPayload { + fn from(blinded_payload_ref: BlindedPayloadRef<'a, T>) -> Self { + map_blinded_payload_ref!(&'a _, blinded_payload_ref, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecPayload for BlindedPayload { + fn block_type() -> BlockType { + BlockType::Blinded + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + map_blinded_payload_into_execution_payload_header!(self.clone(), |inner, cons| { + cons(inner.execution_payload_header) + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) + } + + fn transactions(&self) -> Option<&Transactions> { + None + } + + fn withdrawals_root(&self) -> Result { + match self { + BlindedPayload::Merge(_) => Err(Error::IncorrectStateVariant), + BlindedPayload::Capella(ref inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + } + } + + fn is_default_with_zero_roots(&self) -> bool { + self.to_ref().is_default_with_zero_roots() + } + + // For blinded payloads we must check "defaultness" against the default `ExecutionPayload` + // which has been blinded into an `ExecutionPayloadHeader`, NOT against the default + // `ExecutionPayloadHeader` which has a zeroed out `transactions_root`. The transactions root + // should be the root of the empty list. + fn is_default_with_empty_roots(&self) -> bool { + self.to_ref().is_default_with_empty_roots() + } +} + +impl<'b, T: EthSpec> ExecPayload for BlindedPayloadRef<'b, T> { + fn block_type() -> BlockType { + BlockType::Blinded + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.to_execution_payload_header() + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) + } + + fn transactions(&self) -> Option<&Transactions> { + None + } + + fn withdrawals_root(&self) -> Result { + match self { + BlindedPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant), + BlindedPayloadRef::Capella(inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_blinded_payload_ref!(&'b _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header == <_>::default() + }) + } + + fn is_default_with_empty_roots<'a>(&'a self) -> bool { + map_blinded_payload_ref!(&'b _, self, move |payload, cons| { + cons(payload); + payload.is_default_with_empty_roots() + }) + } +} + +macro_rules! impl_exec_payload_common { + ($wrapper_type:ident, // BlindedPayloadMerge | FullPayloadMerge + $wrapped_type:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadMerge + $wrapped_type_full:ident, // ExecutionPayloadMerge | ExecutionPayloadMerge + $wrapped_type_header:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadHeaderMerge + $wrapped_field:ident, // execution_payload_header | execution_payload + $fork_variant:ident, // Merge | Merge + $block_type_variant:ident, // Blinded | Full + $is_default_with_empty_roots:block, + $f:block, + $g:block) => { + impl ExecPayload for $wrapper_type { + fn block_type() -> BlockType { + BlockType::$block_type_variant + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + ExecutionPayloadHeader::$fork_variant($wrapped_type_header::from( + &self.$wrapped_field, + )) + } + + fn parent_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.parent_hash + } + + fn prev_randao(&self) -> Hash256 { + self.$wrapped_field.prev_randao + } + + fn block_number(&self) -> u64 { + self.$wrapped_field.block_number + } + + fn timestamp(&self) -> u64 { + self.$wrapped_field.timestamp + } + + fn block_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.block_hash + } + + fn fee_recipient(&self) -> Address { + self.$wrapped_field.fee_recipient + } + + fn gas_limit(&self) -> u64 { + self.$wrapped_field.gas_limit + } + + fn is_default_with_zero_roots(&self) -> bool { + self.$wrapped_field == $wrapped_type::default() + } + + fn is_default_with_empty_roots(&self) -> bool { + let f = $is_default_with_empty_roots; + f(self) + } + + fn transactions(&self) -> Option<&Transactions> { + let f = $f; + f(self) + } + + fn withdrawals_root(&self) -> Result { + let g = $g; + g(self) + } + } + + impl From<$wrapped_type> for $wrapper_type { + fn from($wrapped_field: $wrapped_type) -> Self { + Self { $wrapped_field } + } + } + }; +} + +macro_rules! impl_exec_payload_for_fork { + // BlindedPayloadMerge, FullPayloadMerge, ExecutionPayloadHeaderMerge, ExecutionPayloadMerge, Merge + ($wrapper_type_header:ident, $wrapper_type_full:ident, $wrapped_type_header:ident, $wrapped_type_full:ident, $fork_variant:ident) => { + //*************** Blinded payload implementations ******************// + + impl_exec_payload_common!( + $wrapper_type_header, // BlindedPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + execution_payload_header, + $fork_variant, // Merge + Blinded, + { + |wrapper: &$wrapper_type_header| { + wrapper.execution_payload_header + == $wrapped_type_header::from(&$wrapped_type_full::default()) + } + }, + { |_| { None } }, + { + let c: for<'a> fn(&'a $wrapper_type_header) -> Result = + |payload: &$wrapper_type_header| { + let wrapper_ref_type = BlindedPayloadRef::$fork_variant(&payload); + wrapper_ref_type.withdrawals_root() + }; + c + } + ); + + impl TryInto<$wrapper_type_header> for BlindedPayload { + type Error = Error; + + fn try_into(self) -> Result<$wrapper_type_header, Self::Error> { + match self { + BlindedPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::IncorrectStateVariant), + } + } + } + + // NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default` + // implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the + // default payload in `is_merge_transition_block` to determine whether the merge has occurred. + // + // The default `BlindedPayload` is therefore the payload header that results from blinding the + // default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that + // its `transactions_root` is the hash of the empty list rather than 0x0. + impl Default for $wrapper_type_header { + fn default() -> Self { + Self { + execution_payload_header: $wrapped_type_header::from( + &$wrapped_type_full::default(), + ), + } + } + } + + impl TryFrom> for $wrapper_type_header { + type Error = Error; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::$fork_variant(execution_payload_header) => { + Ok(execution_payload_header.into()) + } + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } + + // BlindedPayload* from CoW reference to ExecutionPayload* (hopefully just a reference). + impl<'a, T: EthSpec> From>> for $wrapper_type_header { + fn from(execution_payload: Cow<'a, $wrapped_type_full>) -> Self { + Self { + execution_payload_header: $wrapped_type_header::from(&*execution_payload), + } + } + } + + //*************** Full payload implementations ******************// + + impl_exec_payload_common!( + $wrapper_type_full, // FullPayloadMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + execution_payload, + $fork_variant, // Merge + Full, + { + |wrapper: &$wrapper_type_full| { + wrapper.execution_payload == $wrapped_type_full::default() + } + }, + { + let c: for<'a> fn(&'a $wrapper_type_full) -> Option<&'a Transactions> = + |payload: &$wrapper_type_full| Some(&payload.execution_payload.transactions); + c + }, + { + let c: for<'a> fn(&'a $wrapper_type_full) -> Result = + |payload: &$wrapper_type_full| { + let wrapper_ref_type = FullPayloadRef::$fork_variant(&payload); + wrapper_ref_type.withdrawals_root() + }; + c + } + ); + + impl Default for $wrapper_type_full { + fn default() -> Self { + Self { + execution_payload: $wrapped_type_full::default(), + } + } + } + + // FullPayload * from CoW reference to ExecutionPayload* (hopefully already owned). + impl<'a, T: EthSpec> From>> for $wrapper_type_full { + fn from(execution_payload: Cow<'a, $wrapped_type_full>) -> Self { + Self { + execution_payload: $wrapped_type_full::from(execution_payload.into_owned()), + } + } + } + + impl TryFrom> for $wrapper_type_full { + type Error = Error; + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryFrom<$wrapped_type_header> for $wrapper_type_full { + type Error = Error; + fn try_from(_: $wrapped_type_header) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryInto<$wrapper_type_full> for FullPayload { + type Error = Error; + + fn try_into(self) -> Result<$wrapper_type_full, Self::Error> { + match self { + FullPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } + }; +} + +impl_exec_payload_for_fork!( + BlindedPayloadMerge, + FullPayloadMerge, + ExecutionPayloadHeaderMerge, + ExecutionPayloadMerge, + Merge +); +impl_exec_payload_for_fork!( + BlindedPayloadCapella, + FullPayloadCapella, + ExecutionPayloadHeaderCapella, + ExecutionPayloadCapella, + Capella +); + +impl AbstractExecPayload for BlindedPayload { + type Ref<'a> = BlindedPayloadRef<'a, T>; + type Merge = BlindedPayloadMerge; + type Capella = BlindedPayloadCapella; + + fn default_at_fork(fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant), + ForkName::Merge => Ok(BlindedPayloadMerge::default().into()), + ForkName::Capella => Ok(BlindedPayloadCapella::default().into()), + } + } +} + +impl From> for BlindedPayload { + fn from(payload: ExecutionPayload) -> Self { + // This implementation is a bit wasteful in that it discards the payload body. + // Required by the top-level constraint on AbstractExecPayload but could maybe be loosened + // in future. + map_execution_payload_into_blinded_payload!(payload, |inner, cons| cons(From::from( + Cow::Owned(inner) + ))) + } +} + +impl From> for BlindedPayload { + fn from(execution_payload_header: ExecutionPayloadHeader) -> Self { + match execution_payload_header { + ExecutionPayloadHeader::Merge(execution_payload_header) => { + Self::Merge(BlindedPayloadMerge { + execution_payload_header, + }) + } + ExecutionPayloadHeader::Capella(execution_payload_header) => { + Self::Capella(BlindedPayloadCapella { + execution_payload_header, + }) + } + } + } +} + +impl From> for ExecutionPayloadHeader { + fn from(blinded: BlindedPayload) -> Self { + match blinded { + BlindedPayload::Merge(blinded_payload) => { + ExecutionPayloadHeader::Merge(blinded_payload.execution_payload_header) + } + BlindedPayload::Capella(blinded_payload) => { + ExecutionPayloadHeader::Capella(blinded_payload.execution_payload_header) + } + } + } +} diff --git a/primitives/beacon/src/pending_attestation.rs b/primitives/beacon/src/pending_attestation.rs new file mode 100644 index 00000000..0c04f7f1 --- /dev/null +++ b/primitives/beacon/src/pending_attestation.rs @@ -0,0 +1,33 @@ +use crate::{AttestationData, BitList, EthSpec}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// An attestation that has been included in the state but not yet fully processed. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct PendingAttestation { + pub aggregation_bits: BitList, + pub data: AttestationData, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub inclusion_delay: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_index: u64, +} diff --git a/primitives/beacon/src/preset.rs b/primitives/beacon/src/preset.rs new file mode 100644 index 00000000..fb527dcd --- /dev/null +++ b/primitives/beacon/src/preset.rs @@ -0,0 +1,208 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::{ChainSpec, Epoch, EthSpec, Unsigned}; +use serde::{Deserialize, Serialize}; + +/// Value-level representation of an Ethereum consensus "preset". +/// +/// This should only be used to check consistency of the compile-time constants +/// with a preset YAML file, or to make preset values available to the API. Prefer +/// the constants on `EthSpec` or the fields on `ChainSpec` to constructing and using +/// one of these structs. +/// +/// https://github.com/ethereum/eth2.0-specs/blob/dev/presets/mainnet/phase0.yaml +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct BasePreset { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_committees_per_slot: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub target_committee_size: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_validators_per_committee: u64, + #[serde(with = "eth2_serde_utils::quoted_u8")] + pub shuffle_round_count: u8, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub hysteresis_quotient: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub hysteresis_downward_multiplier: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub hysteresis_upward_multiplier: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub safe_slots_to_update_justified: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_deposit_amount: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_effective_balance: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub effective_balance_increment: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_attestation_inclusion_delay: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub slots_per_epoch: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_seed_lookahead: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_seed_lookahead: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub epochs_per_eth1_voting_period: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub slots_per_historical_root: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_epochs_to_inactivity_penalty: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub epochs_per_historical_vector: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub epochs_per_slashings_vector: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub historical_roots_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_registry_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub base_reward_factor: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub whistleblower_reward_quotient: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_reward_quotient: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub inactivity_penalty_quotient: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_slashing_penalty_quotient: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proportional_slashing_multiplier: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_proposer_slashings: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_attester_slashings: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_attestations: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_deposits: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_voluntary_exits: u64, +} + +impl BasePreset { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + max_committees_per_slot: spec.max_committees_per_slot as u64, + target_committee_size: spec.target_committee_size as u64, + max_validators_per_committee: T::MaxValidatorsPerCommittee::to_u64(), + shuffle_round_count: spec.shuffle_round_count, + hysteresis_quotient: spec.hysteresis_quotient, + hysteresis_downward_multiplier: spec.hysteresis_downward_multiplier, + hysteresis_upward_multiplier: spec.hysteresis_upward_multiplier, + safe_slots_to_update_justified: spec.safe_slots_to_update_justified, + min_deposit_amount: spec.min_deposit_amount, + max_effective_balance: spec.max_effective_balance, + effective_balance_increment: spec.effective_balance_increment, + min_attestation_inclusion_delay: spec.min_attestation_inclusion_delay, + slots_per_epoch: T::SlotsPerEpoch::to_u64(), + min_seed_lookahead: spec.min_seed_lookahead, + max_seed_lookahead: spec.max_seed_lookahead, + epochs_per_eth1_voting_period: T::EpochsPerEth1VotingPeriod::to_u64(), + slots_per_historical_root: T::SlotsPerHistoricalRoot::to_u64(), + min_epochs_to_inactivity_penalty: spec.min_epochs_to_inactivity_penalty, + epochs_per_historical_vector: T::EpochsPerHistoricalVector::to_u64(), + epochs_per_slashings_vector: T::EpochsPerSlashingsVector::to_u64(), + historical_roots_limit: T::HistoricalRootsLimit::to_u64(), + validator_registry_limit: T::ValidatorRegistryLimit::to_u64(), + base_reward_factor: spec.base_reward_factor, + whistleblower_reward_quotient: spec.whistleblower_reward_quotient, + proposer_reward_quotient: spec.proposer_reward_quotient, + inactivity_penalty_quotient: spec.inactivity_penalty_quotient, + min_slashing_penalty_quotient: spec.min_slashing_penalty_quotient, + proportional_slashing_multiplier: spec.proportional_slashing_multiplier, + max_proposer_slashings: T::MaxProposerSlashings::to_u64(), + max_attester_slashings: T::MaxAttesterSlashings::to_u64(), + max_attestations: T::MaxAttestations::to_u64(), + max_deposits: T::MaxDeposits::to_u64(), + max_voluntary_exits: T::MaxVoluntaryExits::to_u64(), + } + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct AltairPreset { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub inactivity_penalty_quotient_altair: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_slashing_penalty_quotient_altair: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proportional_slashing_multiplier_altair: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub sync_committee_size: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub epochs_per_sync_committee_period: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_sync_committee_participants: u64, +} + +impl AltairPreset { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + inactivity_penalty_quotient_altair: spec.inactivity_penalty_quotient_altair, + min_slashing_penalty_quotient_altair: spec.min_slashing_penalty_quotient_altair, + proportional_slashing_multiplier_altair: spec.proportional_slashing_multiplier_altair, + sync_committee_size: T::SyncCommitteeSize::to_u64(), + epochs_per_sync_committee_period: spec.epochs_per_sync_committee_period, + min_sync_committee_participants: spec.min_sync_committee_participants, + } + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct BellatrixPreset { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub inactivity_penalty_quotient_bellatrix: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub min_slashing_penalty_quotient_bellatrix: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proportional_slashing_multiplier_bellatrix: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_bytes_per_transaction: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_transactions_per_payload: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub bytes_per_logs_bloom: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_extra_data_bytes: u64, +} + +impl BellatrixPreset { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + inactivity_penalty_quotient_bellatrix: spec.inactivity_penalty_quotient_bellatrix, + min_slashing_penalty_quotient_bellatrix: spec.min_slashing_penalty_quotient_bellatrix, + proportional_slashing_multiplier_bellatrix: spec + .proportional_slashing_multiplier_bellatrix, + max_bytes_per_transaction: T::max_bytes_per_transaction() as u64, + max_transactions_per_payload: T::max_transactions_per_payload() as u64, + bytes_per_logs_bloom: T::bytes_per_logs_bloom() as u64, + max_extra_data_bytes: T::max_extra_data_bytes() as u64, + } + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub struct CapellaPreset { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_bls_to_execution_changes: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_withdrawals_per_payload: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub max_validators_per_withdrawals_sweep: u64, +} + +impl CapellaPreset { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { + Self { + max_bls_to_execution_changes: T::max_bls_to_execution_changes() as u64, + max_withdrawals_per_payload: T::max_withdrawals_per_payload() as u64, + max_validators_per_withdrawals_sweep: spec.max_validators_per_withdrawals_sweep, + } + } +} diff --git a/primitives/beacon/src/proposer_preparation_data.rs b/primitives/beacon/src/proposer_preparation_data.rs new file mode 100644 index 00000000..c02d4874 --- /dev/null +++ b/primitives/beacon/src/proposer_preparation_data.rs @@ -0,0 +1,15 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; + +/// A proposer preparation, created when a validator prepares the beacon node for potential proposers +/// by supplying information required when proposing blocks for the given validators. +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +pub struct ProposerPreparationData { + /// The validators index. + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + /// The fee-recipient address. + pub fee_recipient: Address, +} diff --git a/primitives/beacon/src/proposer_slashing.rs b/primitives/beacon/src/proposer_slashing.rs new file mode 100644 index 00000000..d715d229 --- /dev/null +++ b/primitives/beacon/src/proposer_slashing.rs @@ -0,0 +1,37 @@ +use crate::SignedBeaconBlockHeader; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Two conflicting proposals from the same proposer (validator). +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ProposerSlashing { + pub signed_header_1: SignedBeaconBlockHeader, + pub signed_header_2: SignedBeaconBlockHeader, +} + +impl ProposerSlashing { + /// Get proposer index, assuming slashing validity has already been checked. + pub fn proposer_index(&self) -> u64 { + self.signed_header_1.message.proposer_index + } +} diff --git a/primitives/beacon/src/relative_epoch.rs b/primitives/beacon/src/relative_epoch.rs new file mode 100644 index 00000000..6ec52fbc --- /dev/null +++ b/primitives/beacon/src/relative_epoch.rs @@ -0,0 +1,72 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::*; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Error { + EpochTooLow { base: Epoch, other: Epoch }, + EpochTooHigh { base: Epoch, other: Epoch }, + ArithError(ArithError), +} + +impl From for Error { + fn from(e: ArithError) -> Self { + Self::ArithError(e) + } +} + +/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior +/// to and following some epoch. +/// +/// Spec v0.12.1 +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum RelativeEpoch { + /// The prior epoch. + Previous, + /// The current epoch. + Current, + /// The next epoch. + Next, +} + +impl RelativeEpoch { + /// Returns the `epoch` that `self` refers to, with respect to the `base` epoch. + /// + /// Spec v0.12.1 + pub fn into_epoch(self, base: Epoch) -> Epoch { + match self { + // Due to saturating nature of epoch, check for current first. + RelativeEpoch::Current => base, + RelativeEpoch::Previous => base.saturating_sub(1u64), + RelativeEpoch::Next => base.saturating_add(1u64), + } + } + + /// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base` + /// + /// ## Errors + /// Returns an error when: + /// - `EpochTooLow` when `other` is more than 1 prior to `base`. + /// - `EpochTooHigh` when `other` is more than 1 after `base`. + /// + /// Spec v0.12.1 + pub fn from_epoch(base: Epoch, other: Epoch) -> Result { + if other == base { + Ok(RelativeEpoch::Current) + } else if other.safe_add(1)? == base { + Ok(RelativeEpoch::Previous) + } else if other == base.safe_add(1)? { + Ok(RelativeEpoch::Next) + } else if other < base { + Err(Error::EpochTooLow { base, other }) + } else { + Err(Error::EpochTooHigh { base, other }) + } + } + + /// Convenience function for `Self::from_epoch` where both slots are converted into epochs. + pub fn from_slot(base: Slot, other: Slot, slots_per_epoch: u64) -> Result { + Self::from_epoch(base.epoch(slots_per_epoch), other.epoch(slots_per_epoch)) + } +} diff --git a/primitives/beacon/src/safe_arith.rs b/primitives/beacon/src/safe_arith.rs new file mode 100644 index 00000000..707edcfd --- /dev/null +++ b/primitives/beacon/src/safe_arith.rs @@ -0,0 +1,132 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; + +/// Extension trait for iterators, providing a safe replacement for `sum`. +pub trait SafeArithIter { + fn safe_sum(self) -> Result; +} + +impl SafeArithIter for I +where + I: Iterator + Sized, + T: SafeArith, +{ + fn safe_sum(mut self) -> Result { + self.try_fold(T::ZERO, |acc, x| acc.safe_add(x)) + } +} + +/// Error representing the failure of an arithmetic operation. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ArithError { + Overflow, + DivisionByZero, +} + +pub type Result = core::result::Result; + +macro_rules! assign_method { + ($name:ident, $op:ident, $doc_op:expr) => { + assign_method!($name, $op, Self, $doc_op); + }; + ($name:ident, $op:ident, $rhs_ty:ty, $doc_op:expr) => { + #[doc = "Safe variant of `"] + #[doc = $doc_op] + #[doc = "`."] + #[inline] + fn $name(&mut self, other: $rhs_ty) -> Result<()> { + *self = self.$op(other)?; + Ok(()) + } + }; +} + +/// Trait providing safe arithmetic operations for built-in types. +pub trait SafeArith: Sized + Copy { + const ZERO: Self; + const ONE: Self; + + /// Safe variant of `+` that guards against overflow. + fn safe_add(&self, other: Rhs) -> Result; + + /// Safe variant of `-` that guards against overflow. + fn safe_sub(&self, other: Rhs) -> Result; + + /// Safe variant of `*` that guards against overflow. + fn safe_mul(&self, other: Rhs) -> Result; + + /// Safe variant of `/` that guards against division by 0. + fn safe_div(&self, other: Rhs) -> Result; + + /// Safe variant of `%` that guards against division by 0. + fn safe_rem(&self, other: Rhs) -> Result; + + /// Safe variant of `<<` that guards against overflow. + fn safe_shl(&self, other: u32) -> Result; + + /// Safe variant of `>>` that guards against overflow. + fn safe_shr(&self, other: u32) -> Result; + + assign_method!(safe_add_assign, safe_add, Rhs, "+="); + assign_method!(safe_sub_assign, safe_sub, Rhs, "-="); + assign_method!(safe_mul_assign, safe_mul, Rhs, "*="); + assign_method!(safe_div_assign, safe_div, Rhs, "/="); + assign_method!(safe_rem_assign, safe_rem, Rhs, "%="); + assign_method!(safe_shl_assign, safe_shl, u32, "<<="); + assign_method!(safe_shr_assign, safe_shr, u32, ">>="); +} + +macro_rules! impl_safe_arith { + ($typ:ty) => { + impl SafeArith for $typ { + const ZERO: Self = 0; + const ONE: Self = 1; + + #[inline] + fn safe_add(&self, other: Self) -> Result { + self.checked_add(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_sub(&self, other: Self) -> Result { + self.checked_sub(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_mul(&self, other: Self) -> Result { + self.checked_mul(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_div(&self, other: Self) -> Result { + self.checked_div(other).ok_or(ArithError::DivisionByZero) + } + + #[inline] + fn safe_rem(&self, other: Self) -> Result { + self.checked_rem(other).ok_or(ArithError::DivisionByZero) + } + + #[inline] + fn safe_shl(&self, other: u32) -> Result { + self.checked_shl(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_shr(&self, other: u32) -> Result { + self.checked_shr(other).ok_or(ArithError::Overflow) + } + } + }; +} + +impl_safe_arith!(u8); +impl_safe_arith!(u16); +impl_safe_arith!(u32); +impl_safe_arith!(u64); +impl_safe_arith!(usize); +impl_safe_arith!(i8); +impl_safe_arith!(i16); +impl_safe_arith!(i32); +impl_safe_arith!(i64); +impl_safe_arith!(isize); diff --git a/primitives/beacon/src/selection_proof.rs b/primitives/beacon/src/selection_proof.rs new file mode 100644 index 00000000..34a7814b --- /dev/null +++ b/primitives/beacon/src/selection_proof.rs @@ -0,0 +1,93 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::{ + ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot, +}; +use core::cmp; +use core::convert::TryInto; +use eth2_hashing::hash; +use ssz::Encode; + +#[derive(PartialEq, Debug, Clone)] +pub struct SelectionProof(Signature); + +impl SelectionProof { + pub fn new( + slot: Slot, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let domain = spec.get_domain( + slot.epoch(T::slots_per_epoch()), + Domain::SelectionProof, + fork, + genesis_validators_root, + ); + let message = slot.signing_root(domain); + + Self(secret_key.sign(message)) + } + + /// Returns the "modulo" used for determining if a `SelectionProof` elects an aggregator. + pub fn modulo(committee_len: usize, spec: &ChainSpec) -> Result { + Ok(cmp::max( + 1, + (committee_len as u64).safe_div(spec.target_aggregators_per_committee)?, + )) + } + + pub fn is_aggregator( + &self, + committee_len: usize, + spec: &ChainSpec, + ) -> Result { + self.is_aggregator_from_modulo(Self::modulo(committee_len, spec)?) + } + + pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result { + let signature_hash = hash(&self.0.as_ssz_bytes()); + let signature_hash_int = u64::from_le_bytes( + signature_hash + .get(0..8) + .expect("hash is 32 bytes") + .try_into() + .expect("first 8 bytes of signature should always convert to fixed array"), + ); + + signature_hash_int.safe_rem(modulo).map(|rem| rem == 0) + } + + pub fn verify( + &self, + slot: Slot, + pubkey: &PublicKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> bool { + let domain = spec.get_domain( + slot.epoch(T::slots_per_epoch()), + Domain::SelectionProof, + fork, + genesis_validators_root, + ); + let message = slot.signing_root(domain); + + self.0.verify(pubkey, message) + } +} + +impl Into for SelectionProof { + fn into(self) -> Signature { + self.0 + } +} + +impl From for SelectionProof { + fn from(sig: Signature) -> Self { + Self(sig) + } +} diff --git a/primitives/beacon/src/shuffling_id.rs b/primitives/beacon/src/shuffling_id.rs new file mode 100644 index 00000000..a757224f --- /dev/null +++ b/primitives/beacon/src/shuffling_id.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; +use crate::*; +use core::hash::Hash; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// Can be used to key (ID) the shuffling in some chain, in some epoch. +/// +/// ## Reasoning +/// +/// We say that the ID of some shuffling is always equal to a 2-tuple: +/// +/// - The epoch for which the shuffling should be effective. +/// - A block root, where this is the root at the *last* slot of the penultimate epoch. I.e., the +/// final block which contributed a randao reveal to the seed for the shuffling. +/// +/// The struct stores exactly that 2-tuple. +#[derive( + Debug, + PartialEq, + Eq, + Clone, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct AttestationShufflingId { + pub shuffling_epoch: Epoch, + pub shuffling_decision_block: Hash256, +} + +impl AttestationShufflingId { + pub fn from_components(shuffling_epoch: Epoch, shuffling_decision_block: Hash256) -> Self { + Self { + shuffling_epoch, + shuffling_decision_block, + } + } +} diff --git a/primitives/beacon/src/signed_aggregate_and_proof.rs b/primitives/beacon/src/signed_aggregate_and_proof.rs new file mode 100644 index 00000000..42fe6d87 --- /dev/null +++ b/primitives/beacon/src/signed_aggregate_and_proof.rs @@ -0,0 +1,75 @@ +use super::{ + AggregateAndProof, Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, + SelectionProof, Signature, SignedRoot, +}; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A Validators signed aggregate proof to publish on the `beacon_aggregate_and_proof` +/// gossipsub topic. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SignedAggregateAndProof { + /// The `AggregateAndProof` that was signed. + pub message: AggregateAndProof, + /// The aggregate attestation. + pub signature: Signature, +} + +impl SignedAggregateAndProof { + /// Produces a new `SignedAggregateAndProof` with a `selection_proof` generated by signing + /// `aggregate.data.slot` with `secret_key`. + /// + /// If `selection_proof.is_none()` it will be computed locally. + pub fn from_aggregate( + aggregator_index: u64, + aggregate: Attestation, + selection_proof: Option, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let message = AggregateAndProof::from_aggregate( + aggregator_index, + aggregate, + selection_proof, + secret_key, + fork, + genesis_validators_root, + spec, + ); + + let target_epoch = message.aggregate.data.slot.epoch(T::slots_per_epoch()); + let domain = spec.get_domain( + target_epoch, + Domain::AggregateAndProof, + fork, + genesis_validators_root, + ); + let signing_message = message.signing_root(domain); + + SignedAggregateAndProof { + message, + signature: secret_key.sign(signing_message), + } + } +} diff --git a/primitives/beacon/src/signed_beacon_block.rs b/primitives/beacon/src/signed_beacon_block.rs new file mode 100644 index 00000000..d4f73838 --- /dev/null +++ b/primitives/beacon/src/signed_beacon_block.rs @@ -0,0 +1,454 @@ +use crate::prelude::*; +use crate::*; +use bls::Signature; +use core::fmt; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub struct SignedBeaconBlockHash(Hash256); + +impl fmt::Debug for SignedBeaconBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SignedBeaconBlockHash({:?})", self.0) + } +} + +impl fmt::Display for SignedBeaconBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for SignedBeaconBlockHash { + fn from(hash: Hash256) -> SignedBeaconBlockHash { + SignedBeaconBlockHash(hash) + } +} + +impl From for Hash256 { + fn from(signed_beacon_block_hash: SignedBeaconBlockHash) -> Hash256 { + signed_beacon_block_hash.0 + } +} + +/// A `BeaconBlock` and a signature from its proposer. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "E: EthSpec")), + serde(bound = "E: EthSpec, Payload: AbstractExecPayload"), + ), + map_into(BeaconBlock), + map_ref_into(BeaconBlockRef), + map_ref_mut_into(BeaconBlockRefMut) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "E: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "E: EthSpec, Payload: AbstractExecPayload")] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +pub struct SignedBeaconBlock = FullPayload> { + #[superstruct(only(Base), partial_getter(rename = "message_base"))] + pub message: BeaconBlockBase, + #[superstruct(only(Altair), partial_getter(rename = "message_altair"))] + pub message: BeaconBlockAltair, + #[superstruct(only(Merge), partial_getter(rename = "message_merge"))] + pub message: BeaconBlockMerge, + #[superstruct(only(Capella), partial_getter(rename = "message_capella"))] + pub message: BeaconBlockCapella, + pub signature: Signature, +} + +pub type SignedBlindedBeaconBlock = SignedBeaconBlock>; + +impl> SignedBeaconBlock { + /// Returns the name of the fork pertaining to `self`. + /// + /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork + /// dictated by `self.slot()`. + pub fn fork_name(&self, spec: &ChainSpec) -> Result { + self.message().fork_name(spec) + } + + /// SSZ decode with fork variant determined by slot. + pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { + Self::from_ssz_bytes_with(bytes, |bytes| BeaconBlock::from_ssz_bytes(bytes, spec)) + } + + /// SSZ decode which attempts to decode all variants (slow). + pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { + Self::from_ssz_bytes_with(bytes, BeaconBlock::any_from_ssz_bytes) + } + + /// SSZ decode with custom decode function. + pub fn from_ssz_bytes_with( + bytes: &[u8], + block_decoder: impl FnOnce(&[u8]) -> Result, ssz::DecodeError>, + ) -> Result { + // We need the customer decoder for `BeaconBlock`, which doesn't compose with the other + // SSZ utils, so we duplicate some parts of `ssz_derive` here. + let mut builder = ssz::SszDecoderBuilder::new(bytes); + + builder.register_anonymous_variable_length_item()?; + builder.register_type::()?; + + let mut decoder = builder.build()?; + + // Read the first item as a `BeaconBlock`. + let message = decoder.decode_next_with(block_decoder)?; + let signature = decoder.decode_next()?; + + Ok(Self::from_block(message, signature)) + } + + /// Create a new `SignedBeaconBlock` from a `BeaconBlock` and `Signature`. + pub fn from_block(block: BeaconBlock, signature: Signature) -> Self { + match block { + BeaconBlock::Base(message) => { + SignedBeaconBlock::Base(SignedBeaconBlockBase { message, signature }) + } + BeaconBlock::Altair(message) => { + SignedBeaconBlock::Altair(SignedBeaconBlockAltair { message, signature }) + } + BeaconBlock::Merge(message) => { + SignedBeaconBlock::Merge(SignedBeaconBlockMerge { message, signature }) + } + BeaconBlock::Capella(message) => { + SignedBeaconBlock::Capella(SignedBeaconBlockCapella { message, signature }) + } + } + } + + /// Deconstruct the `SignedBeaconBlock` into a `BeaconBlock` and `Signature`. + /// + /// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because + /// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_. + pub fn deconstruct(self) -> (BeaconBlock, Signature) { + map_signed_beacon_block_into_beacon_block!(self, |block, beacon_block_cons| { + (beacon_block_cons(block.message), block.signature) + }) + } + + /// Accessor for the block's `message` field as a ref. + pub fn message<'a>(&'a self) -> BeaconBlockRef<'a, E, Payload> { + map_signed_beacon_block_ref_into_beacon_block_ref!( + &'a _, + self.to_ref(), + |inner, cons| cons(&inner.message) + ) + } + + /// Accessor for the block's `message` as a mutable reference (for testing only). + pub fn message_mut<'a>(&'a mut self) -> BeaconBlockRefMut<'a, E, Payload> { + map_signed_beacon_block_ref_mut_into_beacon_block_ref_mut!( + &'a _, + self.to_mut(), + |inner, cons| cons(&mut inner.message) + ) + } + + /// Verify `self.signature`. + /// + /// If the root of `block.message` is already known it can be passed in via `object_root_opt`. + /// Otherwise, it will be computed locally. + pub fn verify_signature( + &self, + object_root_opt: Option, + pubkey: &PublicKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> bool { + // Refuse to verify the signature of a block if its structure does not match the fork at + // `self.slot()`. + if self.fork_name(spec).is_err() { + return false; + } + + let domain = spec.get_domain( + self.slot().epoch(E::slots_per_epoch()), + Domain::BeaconProposer, + fork, + genesis_validators_root, + ); + + let message = if let Some(object_root) = object_root_opt { + SigningData { + object_root, + domain, + } + .tree_hash_root() + } else { + self.message().signing_root(domain) + }; + + self.signature().verify(pubkey, message) + } + + /// Produce a signed beacon block header corresponding to this block. + pub fn signed_block_header(&self) -> SignedBeaconBlockHeader { + SignedBeaconBlockHeader { + message: self.message().block_header(), + signature: self.signature().clone(), + } + } + + /// Convenience accessor for the block's slot. + pub fn slot(&self) -> Slot { + self.message().slot() + } + + /// Convenience accessor for the block's parent root. + pub fn parent_root(&self) -> Hash256 { + self.message().parent_root() + } + + /// Convenience accessor for the block's state root. + pub fn state_root(&self) -> Hash256 { + self.message().state_root() + } + + /// Returns the `tree_hash_root` of the block. + pub fn canonical_root(&self) -> Hash256 { + self.message().tree_hash_root() + } +} + +// We can convert pre-Bellatrix blocks without payloads into blocks with payloads. +impl From>> + for SignedBeaconBlockBase> +{ + fn from(signed_block: SignedBeaconBlockBase>) -> Self { + let SignedBeaconBlockBase { message, signature } = signed_block; + SignedBeaconBlockBase { + message: message.into(), + signature, + } + } +} + +impl From>> + for SignedBeaconBlockAltair> +{ + fn from(signed_block: SignedBeaconBlockAltair>) -> Self { + let SignedBeaconBlockAltair { message, signature } = signed_block; + SignedBeaconBlockAltair { + message: message.into(), + signature, + } + } +} + +// Post-Bellatrix blocks can be "unblinded" by adding the full payload. +// NOTE: It might be nice to come up with a `superstruct` pattern to abstract over this before +// the first fork after Bellatrix. +impl SignedBeaconBlockMerge> { + pub fn into_full_block( + self, + execution_payload: ExecutionPayloadMerge, + ) -> SignedBeaconBlockMerge> { + let SignedBeaconBlockMerge { + message: + BeaconBlockMerge { + slot, + proposer_index, + parent_root, + state_root, + body: + BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadMerge { .. }, + }, + }, + signature, + } = self; + SignedBeaconBlockMerge { + message: BeaconBlockMerge { + slot, + proposer_index, + parent_root, + state_root, + body: BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + }, + }, + signature, + } + } +} + +impl SignedBeaconBlockCapella> { + pub fn into_full_block( + self, + execution_payload: ExecutionPayloadCapella, + ) -> SignedBeaconBlockCapella> { + let SignedBeaconBlockCapella { + message: + BeaconBlockCapella { + slot, + proposer_index, + parent_root, + state_root, + body: + BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadCapella { .. }, + bls_to_execution_changes, + }, + }, + signature, + } = self; + SignedBeaconBlockCapella { + message: BeaconBlockCapella { + slot, + proposer_index, + parent_root, + state_root, + body: BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + }, + }, + signature, + } + } +} + +impl SignedBeaconBlock> { + pub fn try_into_full_block( + self, + execution_payload: Option>, + ) -> Option>> { + let full_block = match (self, execution_payload) { + (SignedBeaconBlock::Base(block), _) => SignedBeaconBlock::Base(block.into()), + (SignedBeaconBlock::Altair(block), _) => SignedBeaconBlock::Altair(block.into()), + (SignedBeaconBlock::Merge(block), Some(ExecutionPayload::Merge(payload))) => { + SignedBeaconBlock::Merge(block.into_full_block(payload)) + } + (SignedBeaconBlock::Capella(block), Some(ExecutionPayload::Capella(payload))) => { + SignedBeaconBlock::Capella(block.into_full_block(payload)) + } + // avoid wildcard matching forks so that compiler will + // direct us here when a new fork has been added + (SignedBeaconBlock::Merge(_), _) => return None, + (SignedBeaconBlock::Capella(_), _) => return None, + }; + Some(full_block) + } +} + +// We can blind blocks with payloads by converting the payload into a header. +// +// We can optionally keep the header, or discard it. +impl From> + for (SignedBlindedBeaconBlock, Option>) +{ + fn from(signed_block: SignedBeaconBlock) -> Self { + let (block, signature) = signed_block.deconstruct(); + let (blinded_block, payload) = block.into(); + ( + SignedBeaconBlock::from_block(blinded_block, signature), + payload, + ) + } +} + +impl From> for SignedBlindedBeaconBlock { + fn from(signed_block: SignedBeaconBlock) -> Self { + let (blinded_block, _) = signed_block.into(); + blinded_block + } +} + +// We can blind borrowed blocks with payloads by converting the payload into a header (without +// cloning the payload contents). +impl SignedBeaconBlock { + pub fn clone_as_blinded(&self) -> SignedBlindedBeaconBlock { + SignedBeaconBlock::from_block(self.message().into(), self.signature().clone()) + } +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for SignedBeaconBlock +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + Ok(map_fork_name!( + fork_name, + Self, + serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!( + "SignedBeaconBlock failed to deserialize: {:?}", + e + )))? + )) + } +} diff --git a/primitives/beacon/src/signed_beacon_block_header.rs b/primitives/beacon/src/signed_beacon_block_header.rs new file mode 100644 index 00000000..d69a86f1 --- /dev/null +++ b/primitives/beacon/src/signed_beacon_block_header.rs @@ -0,0 +1,53 @@ +use crate::prelude::*; +use crate::{ + BeaconBlockHeader, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, Signature, SignedRoot, +}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A signed header of a `BeaconBlock`. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedBeaconBlockHeader { + pub message: BeaconBlockHeader, + pub signature: Signature, +} + +impl SignedBeaconBlockHeader { + /// Verify that this block header was signed by `pubkey`. + pub fn verify_signature( + &self, + pubkey: &PublicKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> bool { + let domain = spec.get_domain( + self.message.slot.epoch(E::slots_per_epoch()), + Domain::BeaconProposer, + fork, + genesis_validators_root, + ); + + let message = self.message.signing_root(domain); + + self.signature.verify(pubkey, message) + } +} diff --git a/primitives/beacon/src/signed_bls_to_execution_change.rs b/primitives/beacon/src/signed_bls_to_execution_change.rs new file mode 100644 index 00000000..ebe88c93 --- /dev/null +++ b/primitives/beacon/src/signed_bls_to_execution_change.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +use crate::*; +use bls::Signature; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedBlsToExecutionChange { + pub message: BlsToExecutionChange, + pub signature: Signature, +} diff --git a/primitives/beacon/src/signed_contribution_and_proof.rs b/primitives/beacon/src/signed_contribution_and_proof.rs new file mode 100644 index 00000000..8d37a019 --- /dev/null +++ b/primitives/beacon/src/signed_contribution_and_proof.rs @@ -0,0 +1,73 @@ +use super::{ + ChainSpec, ContributionAndProof, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, + SignedRoot, SyncCommitteeContribution, SyncSelectionProof, +}; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A Validators signed contribution proof to publish on the `sync_committee_contribution_and_proof` +/// gossipsub topic. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SignedContributionAndProof { + /// The `ContributionAndProof` that was signed. + pub message: ContributionAndProof, + /// The validator's signature of `message`. + pub signature: Signature, +} + +impl SignedContributionAndProof { + /// Produces a new `SignedContributionAndProof` with a `selection_proof` generated by signing + /// `aggregate.data.slot` with `secret_key`. + /// + /// If `selection_proof.is_none()` it will be computed locally. + pub fn from_aggregate( + aggregator_index: u64, + contribution: SyncCommitteeContribution, + selection_proof: Option, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let message = ContributionAndProof::from_aggregate( + aggregator_index, + contribution, + selection_proof, + secret_key, + fork, + genesis_validators_root, + spec, + ); + + let epoch = message.contribution.slot.epoch(T::slots_per_epoch()); + let domain = spec.get_domain( + epoch, + Domain::ContributionAndProof, + fork, + genesis_validators_root, + ); + let signing_message = message.signing_root(domain); + + SignedContributionAndProof { + message, + signature: secret_key.sign(signing_message), + } + } +} diff --git a/primitives/beacon/src/signed_voluntary_exit.rs b/primitives/beacon/src/signed_voluntary_exit.rs new file mode 100644 index 00000000..6cd0a93e --- /dev/null +++ b/primitives/beacon/src/signed_voluntary_exit.rs @@ -0,0 +1,30 @@ +use crate::VoluntaryExit; +use bls::Signature; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// An exit voluntarily submitted a validator who wishes to withdraw. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedVoluntaryExit { + pub message: VoluntaryExit, + pub signature: Signature, +} diff --git a/primitives/beacon/src/signing_data.rs b/primitives/beacon/src/signing_data.rs new file mode 100644 index 00000000..4bb90d9a --- /dev/null +++ b/primitives/beacon/src/signing_data.rs @@ -0,0 +1,36 @@ +use crate::Hash256; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SigningData { + pub object_root: Hash256, + pub domain: Hash256, +} + +pub trait SignedRoot: TreeHash { + fn signing_root(&self, domain: Hash256) -> Hash256 { + SigningData { + object_root: self.tree_hash_root(), + domain, + } + .tree_hash_root() + } +} diff --git a/primitives/beacon/src/slot_data.rs b/primitives/beacon/src/slot_data.rs new file mode 100644 index 00000000..19775913 --- /dev/null +++ b/primitives/beacon/src/slot_data.rs @@ -0,0 +1,13 @@ +use crate::Slot; + +/// A trait providing a `Slot` getter for messages that are related to a single slot. Useful in +/// making parts of attestation and sync committee processing generic. +pub trait SlotData { + fn get_slot(&self) -> Slot; +} + +impl SlotData for Slot { + fn get_slot(&self) -> Slot { + *self + } +} diff --git a/primitives/beacon/src/slot_epoch.rs b/primitives/beacon/src/slot_epoch.rs new file mode 100644 index 00000000..6ede2a75 --- /dev/null +++ b/primitives/beacon/src/slot_epoch.rs @@ -0,0 +1,167 @@ +//! The `Slot` and `Epoch` types are defined as new types over u64 to enforce type-safety between +//! the two types. +//! +//! `Slot` and `Epoch` have implementations which permit conversion, comparison and math operations +//! between each and `u64`, however specifically not between each other. +//! +//! All math operations on `Slot` and `Epoch` are saturating, they never wrap. +//! +//! It would be easy to define `PartialOrd` and other traits generically across all types which +//! implement `Into`, however this would allow operations between `Slots` and `Epochs` which +//! may lead to programming errors which are not detected by the compiler. + +use crate::{EthSpec, SignedRoot}; + +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use core::fmt; +use core::hash::Hash; +use core::iter::Iterator; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; +use ssz_types::typenum::Unsigned; + +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign}; + +#[derive( + Clone, + Copy, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Slot(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +#[derive( + Clone, + Copy, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Epoch(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +impl_common!(Slot); +impl_common!(Epoch); + +impl Slot { + pub const fn new(slot: u64) -> Slot { + Slot(slot) + } + + pub fn epoch(self, slots_per_epoch: u64) -> Epoch { + Epoch::new(self.0) + .safe_div(slots_per_epoch) + .expect("slots_per_epoch is not 0") + } + + pub fn epoch_with_spec(self) -> Epoch { + self.epoch(T::slots_per_epoch()) + } + + pub fn max_value() -> Slot { + Slot(u64::max_value()) + } +} + +impl Epoch { + pub const fn new(slot: u64) -> Epoch { + Epoch(slot) + } + + pub fn max_value() -> Epoch { + Epoch(u64::max_value()) + } + + /// The first slot in the epoch. + pub fn start_slot(self, slots_per_epoch: u64) -> Slot { + Slot::from(self.0.saturating_mul(slots_per_epoch)) + } + + /// The last slot in the epoch. + pub fn end_slot(self, slots_per_epoch: u64) -> Slot { + Slot::from( + self.0 + .saturating_mul(slots_per_epoch) + .saturating_add(slots_per_epoch.saturating_sub(1)), + ) + } + + /// Position of some slot inside an epoch, if any. + /// + /// E.g., the first `slot` in `epoch` is at position `0`. + pub fn position(self, slot: Slot, slots_per_epoch: u64) -> Option { + let start = self.start_slot(slots_per_epoch); + let end = self.end_slot(slots_per_epoch); + + if slot >= start && slot <= end { + slot.as_usize().checked_sub(start.as_usize()) + } else { + None + } + } + + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period( + &self, + epochs_per_sync_committee_period: Epoch, + ) -> Result { + Ok(self.safe_div(epochs_per_sync_committee_period)?.as_u64()) + } + + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period_with_spec(&self) -> Result { + Ok(self + .safe_div(Epoch::new(T::EpochsPerSyncCommitteePeriod::to_u64()))? + .as_u64()) + } + + pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter { + SlotIter { + current_iteration: 0, + epoch: self, + slots_per_epoch, + } + } +} + +pub struct SlotIter<'a> { + current_iteration: u64, + epoch: &'a Epoch, + slots_per_epoch: u64, +} + +impl<'a> Iterator for SlotIter<'a> { + type Item = Slot; + + fn next(&mut self) -> Option { + if self.current_iteration >= self.slots_per_epoch { + None + } else { + let start_slot = self.epoch.start_slot(self.slots_per_epoch); + let previous = self.current_iteration; + self.current_iteration = self.current_iteration.checked_add(1)?; + start_slot.safe_add(previous).ok() + } + } +} diff --git a/primitives/beacon/src/slot_epoch_macros.rs b/primitives/beacon/src/slot_epoch_macros.rs new file mode 100644 index 00000000..bb3cc582 --- /dev/null +++ b/primitives/beacon/src/slot_epoch_macros.rs @@ -0,0 +1,324 @@ +macro_rules! impl_from_into_u64 { + ($main: ident) => { + impl From for $main { + fn from(n: u64) -> $main { + $main(n) + } + } + + impl Into for $main { + fn into(self) -> u64 { + self.0 + } + } + + impl $main { + pub fn as_u64(&self) -> u64 { + self.0 + } + } + }; +} + +macro_rules! impl_from_into_usize { + ($main: ident) => { + impl From for $main { + fn from(n: usize) -> $main { + $main(n as u64) + } + } + + impl Into for $main { + fn into(self) -> usize { + self.0 as usize + } + } + + impl $main { + pub fn as_usize(&self) -> usize { + self.0 as usize + } + } + }; +} + +macro_rules! impl_u64_eq_ord { + ($type: ident) => { + impl PartialEq for $type { + fn eq(&self, other: &u64) -> bool { + self.as_u64() == *other + } + } + + impl PartialOrd for $type { + fn partial_cmp(&self, other: &u64) -> Option { + self.as_u64().partial_cmp(other) + } + } + }; +} + +macro_rules! impl_safe_arith { + ($type: ident, $rhs_ty: ident) => { + impl crate::safe_arith::SafeArith<$rhs_ty> for $type { + const ZERO: Self = $type::new(0); + const ONE: Self = $type::new(1); + + fn safe_add(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_add(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_sub(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_sub(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_mul(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_mul(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_div(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_div(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::DivisionByZero) + } + + fn safe_rem(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_rem(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::DivisionByZero) + } + + fn safe_shl(&self, other: u32) -> crate::safe_arith::Result { + self.0 + .checked_shl(other) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_shr(&self, other: u32) -> crate::safe_arith::Result { + self.0 + .checked_shr(other) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + } + }; +} + +macro_rules! impl_math_between { + ($main: ident, $other: ident) => { + impl Add<$other> for $main { + type Output = $main; + + fn add(self, other: $other) -> $main { + $main::from(self.0.saturating_add(other.into())) + } + } + + impl AddAssign<$other> for $main { + fn add_assign(&mut self, other: $other) { + self.0 = self.0.saturating_add(other.into()); + } + } + + impl Sub<$other> for $main { + type Output = $main; + + fn sub(self, other: $other) -> $main { + $main::from(self.0.saturating_sub(other.into())) + } + } + + impl SubAssign<$other> for $main { + fn sub_assign(&mut self, other: $other) { + self.0 = self.0.saturating_sub(other.into()); + } + } + + impl Mul<$other> for $main { + type Output = $main; + + fn mul(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + $main::from(self.0.saturating_mul(rhs)) + } + } + + impl MulAssign<$other> for $main { + fn mul_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + self.0 = self.0.saturating_mul(rhs) + } + } + + impl Div<$other> for $main { + type Output = $main; + + fn div(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + $main::from( + self.0 + .checked_div(rhs) + .expect("Cannot divide by zero-valued Slot/Epoch"), + ) + } + } + + impl DivAssign<$other> for $main { + fn div_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + self.0 = self + .0 + .checked_div(rhs) + .expect("Cannot divide by zero-valued Slot/Epoch"); + } + } + + impl Rem<$other> for $main { + type Output = $main; + + fn rem(self, modulus: $other) -> $main { + let modulus: u64 = modulus.into(); + $main::from( + self.0 + .checked_rem(modulus) + .expect("Cannot divide by zero-valued Slot/Epoch"), + ) + } + } + }; +} + +macro_rules! impl_math { + ($type: ident) => { + impl $type { + pub fn saturating_sub>(&self, other: T) -> $type { + $type::new(self.as_u64().saturating_sub(other.into().as_u64())) + } + + pub fn saturating_add>(&self, other: T) -> $type { + $type::new(self.as_u64().saturating_add(other.into().as_u64())) + } + + pub fn is_power_of_two(&self) -> bool { + self.0.is_power_of_two() + } + } + }; +} + +macro_rules! impl_display { + ($type: ident) => { + impl fmt::Display for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + }; +} + +macro_rules! impl_debug { + ($type: ident) => { + impl fmt::Debug for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}({:?})", stringify!($type), self.0) + } + } + }; +} + +macro_rules! impl_ssz { + ($type: ident) => { + impl Encode for $type { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + 0_u64.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } + } + + impl Decode for $type { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Ok($type(u64::from_ssz_bytes(bytes)?)) + } + } + + impl tree_hash::TreeHash for $type { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + 32usize.wrapping_div(8) + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + tree_hash::Hash256::from_slice(&crate::int_to_bytes::int_to_fixed_bytes32(self.0)) + } + } + + impl SignedRoot for $type {} + }; +} + +macro_rules! impl_from_str { + ($type: ident) => { + impl core::str::FromStr for $type { + type Err = core::num::ParseIntError; + + fn from_str(s: &str) -> Result<$type, Self::Err> { + u64::from_str(s).map($type) + } + } + }; +} + +macro_rules! impl_common { + ($type: ident) => { + impl_from_into_u64!($type); + impl_from_into_usize!($type); + impl_u64_eq_ord!($type); + impl_safe_arith!($type, $type); + impl_safe_arith!($type, u64); + impl_math_between!($type, $type); + impl_math_between!($type, u64); + impl_math!($type); + impl_display!($type); + impl_debug!($type); + impl_ssz!($type); + impl_from_str!($type); + }; +} diff --git a/primitives/beacon/src/subnet_id.rs b/primitives/beacon/src/subnet_id.rs new file mode 100644 index 00000000..c423d534 --- /dev/null +++ b/primitives/beacon/src/subnet_id.rs @@ -0,0 +1,125 @@ +//! Identifies each shard by an integer identifier. +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::{AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot}; +use core::ops::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; + +const MAX_SUBNET_ID: usize = 64; + +lazy_static::lazy_static! { + static ref SUBNET_ID_TO_STRING: Vec = { + let mut v = Vec::with_capacity(MAX_SUBNET_ID); + + for i in 0..MAX_SUBNET_ID { + v.push(i.to_string()); + } + v + }; +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct SubnetId(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +pub fn subnet_id_to_string(i: u64) -> &'static str { + if i < MAX_SUBNET_ID as u64 { + SUBNET_ID_TO_STRING + .get(i as usize) + .expect("index below MAX_SUBNET_ID") + } else { + "subnet id out of range" + } +} + +impl SubnetId { + pub fn new(id: u64) -> Self { + id.into() + } + + /// Compute the subnet for an attestation with `attestation_data` where each slot in the + /// attestation epoch contains `committee_count_per_slot` committees. + pub fn compute_subnet_for_attestation_data( + attestation_data: &AttestationData, + committee_count_per_slot: u64, + spec: &ChainSpec, + ) -> Result { + Self::compute_subnet::( + attestation_data.slot, + attestation_data.index, + committee_count_per_slot, + spec, + ) + } + + /// Compute the subnet for an attestation with `attestation.data.slot == slot` and + /// `attestation.data.index == committee_index` where each slot in the attestation epoch + /// contains `committee_count_at_slot` committees. + pub fn compute_subnet( + slot: Slot, + committee_index: CommitteeIndex, + committee_count_at_slot: u64, + spec: &ChainSpec, + ) -> Result { + let slots_since_epoch_start: u64 = slot.as_u64().safe_rem(T::slots_per_epoch())?; + + let committees_since_epoch_start = + committee_count_at_slot.safe_mul(slots_since_epoch_start)?; + + Ok(committees_since_epoch_start + .safe_add(committee_index)? + .safe_rem(spec.attestation_subnet_count)? + .into()) + } +} + +impl Deref for SubnetId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SubnetId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for SubnetId { + fn from(x: u64) -> Self { + Self(x) + } +} + +impl Into for SubnetId { + fn into(self) -> u64 { + self.0 + } +} + +impl Into for &SubnetId { + fn into(self) -> u64 { + self.0 + } +} + +impl AsRef for SubnetId { + fn as_ref(&self) -> &str { + subnet_id_to_string(self.0) + } +} diff --git a/primitives/beacon/src/sync_aggregate.rs b/primitives/beacon/src/sync_aggregate.rs new file mode 100644 index 00000000..861f60b6 --- /dev/null +++ b/primitives/beacon/src/sync_aggregate.rs @@ -0,0 +1,97 @@ +use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::{AggregateSignature, BitVector, EthSpec, SyncCommitteeContribution}; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive(Debug, PartialEq)] +pub enum Error { + SszTypesError(ssz_types::Error), + ArithError(ArithError), +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SyncAggregate { + pub sync_committee_bits: BitVector, + pub sync_committee_signature: AggregateSignature, +} + +impl SyncAggregate { + /// New aggregate to be used as the seed for aggregating other signatures. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + sync_committee_bits: BitVector::default(), + sync_committee_signature: AggregateSignature::infinity(), + } + } + + /// Create a `SyncAggregate` from a slice of `SyncCommitteeContribution`s. + /// + /// Equivalent to `process_sync_committee_contributions` from the spec. + pub fn from_contributions( + contributions: &[SyncCommitteeContribution], + ) -> Result, Error> { + let mut sync_aggregate = Self::new(); + let sync_subcommittee_size = + T::sync_committee_size().safe_div(SYNC_COMMITTEE_SUBNET_COUNT as usize)?; + for contribution in contributions { + for (index, participated) in contribution.aggregation_bits.iter().enumerate() { + if participated { + let participant_index = sync_subcommittee_size + .safe_mul(contribution.subcommittee_index as usize)? + .safe_add(index)?; + sync_aggregate + .sync_committee_bits + .set(participant_index, true) + .map_err(Error::SszTypesError)?; + } + } + sync_aggregate + .sync_committee_signature + .add_assign_aggregate(&contribution.signature); + } + Ok(sync_aggregate) + } + + /// Empty aggregate to be used at genesis. + /// + /// Contains an empty signature and should *not* be used as the starting point for aggregation, + /// use `new` instead. + pub fn empty() -> Self { + Self { + sync_committee_bits: BitVector::default(), + sync_committee_signature: AggregateSignature::empty(), + } + } + + /// Returns how many bits are `true` in `self.sync_committee_bits`. + pub fn num_set_bits(&self) -> usize { + self.sync_committee_bits.num_set_bits() + } +} diff --git a/primitives/beacon/src/sync_aggregator_selection_data.rs b/primitives/beacon/src/sync_aggregator_selection_data.rs new file mode 100644 index 00000000..8b6e6c7e --- /dev/null +++ b/primitives/beacon/src/sync_aggregator_selection_data.rs @@ -0,0 +1,29 @@ +use crate::{SignedRoot, Slot}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Hash, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SyncAggregatorSelectionData { + pub slot: Slot, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub subcommittee_index: u64, +} + +impl SignedRoot for SyncAggregatorSelectionData {} diff --git a/primitives/beacon/src/sync_committee.rs b/primitives/beacon/src/sync_committee.rs new file mode 100644 index 00000000..1c9f7a9b --- /dev/null +++ b/primitives/beacon/src/sync_committee.rs @@ -0,0 +1,103 @@ +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::typenum::Unsigned; +use crate::{EthSpec, FixedVector, SyncSubnetId}; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive(Debug, PartialEq)] +pub enum Error { + ArithError(ArithError), + InvalidSubcommitteeRange { + start_subcommittee_index: usize, + end_subcommittee_index: usize, + subcommittee_index: usize, + }, +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SyncCommittee { + pub pubkeys: FixedVector, + pub aggregate_pubkey: PublicKeyBytes, +} + +impl SyncCommittee { + /// Create a temporary sync committee that should *never* be included in a legitimate consensus object. + pub fn temporary() -> Result { + Ok(Self { + pubkeys: FixedVector::new(vec![ + PublicKeyBytes::empty(); + T::SyncCommitteeSize::to_usize() + ])?, + aggregate_pubkey: PublicKeyBytes::empty(), + }) + } + + /// Return the pubkeys in this `SyncCommittee` for the given `subcommittee_index`. + pub fn get_subcommittee_pubkeys( + &self, + subcommittee_index: usize, + ) -> Result, Error> { + let start_subcommittee_index = subcommittee_index.safe_mul(T::sync_subcommittee_size())?; + let end_subcommittee_index = + start_subcommittee_index.safe_add(T::sync_subcommittee_size())?; + self.pubkeys + .get(start_subcommittee_index..end_subcommittee_index) + .ok_or(Error::InvalidSubcommitteeRange { + start_subcommittee_index, + end_subcommittee_index, + subcommittee_index, + }) + .map(|s| s.to_vec()) + } + + /// For a given `pubkey`, finds all subcommittees that it is included in, and maps the + /// subcommittee index (typed as `SyncSubnetId`) to all positions this `pubkey` is associated + /// with within the subcommittee. + pub fn subcommittee_positions_for_public_key( + &self, + pubkey: &PublicKeyBytes, + ) -> Result>, Error> { + let mut subnet_positions = BTreeMap::new(); + for (committee_index, validator_pubkey) in self.pubkeys.iter().enumerate() { + if pubkey == validator_pubkey { + let subcommittee_index = committee_index.safe_div(T::sync_subcommittee_size())?; + let position_in_subcommittee = + committee_index.safe_rem(T::sync_subcommittee_size())?; + subnet_positions + .entry(SyncSubnetId::new(subcommittee_index as u64)) + .or_insert_with(Vec::new) + .push(position_in_subcommittee); + } + } + Ok(subnet_positions) + } + + /// Returns `true` if the pubkey exists in the `SyncCommittee`. + pub fn contains(&self, pubkey: &PublicKeyBytes) -> bool { + self.pubkeys.contains(pubkey) + } +} diff --git a/primitives/beacon/src/sync_committee_contribution.rs b/primitives/beacon/src/sync_committee_contribution.rs new file mode 100644 index 00000000..f4081bd5 --- /dev/null +++ b/primitives/beacon/src/sync_committee_contribution.rs @@ -0,0 +1,119 @@ +use super::{AggregateSignature, EthSpec, SignedRoot}; +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::slot_data::SlotData; +use crate::{BitVector, Hash256, Slot, SyncCommitteeMessage}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive(Debug, PartialEq)] +pub enum Error { + SszTypesError(ssz_types::Error), + AlreadySigned(usize), + SubnetCountIsZero(ArithError), +} + +/// An aggregation of `SyncCommitteeMessage`s, used in creating a `SignedContributionAndProof`. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SyncCommitteeContribution { + pub slot: Slot, + pub beacon_block_root: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub subcommittee_index: u64, + pub aggregation_bits: BitVector, + pub signature: AggregateSignature, +} + +impl SyncCommitteeContribution { + /// Create a `SyncCommitteeContribution` from: + /// + /// - `message`: A single `SyncCommitteeMessage`. + /// - `subcommittee_index`: The subcommittee this contribution pertains to out of the broader + /// sync committee. This can be determined from the `SyncSubnetId` of the gossip subnet + /// this message was seen on. + /// - `validator_sync_committee_index`: The index of the validator **within** the subcommittee. + pub fn from_message( + message: &SyncCommitteeMessage, + subcommittee_index: u64, + validator_sync_committee_index: usize, + ) -> Result { + let mut bits = BitVector::new(); + bits.set(validator_sync_committee_index, true) + .map_err(Error::SszTypesError)?; + Ok(Self { + slot: message.slot, + beacon_block_root: message.beacon_block_root, + subcommittee_index, + aggregation_bits: bits, + signature: AggregateSignature::from(&message.signature), + }) + } + + /// Are the aggregation bitfields of these sync contribution disjoint? + pub fn signers_disjoint_from(&self, other: &Self) -> bool { + self.aggregation_bits + .intersection(&other.aggregation_bits) + .is_zero() + } + + /// Aggregate another `SyncCommitteeContribution` into this one. + /// + /// The aggregation bitfields must be disjoint, and the data must be the same. + pub fn aggregate(&mut self, other: &Self) { + debug_assert_eq!(self.slot, other.slot); + debug_assert_eq!(self.beacon_block_root, other.beacon_block_root); + debug_assert_eq!(self.subcommittee_index, other.subcommittee_index); + debug_assert!(self.signers_disjoint_from(other)); + + self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); + self.signature.add_assign_aggregate(&other.signature); + } +} + +impl SignedRoot for Hash256 {} + +/// This is not in the spec, but useful for determining uniqueness of sync committee contributions +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash)] +pub struct SyncContributionData { + pub slot: Slot, + pub beacon_block_root: Hash256, + pub subcommittee_index: u64, +} + +impl SyncContributionData { + pub fn from_contribution(signing_data: &SyncCommitteeContribution) -> Self { + Self { + slot: signing_data.slot, + beacon_block_root: signing_data.beacon_block_root, + subcommittee_index: signing_data.subcommittee_index, + } + } +} + +impl SlotData for SyncCommitteeContribution { + fn get_slot(&self) -> Slot { + self.slot + } +} + +impl SlotData for SyncContributionData { + fn get_slot(&self) -> Slot { + self.slot + } +} diff --git a/primitives/beacon/src/sync_committee_message.rs b/primitives/beacon/src/sync_committee_message.rs new file mode 100644 index 00000000..7a0c52f8 --- /dev/null +++ b/primitives/beacon/src/sync_committee_message.rs @@ -0,0 +1,61 @@ +use crate::{ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, Slot}; + +use crate::prelude::*; +use crate::slot_data::SlotData; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data upon which a `SyncCommitteeContribution` is based. +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SyncCommitteeMessage { + pub slot: Slot, + pub beacon_block_root: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + // Signature by the validator over `beacon_block_root`. + pub signature: Signature, +} + +impl SyncCommitteeMessage { + /// Equivalent to `get_sync_committee_message` from the spec. + pub fn new( + slot: Slot, + beacon_block_root: Hash256, + validator_index: u64, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let epoch = slot.epoch(E::slots_per_epoch()); + let domain = spec.get_domain(epoch, Domain::SyncCommittee, fork, genesis_validators_root); + let message = beacon_block_root.signing_root(domain); + let signature = secret_key.sign(message); + Self { + slot, + beacon_block_root, + validator_index, + signature, + } + } +} + +impl SlotData for SyncCommitteeMessage { + fn get_slot(&self) -> Slot { + self.slot + } +} diff --git a/primitives/beacon/src/sync_committee_subscription.rs b/primitives/beacon/src/sync_committee_subscription.rs new file mode 100644 index 00000000..708b4f9f --- /dev/null +++ b/primitives/beacon/src/sync_committee_subscription.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; +use crate::Epoch; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// A sync committee subscription created when a validator subscribes to sync committee subnets to perform +/// sync committee duties. +#[derive( + PartialEq, + Debug, + Serialize, + Deserialize, + Clone, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, +)] +pub struct SyncCommitteeSubscription { + /// The validators index. + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + /// The sync committee indices. + #[serde(with = "eth2_serde_utils::quoted_u64_vec")] + pub sync_committee_indices: Vec, + /// Epoch until which this subscription is required. + pub until_epoch: Epoch, +} diff --git a/primitives/beacon/src/sync_duty.rs b/primitives/beacon/src/sync_duty.rs new file mode 100644 index 00000000..d47ef9bd --- /dev/null +++ b/primitives/beacon/src/sync_duty.rs @@ -0,0 +1,83 @@ +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::{EthSpec, SyncCommittee, SyncSubnetId}; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SyncDuty { + pub pubkey: PublicKeyBytes, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + #[serde(with = "eth2_serde_utils::quoted_u64_vec")] + pub validator_sync_committee_indices: Vec, +} + +impl SyncDuty { + /// Create a new `SyncDuty` from the list of validator indices in a sync committee. + pub fn from_sync_committee_indices( + validator_index: u64, + pubkey: PublicKeyBytes, + sync_committee_indices: &[usize], + ) -> Option { + // Positions of the `validator_index` within the committee. + let validator_sync_committee_indices = sync_committee_indices + .iter() + .enumerate() + .filter_map(|(i, &v)| { + if validator_index == v as u64 { + Some(i as u64) + } else { + None + } + }) + .collect(); + Self::new(validator_index, pubkey, validator_sync_committee_indices) + } + + /// Create a new `SyncDuty` from a `SyncCommittee`, which contains the pubkeys but not the + /// indices. + pub fn from_sync_committee( + validator_index: u64, + pubkey: PublicKeyBytes, + sync_committee: &SyncCommittee, + ) -> Option { + let validator_sync_committee_indices = sync_committee + .pubkeys + .iter() + .enumerate() + .filter_map(|(i, committee_pubkey)| { + if &pubkey == committee_pubkey { + Some(i as u64) + } else { + None + } + }) + .collect(); + Self::new(validator_index, pubkey, validator_sync_committee_indices) + } + + /// Create a duty if the `validator_sync_committee_indices` is non-empty. + fn new( + validator_index: u64, + pubkey: PublicKeyBytes, + validator_sync_committee_indices: Vec, + ) -> Option { + if !validator_sync_committee_indices.is_empty() { + Some(SyncDuty { + pubkey, + validator_index, + validator_sync_committee_indices, + }) + } else { + None + } + } + + /// Get the set of subnet IDs for this duty. + pub fn subnet_ids(&self) -> Result, ArithError> { + SyncSubnetId::compute_subnets_for_sync_committee::( + &self.validator_sync_committee_indices, + ) + } +} diff --git a/primitives/beacon/src/sync_selection_proof.rs b/primitives/beacon/src/sync_selection_proof.rs new file mode 100644 index 00000000..ca8ab465 --- /dev/null +++ b/primitives/beacon/src/sync_selection_proof.rs @@ -0,0 +1,106 @@ +use crate::consts::altair::{ + SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, +}; +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::{ + ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot, + SyncAggregatorSelectionData, +}; +use core::cmp; +use core::convert::TryInto; +use eth2_hashing::hash; +use ssz::Encode; +use ssz_types::typenum::Unsigned; + +#[derive(PartialEq, Debug, Clone)] +pub struct SyncSelectionProof(Signature); + +impl SyncSelectionProof { + pub fn new( + slot: Slot, + subcommittee_index: u64, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Self { + let domain = spec.get_domain( + slot.epoch(T::slots_per_epoch()), + Domain::SyncCommitteeSelectionProof, + fork, + genesis_validators_root, + ); + let message = SyncAggregatorSelectionData { + slot, + subcommittee_index, + } + .signing_root(domain); + + Self(secret_key.sign(message)) + } + + /// Returns the "modulo" used for determining if a `SyncSelectionProof` elects an aggregator. + pub fn modulo() -> Result { + Ok(cmp::max( + 1, + (T::SyncCommitteeSize::to_u64()) + .safe_div(SYNC_COMMITTEE_SUBNET_COUNT)? + .safe_div(TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE)?, + )) + } + + pub fn is_aggregator(&self) -> Result { + self.is_aggregator_from_modulo(Self::modulo::()?) + } + + pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result { + let signature_hash = hash(&self.0.as_ssz_bytes()); + let signature_hash_int = u64::from_le_bytes( + signature_hash + .get(0..8) + .expect("hash is 32 bytes") + .try_into() + .expect("first 8 bytes of signature should always convert to fixed array"), + ); + + signature_hash_int.safe_rem(modulo).map(|rem| rem == 0) + } + + pub fn verify( + &self, + slot: Slot, + subcommittee_index: u64, + pubkey: &PublicKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> bool { + let domain = spec.get_domain( + slot.epoch(T::slots_per_epoch()), + Domain::SyncCommitteeSelectionProof, + fork, + genesis_validators_root, + ); + let message = SyncAggregatorSelectionData { + slot, + subcommittee_index, + } + .signing_root(domain); + + self.0.verify(pubkey, message) + } +} + +impl Into for SyncSelectionProof { + fn into(self) -> Signature { + self.0 + } +} + +impl From for SyncSelectionProof { + fn from(sig: Signature) -> Self { + Self(sig) + } +} diff --git a/primitives/beacon/src/sync_subnet_id.rs b/primitives/beacon/src/sync_subnet_id.rs new file mode 100644 index 00000000..4df5610f --- /dev/null +++ b/primitives/beacon/src/sync_subnet_id.rs @@ -0,0 +1,97 @@ +//! Identifies each sync committee subnet by an integer identifier. +use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::EthSpec; +use core::fmt::{self, Display}; +use core::ops::{Deref, DerefMut}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use ssz_types::typenum::Unsigned; + +lazy_static! { + static ref SYNC_SUBNET_ID_TO_STRING: Vec = { + let mut v = Vec::with_capacity(SYNC_COMMITTEE_SUBNET_COUNT as usize); + + for i in 0..SYNC_COMMITTEE_SUBNET_COUNT { + v.push(i.to_string()); + } + v + }; +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] +#[serde(transparent)] +pub struct SyncSubnetId(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +pub fn sync_subnet_id_to_string(i: u64) -> &'static str { + if i < SYNC_COMMITTEE_SUBNET_COUNT { + SYNC_SUBNET_ID_TO_STRING + .get(i as usize) + .expect("index below SYNC_COMMITTEE_SUBNET_COUNT") + } else { + "sync subnet id out of range" + } +} + +impl SyncSubnetId { + pub fn new(id: u64) -> Self { + id.into() + } + + /// Compute required subnets to subscribe to given the sync committee indices. + pub fn compute_subnets_for_sync_committee( + sync_committee_indices: &[u64], + ) -> Result, ArithError> { + let subcommittee_size = T::SyncSubcommitteeSize::to_u64(); + + sync_committee_indices + .iter() + .map(|index| index.safe_div(subcommittee_size).map(Self::new)) + .collect() + } +} + +impl Display for SyncSubnetId { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self.0) + } +} + +impl Deref for SyncSubnetId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SyncSubnetId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for SyncSubnetId { + fn from(x: u64) -> Self { + Self(x) + } +} + +impl Into for SyncSubnetId { + fn into(self) -> u64 { + self.0 + } +} + +impl Into for &SyncSubnetId { + fn into(self) -> u64 { + self.0 + } +} + +impl AsRef for SyncSubnetId { + fn as_ref(&self) -> &str { + sync_subnet_id_to_string(self.0) + } +} diff --git a/primitives/beacon/src/validator.rs b/primitives/beacon/src/validator.rs new file mode 100644 index 00000000..805321d0 --- /dev/null +++ b/primitives/beacon/src/validator.rs @@ -0,0 +1,123 @@ +use crate::prelude::*; +use crate::{Address, ChainSpec, Epoch, Hash256, PublicKeyBytes}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Information about a `BeaconChain` validator. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Validator { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub effective_balance: u64, + pub slashed: bool, + pub activation_eligibility_epoch: Epoch, + pub activation_epoch: Epoch, + pub exit_epoch: Epoch, + pub withdrawable_epoch: Epoch, +} + +impl Validator { + /// Returns `true` if the validator is considered active at some epoch. + pub fn is_active_at(&self, epoch: Epoch) -> bool { + self.activation_epoch <= epoch && epoch < self.exit_epoch + } + + /// Returns `true` if the validator is slashable at some epoch. + pub fn is_slashable_at(&self, epoch: Epoch) -> bool { + !self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch + } + + /// Returns `true` if the validator is considered exited at some epoch. + pub fn is_exited_at(&self, epoch: Epoch) -> bool { + self.exit_epoch <= epoch + } + + /// Returns `true` if the validator is able to withdraw at some epoch. + pub fn is_withdrawable_at(&self, epoch: Epoch) -> bool { + epoch >= self.withdrawable_epoch + } + + /// Returns `true` if the validator is eligible to join the activation queue. + /// + /// Spec v0.12.1 + pub fn is_eligible_for_activation_queue(&self, spec: &ChainSpec) -> bool { + self.activation_eligibility_epoch == spec.far_future_epoch + && self.effective_balance == spec.max_effective_balance + } + + /// Returns `true` if the validator has eth1 withdrawal credential. + pub fn has_eth1_withdrawal_credential(&self, spec: &ChainSpec) -> bool { + self.withdrawal_credentials + .as_bytes() + .first() + .map(|byte| *byte == spec.eth1_address_withdrawal_prefix_byte) + .unwrap_or(false) + } + + /// Get the eth1 withdrawal address if this validator has one initialized. + pub fn get_eth1_withdrawal_address(&self, spec: &ChainSpec) -> Option
{ + self.has_eth1_withdrawal_credential(spec) + .then(|| { + self.withdrawal_credentials + .as_bytes() + .get(12..) + .map(Address::from_slice) + }) + .flatten() + } + + /// Changes withdrawal credentials to the provided eth1 execution address. + /// + /// WARNING: this function does NO VALIDATION - it just does it! + pub fn change_withdrawal_credentials(&mut self, execution_address: &Address, spec: &ChainSpec) { + let mut bytes = [0u8; 32]; + bytes[0] = spec.eth1_address_withdrawal_prefix_byte; + bytes[12..].copy_from_slice(execution_address.as_bytes()); + self.withdrawal_credentials = Hash256::from(bytes); + } + + /// Returns `true` if the validator is fully withdrawable at some epoch. + pub fn is_fully_withdrawable_at(&self, balance: u64, epoch: Epoch, spec: &ChainSpec) -> bool { + self.has_eth1_withdrawal_credential(spec) && self.withdrawable_epoch <= epoch && balance > 0 + } + + /// Returns `true` if the validator is partially withdrawable. + pub fn is_partially_withdrawable_validator(&self, balance: u64, spec: &ChainSpec) -> bool { + self.has_eth1_withdrawal_credential(spec) + && self.effective_balance == spec.max_effective_balance + && balance > spec.max_effective_balance + } +} + +impl Default for Validator { + /// Yields a "default" `Validator`. Primarily used for testing. + fn default() -> Self { + Self { + pubkey: PublicKeyBytes::empty(), + withdrawal_credentials: Hash256::default(), + activation_eligibility_epoch: Epoch::from(core::u64::MAX), + activation_epoch: Epoch::from(core::u64::MAX), + exit_epoch: Epoch::from(core::u64::MAX), + withdrawable_epoch: Epoch::from(core::u64::MAX), + slashed: false, + effective_balance: core::u64::MAX, + } + } +} diff --git a/primitives/beacon/src/validator_registration_data.rs b/primitives/beacon/src/validator_registration_data.rs new file mode 100644 index 00000000..40b8de64 --- /dev/null +++ b/primitives/beacon/src/validator_registration_data.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Validator registration, for use in interacting with servers implementing the builder API. +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +pub struct SignedValidatorRegistrationData { + pub message: ValidatorRegistrationData, + pub signature: Signature, +} + +#[derive( + PartialEq, + Debug, + Serialize, + Deserialize, + Clone, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ValidatorRegistrationData { + pub fee_recipient: Address, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub timestamp: u64, + pub pubkey: PublicKeyBytes, +} + +impl SignedRoot for ValidatorRegistrationData {} diff --git a/primitives/beacon/src/validator_subscription.rs b/primitives/beacon/src/validator_subscription.rs new file mode 100644 index 00000000..610fe6fc --- /dev/null +++ b/primitives/beacon/src/validator_subscription.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; + +/// A validator subscription, created when a validator subscribes to a slot to perform optional aggregation +/// duties. +#[derive( + PartialEq, + Debug, + Serialize, + Deserialize, + Clone, + Encode, + Decode, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ValidatorSubscription { + /// The validators index. + pub validator_index: u64, + /// The index of the committee within `slot` of which the validator is a member. Used by the + /// beacon node to quickly evaluate the associated `SubnetId`. + pub attestation_committee_index: CommitteeIndex, + /// The slot in which to subscribe. + pub slot: Slot, + /// Committee count at slot to subscribe. + pub committee_count_at_slot: u64, + /// If true, the validator is an aggregator and the beacon node should aggregate attestations + /// for this slot. + pub is_aggregator: bool, +} diff --git a/primitives/beacon/src/voluntary_exit.rs b/primitives/beacon/src/voluntary_exit.rs new file mode 100644 index 00000000..ebbdab17 --- /dev/null +++ b/primitives/beacon/src/voluntary_exit.rs @@ -0,0 +1,55 @@ +use crate::{ChainSpec, Domain, Epoch, Fork, Hash256, SecretKey, SignedRoot, SignedVoluntaryExit}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// An exit voluntarily submitted a validator who wishes to withdraw. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct VoluntaryExit { + /// Earliest epoch when voluntary exit can be processed. + pub epoch: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, +} + +impl SignedRoot for VoluntaryExit {} + +impl VoluntaryExit { + pub fn sign( + self, + secret_key: &SecretKey, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedVoluntaryExit { + let domain = spec.get_domain( + self.epoch, + Domain::VoluntaryExit, + fork, + genesis_validators_root, + ); + let message = self.signing_root(domain); + SignedVoluntaryExit { + message: self, + signature: secret_key.sign(message), + } + } +} diff --git a/primitives/beacon/src/withdrawal.rs b/primitives/beacon/src/withdrawal.rs new file mode 100644 index 00000000..d985192c --- /dev/null +++ b/primitives/beacon/src/withdrawal.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Withdrawal { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub index: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + pub address: Address, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub amount: u64, +} diff --git a/primitives/ethereum/Cargo.toml b/primitives/ethereum/Cargo.toml index 197c7b2a..5e7720a7 100644 --- a/primitives/ethereum/Cargo.toml +++ b/primitives/ethereum/Cargo.toml @@ -24,8 +24,7 @@ hex-literal = { version = "0.3.1", default-features = false } libsecp256k1 = { version = "0.7", default-features = false } parity-bytes = { version = "0.1.2", default-features = false } rlp = { version = "0.5", default-features = false } -serde = { version = "1.0.101", optional = true, features = ["derive"] } -getrandom = { version = "0.2.1", features = ["js"] } +serde = { version = "1.0.101", optional = true, features = ["derive"], default-features = false } frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } diff --git a/primitives/ethereum/src/beacon/config.rs b/primitives/ethereum/src/beacon.rs similarity index 51% rename from primitives/ethereum/src/beacon/config.rs rename to primitives/ethereum/src/beacon.rs index e257e1bd..b4767d02 100644 --- a/primitives/ethereum/src/beacon/config.rs +++ b/primitives/ethereum/src/beacon.rs @@ -1,38 +1,42 @@ -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::RuntimeDebug; use hex_literal::hex; use scale_info::TypeInfo; -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub type ForkVersion = [u8; 4]; + +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct BeaconFork { #[cfg_attr( feature = "std", serde(with = "crate::serde_utils::serde_fork_version") )] - fork_version: ForkVersion, + version: [u8; 4], epoch: u64, } -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct BeaconForkSchedule { pub phase0: BeaconFork, pub altair: BeaconFork, pub bellatrix: BeaconFork, + pub capella: BeaconFork, // Disabled forks - // capella: BeaconFork, // sharding: BeaconFork, } impl BeaconForkSchedule { pub fn fork_version(&self, epoch: u64) -> ForkVersion { - if epoch >= self.bellatrix.epoch { - self.bellatrix.fork_version + if epoch >= self.capella.epoch { + self.capella.version + } else if epoch >= self.bellatrix.epoch { + self.bellatrix.version } else if epoch >= self.altair.epoch { - self.altair.fork_version + self.altair.version } else { - self.phase0.fork_version + self.phase0.version } } @@ -40,17 +44,21 @@ impl BeaconForkSchedule { pub fn sepolia() -> Self { Self { phase0: BeaconFork { - fork_version: hex!("90000069"), + version: hex!("90000069"), epoch: 0, }, altair: BeaconFork { - fork_version: hex!("90000070"), + version: hex!("90000070"), epoch: 50, }, bellatrix: BeaconFork { - fork_version: hex!("90000071"), + version: hex!("90000071"), epoch: 100, }, + capella: BeaconFork { + version: hex!("90000072"), + epoch: 56832, + }, } } @@ -58,17 +66,21 @@ impl BeaconForkSchedule { pub fn goerli() -> Self { Self { phase0: BeaconFork { - fork_version: hex!("00001020"), + version: hex!("00001020"), epoch: 0, }, altair: BeaconFork { - fork_version: hex!("01001020"), + version: hex!("01001020"), epoch: 36660, }, bellatrix: BeaconFork { - fork_version: hex!("02001020"), + version: hex!("02001020"), epoch: 112260, }, + capella: BeaconFork { + version: hex!("03001020"), + epoch: 162304, + }, } } @@ -76,46 +88,54 @@ impl BeaconForkSchedule { pub fn mainnet() -> Self { Self { phase0: BeaconFork { - fork_version: hex!("00000000"), + version: hex!("00000000"), epoch: 0, }, altair: BeaconFork { - fork_version: hex!("01000000"), + version: hex!("01000000"), epoch: 74240, }, bellatrix: BeaconFork { - fork_version: hex!("02000000"), + version: hex!("02000000"), epoch: 144896, }, + capella: BeaconFork { + version: hex!("04000000"), + epoch: u64::MAX, + }, } } pub fn local() -> Self { Self { phase0: BeaconFork { - fork_version: hex!("00000001"), + version: hex!("00000001"), epoch: 0, }, altair: BeaconFork { - fork_version: hex!("01000001"), + version: hex!("01000001"), epoch: 0, }, bellatrix: BeaconFork { - fork_version: hex!("02000001"), + version: hex!("02000001"), epoch: 0, }, + capella: BeaconFork { + version: hex!("03000001"), + epoch: u64::MAX, + }, } } } -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum BeaconNetworkConfig { Mainnet, Minimal, } -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct BeaconConsensusConfig { pub config: BeaconNetworkConfig, @@ -151,79 +171,7 @@ impl BeaconConsensusConfig { } } - pub fn fork_version_from_slot(&self, slot: u64) -> ForkVersion { - let epoch = self.config.compute_epoch(slot); + pub fn fork_version_from_epoch(&self, epoch: u64) -> ForkVersion { self.fork_schedule.fork_version(epoch) } } - -impl BeaconNetworkConfig { - pub fn compute_current_sync_period(&self, slot: u64) -> u64 { - match self { - Self::Minimal => { - slot / MINIMAL_SLOTS_PER_EPOCH / MINIMAL_EPOCHS_PER_SYNC_COMMITTEE_PERIOD - } - Self::Mainnet => { - slot / MAINNET_SLOTS_PER_EPOCH / MAINNET_EPOCHS_PER_SYNC_COMMITTEE_PERIOD - } - } - } - - pub fn compute_epoch(&self, slot: u64) -> u64 { - match self { - Self::Minimal => slot / MINIMAL_SLOTS_PER_EPOCH, - Self::Mainnet => slot / MAINNET_SLOTS_PER_EPOCH, - } - } - - pub fn epoch_length(&self) -> u64 { - match self { - Self::Minimal => MINIMAL_SLOTS_PER_EPOCH, - Self::Mainnet => MAINNET_SLOTS_PER_EPOCH, - } - } -} - -pub const MAINNET_SLOTS_PER_EPOCH: u64 = 32; -pub const MAINNET_EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 256; -pub const MAINNET_SYNC_COMMITTEE_SIZE: usize = 512; - -pub const MINIMAL_SLOTS_PER_EPOCH: u64 = 8; -pub const MINIMAL_EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 8; -pub const MINIMAL_SYNC_COMMITTEE_SIZE: usize = 32; - -use super::ForkVersion; - -pub const CURRENT_SYNC_COMMITTEE_INDEX: u64 = 22; -pub const CURRENT_SYNC_COMMITTEE_DEPTH: u64 = 5; - -pub const NEXT_SYNC_COMMITTEE_DEPTH: u64 = 5; -pub const NEXT_SYNC_COMMITTEE_INDEX: u64 = 23; - -pub const FINALIZED_ROOT_DEPTH: u64 = 6; -pub const FINALIZED_ROOT_INDEX: u64 = 41; - -pub const MAX_PROPOSER_SLASHINGS: usize = 16; -pub const MAX_ATTESTER_SLASHINGS: usize = 2; -pub const MAX_ATTESTATIONS: usize = 128; -pub const MAX_DEPOSITS: usize = 16; -pub const MAX_VOLUNTARY_EXITS: usize = 16; -pub const MAX_VALIDATORS_PER_COMMITTEE: usize = 2048; -pub const MAX_EXTRA_DATA_BYTES: usize = 32; -pub const MAX_LOGS_BLOOM_SIZE: usize = 256; -pub const MAX_FEE_RECIPIENT_SIZE: usize = 20; -pub const MAX_TRANSACTIONS: usize = 1048576; -pub const MAX_BYTES_PER_TRANSACTION: usize = 1073741824; -pub const MAX_H256_PER_TRANSACTION: usize = (MAX_BYTES_PER_TRANSACTION + 31) / 32; - -pub const DEPOSIT_CONTRACT_TREE_DEPTH: usize = 32; - -/// GENESIS_FORK_VERSION('0x00000000') -pub const GENESIS_FORK_VERSION: ForkVersion = [30, 30, 30, 30]; - -/// DomainType('0x07000000') -/// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#domain-types -pub const DOMAIN_SYNC_COMMITTEE: [u8; 4] = [7, 0, 0, 0]; - -pub const PUBKEY_SIZE: usize = 48; -pub const SIGNATURE_SIZE: usize = 96; diff --git a/primitives/ethereum/src/beacon/mod.rs b/primitives/ethereum/src/beacon/mod.rs deleted file mode 100644 index e249478b..00000000 --- a/primitives/ethereum/src/beacon/mod.rs +++ /dev/null @@ -1,401 +0,0 @@ -pub mod config; - -use crate::mpt; - -#[cfg(feature = "std")] -use crate::serde_utils::{serde_hex, serde_str}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_core::{H160, H256, U256}; -use sp_runtime::{ - traits::{Hash, Keccak256}, - RuntimeDebug, -}; -use sp_std::prelude::*; - -#[cfg(feature = "std")] -use core::fmt::Formatter; -#[cfg(feature = "std")] -use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "std")] -use sp_std::fmt::Result as StdResult; - -pub fn keccak_256(data: &[u8]) -> [u8; 32] { - Keccak256::hash(data).0 -} - -pub type Root = H256; -pub type Domain = H256; -pub type ValidatorIndex = u64; -pub type ProofBranch = Vec; -pub type ForkVersion = [u8; 4]; - -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct PublicKey(pub [u8; 48]); - -impl Default for PublicKey { - fn default() -> Self { - PublicKey([0u8; 48]) - } -} - -#[cfg(feature = "std")] -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_bytes(&self.0) - } -} - -#[cfg(feature = "std")] -struct PublicKeyVisitor; - -#[cfg(feature = "std")] -impl<'de> Visitor<'de> for PublicKeyVisitor { - type Value = PublicKey; - - fn expecting(&self, formatter: &mut Formatter) -> StdResult { - formatter.write_str("a hex string") - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - let str_without_0x = match v.strip_prefix("0x") { - Some(val) => val, - None => v, - }; - - let hex_bytes = match hex::decode(str_without_0x) { - Ok(bytes) => bytes, - Err(e) => return Err(Error::custom(e.to_string())), - }; - if hex_bytes.len() != 48 { - return Err(Error::custom("publickey expected to be 48 characters")); - } - - let mut data = [0u8; 48]; - data[0..48].copy_from_slice(&hex_bytes); - Ok(PublicKey(data)) - } -} - -#[cfg(feature = "std")] -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(PublicKeyVisitor) - } -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct ForkData { - // 1 or 0 bit, indicates whether a sync committee participated in a vote - pub current_version: [u8; 4], - pub genesis_validators_root: [u8; 32], -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct SigningData { - pub object_root: Root, - pub domain: Domain, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ExecutionHeader { - pub parent_hash: H256, - pub fee_recipient: H160, - pub state_root: H256, - pub receipts_root: H256, - pub logs_bloom: Vec, - pub prev_randao: H256, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub block_number: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub gas_limit: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub gas_used: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub timestamp: u64, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub extra_data: Vec, - pub base_fee_per_gas: U256, - pub block_hash: H256, - pub transactions_root: H256, -} - -/// Sync committee as it is stored in the runtime storage. -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct SyncCommittee { - pub pubkeys: Vec, - pub aggregate_pubkey: PublicKey, -} - -/// Beacon block header as it is stored in the runtime storage. The block root is the -/// Merklization of a BeaconHeader. -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct BeaconHeader { - // The slot for which this block is created. Must be greater than the slot of the block defined by parentRoot. - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub slot: u64, - // The index of the validator that proposed the block. - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub proposer_index: ValidatorIndex, - // The block root of the parent block, forming a block chain. - pub parent_root: Root, - // The hash root of the post state of running the state transition through this block. - pub state_root: Root, - // The hash root of the beacon block body - pub body_root: Root, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct DepositData { - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub pubkey: Vec, - pub withdrawal_credentials: H256, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub amount: u64, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub signature: Vec, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Deposit { - pub proof: Vec, - pub data: DepositData, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Checkpoint { - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub epoch: u64, - pub root: H256, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct AttestationData { - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub slot: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub index: u64, - pub beacon_block_root: H256, - pub source: Checkpoint, - pub target: Checkpoint, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct IndexedAttestation { - pub attesting_indices: Vec, - pub data: AttestationData, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub signature: Vec, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct SignedHeader { - pub message: BeaconHeader, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub signature: Vec, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ProposerSlashing { - pub signed_header_1: SignedHeader, - pub signed_header_2: SignedHeader, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct AttesterSlashing { - pub attestation_1: IndexedAttestation, - pub attestation_2: IndexedAttestation, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Attestation { - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub aggregation_bits: Vec, - pub data: AttestationData, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub signature: Vec, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct VoluntaryExit { - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub epoch: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub validator_index: u64, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Eth1Data { - pub deposit_root: H256, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub deposit_count: u64, - pub block_hash: H256, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct SyncAggregate { - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub sync_committee_bits: Vec, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub sync_committee_signature: Vec, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ExecutionPayload { - pub parent_hash: H256, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub fee_recipient: Vec, - pub state_root: H256, - pub receipts_root: H256, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub logs_bloom: Vec, - pub prev_randao: H256, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub block_number: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub gas_limit: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub gas_used: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub timestamp: u64, - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub extra_data: Vec, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub base_fee_per_gas: U256, - pub block_hash: H256, - pub transactions_root: H256, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Body { - #[cfg_attr(feature = "std", serde(with = "serde_hex"))] - pub randao_reveal: Vec, - pub eth1_data: Eth1Data, - pub graffiti: H256, - pub proposer_slashings: Vec, - pub attester_slashings: Vec, - pub attestations: Vec, - pub deposits: Vec, - pub voluntary_exits: Vec, - pub sync_aggregate: SyncAggregate, - pub execution_payload: ExecutionPayload, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct BeaconBlock { - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub slot: u64, - #[cfg_attr(feature = "std", serde(with = "serde_str"))] - pub proposer_index: u64, - pub parent_root: H256, - pub state_root: H256, - pub body: Body, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct InitialSync { - pub header: BeaconHeader, - pub current_sync_committee: SyncCommittee, - pub current_sync_committee_branch: ProofBranch, - pub validators_root: Root, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct SyncCommitteePeriodUpdate { - pub attested_header: BeaconHeader, - pub next_sync_committee: SyncCommittee, - pub next_sync_committee_branch: ProofBranch, - pub finalized_header: BeaconHeader, - pub finality_branch: ProofBranch, - pub sync_aggregate: SyncAggregate, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct FinalizedHeaderUpdate { - pub attested_header: BeaconHeader, - pub finalized_header: BeaconHeader, - pub finality_branch: ProofBranch, - pub sync_aggregate: SyncAggregate, -} - -#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct BlockUpdate { - pub block: BeaconBlock, - pub sync_aggregate: SyncAggregate, -} - -#[derive(Default, Encode, Decode, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct ExecutionHeaderState { - pub beacon_block_root: H256, - pub beacon_slot: u64, - pub block_hash: H256, - pub block_number: u64, -} - -impl ExecutionHeader { - // Copied from ethereum_snowbridge::header - pub fn check_receipt_proof( - &self, - proof: &[Vec], - ) -> Option> { - match self.apply_merkle_proof(proof) { - Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)), - Some((_, _)) => None, - None => None, - } - } - - // Copied from ethereum_snowbridge::header - pub fn apply_merkle_proof(&self, proof: &[Vec]) -> Option<(H256, Vec)> { - let mut iter = proof.iter().rev(); - let first_bytes = match iter.next() { - Some(b) => b, - None => return None, - }; - let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?; - - let final_hash: Option<[u8; 32]> = - iter.fold(Some(keccak_256(first_bytes)), |maybe_hash, bytes| { - let expected_hash = maybe_hash?; - let node: Box = bytes.as_slice().try_into().ok()?; - if (*node).contains_hash(expected_hash.into()) { - return Some(keccak_256(bytes)); - } - None - }); - - final_hash.map(|hash| (hash.into(), item_to_prove.value)) - } -} diff --git a/primitives/ethereum/src/difficulty.rs b/primitives/ethereum/src/difficulty.rs index 06cc5000..7075aa24 100644 --- a/primitives/ethereum/src/difficulty.rs +++ b/primitives/ethereum/src/difficulty.rs @@ -33,7 +33,7 @@ use crate::U256; use sp_runtime::RuntimeDebug; use sp_std::convert::TryFrom; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -67,7 +67,9 @@ pub enum BombDelay { /// Describes when hard forks occurred in Ethereum Mainnet based networks /// that affect difficulty calculations. These values are network-specific. -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ForkConfig { // Block number on which Byzantium (EIP-649) rules activated @@ -153,7 +155,9 @@ impl ForkConfig { /// Describes when hard forks occurred in Ethereum Classic based networks /// that affect difficulty calculations. These values are network-specific. -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ClassicForkConfig { // https://ecips.ethereumclassic.org/ECIPs/ecip-1041 diff --git a/primitives/ethereum/src/header.rs b/primitives/ethereum/src/header.rs index 03564991..4c1eedbb 100644 --- a/primitives/ethereum/src/header.rs +++ b/primitives/ethereum/src/header.rs @@ -114,14 +114,21 @@ impl Header { &self, proof: &[Vec], ) -> Option> { - match self.apply_merkle_proof(proof) { - Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)), + Self::check_receipt_proof_with_root(self.receipts_root, proof) + } + + pub fn check_receipt_proof_with_root( + receipts_root: H256, + proof: &[Vec], + ) -> Option> { + match Self::apply_merkle_proof(proof) { + Some((root, data)) if root == receipts_root => Some(rlp::decode(&data)), Some((_, _)) => None, None => None, } } - pub fn apply_merkle_proof(&self, proof: &[Vec]) -> Option<(H256, Vec)> { + pub fn apply_merkle_proof(proof: &[Vec]) -> Option<(H256, Vec)> { let mut iter = proof.iter().rev(); let first_bytes = match iter.next() { Some(b) => b, diff --git a/primitives/ethereum/src/network_config.rs b/primitives/ethereum/src/network_config.rs index 216e4468..62433921 100644 --- a/primitives/ethereum/src/network_config.rs +++ b/primitives/ethereum/src/network_config.rs @@ -29,17 +29,19 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - beacon::config::BeaconConsensusConfig, + beacon::BeaconConsensusConfig, difficulty::{ClassicForkConfig, ForkConfig}, EVMChainId, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use sp_runtime::RuntimeDebug; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Consensus { Ethash { fork_config: ForkConfig }, @@ -49,17 +51,19 @@ pub enum Consensus { } impl Consensus { - pub fn calc_epoch_length(&self, block_number: u64) -> u64 { + pub fn calc_epoch_length(&self, block_number: u64) -> Option { match self { - Consensus::Clique { epoch, .. } => *epoch, - Consensus::Ethash { fork_config } => fork_config.epoch_length(), - Consensus::Etchash { fork_config } => fork_config.calc_epoch_length(block_number), - Consensus::Beacon(consensus) => consensus.config.epoch_length(), + Consensus::Clique { epoch, .. } => Some(*epoch), + Consensus::Ethash { fork_config } => Some(fork_config.epoch_length()), + Consensus::Etchash { fork_config } => Some(fork_config.calc_epoch_length(block_number)), + Consensus::Beacon(_consensus) => None, } } } -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum NetworkConfig { Mainnet, From 7b59aaf19c3feb7592a2f14d74b1df9dcffec4f2 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 10 Mar 2023 13:06:39 +0300 Subject: [PATCH 3/5] Multi-instance beacon light client --- pallets/ethereum-beacon-client/src/lib.rs | 166 +++++++++++----------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/pallets/ethereum-beacon-client/src/lib.rs b/pallets/ethereum-beacon-client/src/lib.rs index 17b52783..da3f5651 100644 --- a/pallets/ethereum-beacon-client/src/lib.rs +++ b/pallets/ethereum-beacon-client/src/lib.rs @@ -59,11 +59,12 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; type EthSpec: EthSpec; type TimeProvider: UnixTime; type WeightInfo: WeightInfo; @@ -73,14 +74,14 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { BeaconHeaderImported { block_hash: H256, slot: Slot }, ExecutionHeaderImported { block_hash: H256, block_number: u64 }, SyncCommitteeUpdated { period: u64 }, } #[pallet::error] - pub enum Error { + pub enum Error { SyncCommitteeMissing, SyncCommitteeParticipantsNotSupermajority, InvalidHeaderMerkleProof, @@ -106,14 +107,14 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet {} + impl, I: 'static> Hooks> for Pallet {} #[pallet::storage] - pub(super) type FinalizedBeaconHeaders = + pub(super) type FinalizedBeaconHeaders, I: 'static = ()> = StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, BeaconBlockHeader, OptionQuery>; #[pallet::storage] - pub(super) type FinalizedBeaconHeaderSlots = StorageMap< + pub(super) type FinalizedBeaconHeaderSlots, I: 'static = ()> = StorageMap< _, Identity, EVMChainId, @@ -122,11 +123,11 @@ pub mod pallet { >; #[pallet::storage] - pub(super) type FinalizedBeaconHeadersBlockRoot = + pub(super) type FinalizedBeaconHeadersBlockRoot, I: 'static = ()> = StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, H256, OptionQuery>; #[pallet::storage] - pub(super) type ExecutionHeaders = StorageDoubleMap< + pub(super) type ExecutionHeaders, I: 'static = ()> = StorageDoubleMap< _, Identity, EVMChainId, @@ -139,7 +140,7 @@ pub mod pallet { /// Current sync committee corresponding to the active header. /// TODO prune older sync committees than xxx #[pallet::storage] - pub(super) type SyncCommittees = StorageDoubleMap< + pub(super) type SyncCommittees, I: 'static = ()> = StorageDoubleMap< _, Identity, EVMChainId, @@ -150,30 +151,31 @@ pub mod pallet { >; #[pallet::storage] - pub(super) type ValidatorsRoot = + pub(super) type ValidatorsRoot, I: 'static = ()> = StorageMap<_, Identity, EVMChainId, H256, OptionQuery>; #[pallet::storage] - pub(super) type LatestFinalizedHeaderState = + pub(super) type LatestFinalizedHeaderState, I: 'static = ()> = StorageMap<_, Identity, EVMChainId, FinalizedHeaderState, OptionQuery>; #[pallet::storage] - pub(super) type LatestExecutionHeaderState = + pub(super) type LatestExecutionHeaderState, I: 'static = ()> = StorageMap<_, Identity, EVMChainId, ExecutionHeaderState, OptionQuery>; #[pallet::storage] - pub(super) type LatestSyncCommitteePeriod = + pub(super) type LatestSyncCommitteePeriod, I: 'static = ()> = StorageMap<_, Identity, EVMChainId, u64, OptionQuery>; #[pallet::storage] - pub(super) type NetworkConfigs = + pub(super) type NetworkConfigs, I: 'static = ()> = StorageMap<_, Identity, EVMChainId, NetworkConfig, OptionQuery>; #[pallet::storage] - pub(super) type Blocked = StorageMap<_, Identity, EVMChainId, bool, ValueQuery>; + pub(super) type Blocked, I: 'static = ()> = + StorageMap<_, Identity, EVMChainId, bool, ValueQuery>; #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { #[pallet::weight(T::WeightInfo::initialize())] #[transactional] pub fn initialize( @@ -217,7 +219,7 @@ pub mod pallet { let signature_epoch = update.signature_slot.epoch_with_spec::(); let sync_committee_period = signature_epoch .sync_committee_period_with_spec::() - .map_err(|_| Error::::ArithError)?; + .map_err(|_| Error::::ArithError)?; log::info!( target: "ethereum-beacon-client", "💫 Received sync committee update for period {}. Applying update", @@ -326,7 +328,7 @@ pub mod pallet { pub fn unblock_bridge(origin: OriginFor, chain_id: EVMChainId) -> DispatchResult { let _sender = ensure_root(origin)?; - >::set(chain_id, false); + >::set(chain_id, false); log::info!(target: "ethereum-beacon-client","💫 syncing bridge from governance provided checkpoint."); @@ -334,7 +336,7 @@ pub mod pallet { } } - impl Pallet { + impl, I: 'static> Pallet { fn process_initial_sync( chain_id: EVMChainId, initial_sync: LightClientBootstrap, @@ -366,7 +368,7 @@ pub mod pallet { ensure!( update.signature_slot > update.attested_header.slot && update.attested_header.slot >= update.finalized_header.slot, - Error::::InvalidSyncCommitteeHeaderUpdate + Error::::InvalidSyncCommitteeHeaderUpdate ); Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; Self::verify_sync_committee( @@ -387,26 +389,26 @@ pub mod pallet { )?; let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; - let latest_committee_period = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; + let latest_committee_period = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; ensure!( - >::contains_key(chain_id, current_period), - Error::::SyncCommitteeMissing + >::contains_key(chain_id, current_period), + Error::::SyncCommitteeMissing ); let next_period = current_period + 1; ensure!( - !>::contains_key(chain_id, next_period), - Error::::InvalidSyncCommitteePeriodUpdateWithDuplication + !>::contains_key(chain_id, next_period), + Error::::InvalidSyncCommitteePeriodUpdateWithDuplication ); ensure!( (next_period == latest_committee_period + 1), - Error::::InvalidSyncCommitteePeriodUpdateWithGap + Error::::InvalidSyncCommitteePeriodUpdateWithGap ); let current_sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; - let validators_root = - >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + let validators_root = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; Self::verify_signed_header( chain_id, @@ -427,13 +429,13 @@ pub mod pallet { chain_id: EVMChainId, update: LightClientFinalityUpdate, ) -> DispatchResult { - let last_finalized_header = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; + let last_finalized_header = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; ensure!( update.signature_slot > update.attested_header.slot && update.attested_header.slot >= update.finalized_header.slot && update.finalized_header.slot > last_finalized_header.beacon_slot, - Error::::InvalidFinalizedHeaderUpdate + Error::::InvalidFinalizedHeaderUpdate ); let import_time = last_finalized_header.import_time; let weak_subjectivity_period_check = @@ -449,8 +451,8 @@ pub mod pallet { if time > weak_subjectivity_period_check { log::info!(target: "ethereum-beacon-client","💫 Weak subjectivity period exceeded, blocking bridge.",); - >::insert(chain_id, true); - return Err(Error::::BridgeBlocked.into()); + >::insert(chain_id, true); + return Err(Error::::BridgeBlocked.into()); } Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; @@ -471,12 +473,12 @@ pub mod pallet { ensure!( (current_period == last_finalized_period || current_period == last_finalized_period + 1), - Error::::InvalidFinalizedPeriodUpdate + Error::::InvalidFinalizedPeriodUpdate ); let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; - let validators_root = - >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + let validators_root = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; Self::verify_signed_header( chain_id, update.sync_aggregate, @@ -505,38 +507,38 @@ pub mod pallet { update: LightClientOptimisticUpdate, block: BlindedBeaconBlock, ) -> DispatchResult { - let last_finalized_header = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; + let last_finalized_header = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; let latest_finalized_header_slot = last_finalized_header.beacon_slot; let block_slot = update.attested_header.slot; ensure!( block_slot <= latest_finalized_header_slot, - Error::::HeaderNotFinalized + Error::::HeaderNotFinalized ); - let execution_header_state = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; + let execution_header_state = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; let execution_payload = block .body() .execution_payload() - .map_err(|_| Error::::ArithError)? + .map_err(|_| Error::::ArithError)? .to_execution_payload_header(); ensure!( execution_payload.block_number() > execution_header_state.block_number, - Error::::InvalidExecutionHeaderUpdate + Error::::InvalidExecutionHeaderUpdate ); let body_root_hash = Self::body_tree_root_hash(&block); ensure!( body_root_hash == update.attested_header.body_root, - Error::::WrongBlockBodyHashTreeRoot + Error::::WrongBlockBodyHashTreeRoot ); let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; - let validators_root = - >::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + let validators_root = >::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; Self::verify_signed_header( chain_id, update.sync_aggregate, @@ -552,19 +554,19 @@ pub mod pallet { } fn check_bridge_blocked_state(chain_id: EVMChainId) -> DispatchResult { - if >::get(chain_id) { - return Err(Error::::BridgeBlocked.into()); + if >::get(chain_id) { + return Err(Error::::BridgeBlocked.into()); } Ok(()) } fn check_network_config(chain_id: EVMChainId) -> DispatchResult { - let network_config = - NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + let network_config = NetworkConfigs::::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; match network_config.consensus() { ethereum_primitives::network_config::Consensus::Beacon(_config) => Ok(()), - _ => Err(Error::::WrongConsensus.into()), + _ => Err(Error::::WrongConsensus.into()), } } @@ -588,7 +590,7 @@ pub mod pallet { participant_pubkeys.push( pubkey .decompress() - .map_err(|_| Error::::InvalidPublicKeyBytes)?, + .map_err(|_| Error::::InvalidPublicKeyBytes)?, ); } } @@ -614,7 +616,7 @@ pub mod pallet { &participant_pubkeys.iter().collect::>(), ) { - return Err(Error::::SignatureVerificationFailed.into()); + return Err(Error::::SignatureVerificationFailed.into()); } Ok(()) } @@ -649,7 +651,7 @@ pub mod pallet { index, header_state_root ), - Error::::InvalidSyncCommitteeMerkleProof + Error::::InvalidSyncCommitteeMerkleProof ); Ok(()) @@ -670,7 +672,7 @@ pub mod pallet { index, attested_header_state_root ), - Error::::InvalidHeaderMerkleProof + Error::::InvalidHeaderMerkleProof ); Ok(()) @@ -681,7 +683,7 @@ pub mod pallet { period: u64, sync_committee: SyncCommittee, ) { - >::insert(chain_id, period, sync_committee); + >::insert(chain_id, period, sync_committee); log::trace!( target: "ethereum-beacon-client", @@ -689,7 +691,7 @@ pub mod pallet { period ); - >::insert(chain_id, period); + >::insert(chain_id, period); Self::deposit_event(Event::SyncCommitteeUpdated { period }); } @@ -701,7 +703,7 @@ pub mod pallet { ) -> DispatchResult { let slot = header.slot; - >::insert(chain_id, block_root, header); + >::insert(chain_id, block_root, header); Self::add_finalized_header_slot(chain_id, slot)?; log::info!( @@ -711,7 +713,7 @@ pub mod pallet { slot ); - LatestFinalizedHeaderState::::insert( + LatestFinalizedHeaderState::::insert( chain_id, FinalizedHeaderState { import_time: T::TimeProvider::now().as_secs(), @@ -729,14 +731,14 @@ pub mod pallet { } fn add_finalized_header_slot(chain_id: EVMChainId, slot: Slot) -> DispatchResult { - >::try_mutate(chain_id, |b_vec| { + >::try_mutate(chain_id, |b_vec| { let b_vec = b_vec.get_or_insert(Default::default()); if b_vec.len() as u32 == T::MaxFinalizedHeaderSlotArray::get() { b_vec.remove(0); } b_vec.try_push(slot) }) - .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; + .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; Ok(()) } @@ -750,7 +752,7 @@ pub mod pallet { let block_number = header.block_number(); let block_hash = header.block_hash().into_root(); - >::insert(chain_id, block_hash, header); + >::insert(chain_id, block_hash, header); log::trace!( target: "ethereum-beacon-client", @@ -759,7 +761,7 @@ pub mod pallet { block_number ); - LatestExecutionHeaderState::::insert( + LatestExecutionHeaderState::::insert( chain_id, ExecutionHeaderState { beacon_block_root, @@ -776,14 +778,14 @@ pub mod pallet { } fn store_validators_root(chain_id: EVMChainId, validators_root: H256) { - >::insert(chain_id, validators_root); + >::insert(chain_id, validators_root); } - pub(super) fn compute_current_sync_period(slot: Slot) -> Result> { + pub(super) fn compute_current_sync_period(slot: Slot) -> Result> { let period = slot .epoch_with_spec::() .sync_committee_period_with_spec::() - .map_err(|_| Error::::ArithError)?; + .map_err(|_| Error::::ArithError)?; Ok(period) } @@ -835,7 +837,7 @@ pub mod pallet { let sync_committee_len = sync_agg.sync_committee_bits.len(); ensure!( (sync_committee_sum * 3 >= sync_committee_len * 2), - Error::::SyncCommitteeParticipantsNotSupermajority + Error::::SyncCommitteeParticipantsNotSupermajority ); Ok(()) @@ -845,13 +847,13 @@ pub mod pallet { chain_id: EVMChainId, period: u64, ) -> Result, DispatchError> { - let sync_committee = >::get(chain_id, period); + let sync_committee = >::get(chain_id, period); if let Some(sync_committee) = sync_committee { Ok(sync_committee) } else { log::error!(target: "ethereum-beacon-client", "💫 Sync committee for period {} missing", period); - return Err(Error::::SyncCommitteeMissing.into()); + return Err(Error::::SyncCommitteeMissing.into()); } } @@ -859,13 +861,13 @@ pub mod pallet { chain_id: EVMChainId, epoch: Epoch, ) -> Result { - let network_config = - NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + let network_config = NetworkConfigs::::get(chain_id) + .ok_or(Error::::NetworkNotInitialized)?; match network_config.consensus() { ethereum_primitives::network_config::Consensus::Beacon(config) => { Ok(config.fork_version_from_epoch(epoch.as_u64())) } - _ => Err(Error::::WrongConsensus.into()), + _ => Err(Error::::WrongConsensus.into()), } } @@ -907,7 +909,7 @@ pub mod pallet { stored_header.receipts_root(), &proof.data, ) - .ok_or(Error::::InvalidProof)?; + .ok_or(Error::::InvalidProof)?; match result { Ok(receipt) => Ok(receipt), @@ -917,13 +919,13 @@ pub mod pallet { "💫 Failed to decode transaction receipt: {}", err ); - Err(Error::::InvalidProof.into()) + Err(Error::::InvalidProof.into()) } } } } - impl Verifier for Pallet { + impl, I: 'static> Verifier for Pallet { type Result = (Log, u64); /// Verify a message by verifying the existence of the corresponding /// Ethereum log in a block. Returns the log if successful. @@ -934,8 +936,8 @@ pub mod pallet { message.proof.block_hash, ); - let stored_header = >::get(chain_id, message.proof.block_hash) - .ok_or(Error::::MissingHeader)?; + let stored_header = >::get(chain_id, message.proof.block_hash) + .ok_or(Error::::MissingHeader)?; let block_number = stored_header.block_number(); @@ -967,7 +969,7 @@ pub mod pallet { message.proof.block_hash, err ); - return Err(Error::::DecodeFailed.into()); + return Err(Error::::DecodeFailed.into()); } }; @@ -977,7 +979,7 @@ pub mod pallet { "💫 Event log not found in receipt for transaction at index {} in block {}", message.proof.tx_index, message.proof.block_hash, ); - return Err(Error::::InvalidProof.into()); + return Err(Error::::InvalidProof.into()); } log::info!( From 5452ba09cf9a37aa67b943c3946e879897188bbc Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 10 Mar 2023 13:10:39 +0300 Subject: [PATCH 4/5] Rename folder --- .../{ethereum-beacon-client => beacon-light-client}/Cargo.toml | 0 .../{ethereum-beacon-client => beacon-light-client}/src/lib.rs | 0 .../src/weights.rs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename pallets/{ethereum-beacon-client => beacon-light-client}/Cargo.toml (100%) rename pallets/{ethereum-beacon-client => beacon-light-client}/src/lib.rs (100%) rename pallets/{ethereum-beacon-client => beacon-light-client}/src/weights.rs (100%) diff --git a/pallets/ethereum-beacon-client/Cargo.toml b/pallets/beacon-light-client/Cargo.toml similarity index 100% rename from pallets/ethereum-beacon-client/Cargo.toml rename to pallets/beacon-light-client/Cargo.toml diff --git a/pallets/ethereum-beacon-client/src/lib.rs b/pallets/beacon-light-client/src/lib.rs similarity index 100% rename from pallets/ethereum-beacon-client/src/lib.rs rename to pallets/beacon-light-client/src/lib.rs diff --git a/pallets/ethereum-beacon-client/src/weights.rs b/pallets/beacon-light-client/src/weights.rs similarity index 100% rename from pallets/ethereum-beacon-client/src/weights.rs rename to pallets/beacon-light-client/src/weights.rs From 24e9cb604b0082d168f8be405deef9fee29e6cff Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Tue, 18 Apr 2023 04:51:50 +0300 Subject: [PATCH 5/5] Refactoring --- Cargo.lock | 2 + pallets/beacon-light-client/src/lib.rs | 1262 +++++------------ .../beacon-light-client/src/light_client.rs | 477 +++++++ pallets/beacon-light-client/src/weights.rs | 32 +- primitives/beacon/Cargo.toml | 1 + primitives/beacon/src/aggregate_and_proof.rs | 93 -- primitives/beacon/src/application_domain.rs | 18 - primitives/beacon/src/attestation.rs | 29 +- primitives/beacon/src/attestation_duty.rs | 19 - primitives/beacon/src/beacon_block.rs | 367 +---- primitives/beacon/src/beacon_block_header.rs | 18 - primitives/beacon/src/beacon_committee.rs | 27 - .../beacon.rs => beacon/src/beacon_config.rs} | 146 +- primitives/beacon/src/beacon_state.rs | 2 - .../beacon/src/bls_to_execution_change.rs | 20 - primitives/beacon/src/builder_bid.rs | 144 -- primitives/beacon/src/chain_spec.rs | 1159 --------------- primitives/beacon/src/config_and_preset.rs | 106 -- .../beacon/src/contribution_and_proof.rs | 73 - primitives/beacon/src/deposit_data.rs | 23 - primitives/beacon/src/deposit_message.rs | 33 - .../beacon/src/deposit_tree_snapshot.rs | 77 - primitives/beacon/src/enr_fork_id.rs | 33 - primitives/beacon/src/eth_spec.rs | 46 +- .../beacon/src/execution_block_header.rs | 76 - primitives/beacon/src/fork_name.rs | 33 - primitives/beacon/src/historical_batch.rs | 30 - primitives/beacon/src/historical_summary.rs | 31 - primitives/beacon/src/lib.rs | 91 +- .../beacon/src/light_client_bootstrap.rs | 86 +- .../src/light_client_finality_update.rs | 35 - primitives/beacon/src/light_client_header.rs | 93 ++ .../src/light_client_optimistic_update.rs | 30 - primitives/beacon/src/light_client_update.rs | 125 +- primitives/beacon/src/participation_flags.rs | 103 -- primitives/beacon/src/participation_list.rs | 17 - primitives/beacon/src/pending_attestation.rs | 33 - primitives/beacon/src/preset.rs | 208 --- .../beacon/src/proposer_preparation_data.rs | 15 - primitives/beacon/src/relative_epoch.rs | 72 - primitives/beacon/src/selection_proof.rs | 93 -- primitives/beacon/src/shuffling_id.rs | 45 - .../beacon/src/signed_aggregate_and_proof.rs | 75 - primitives/beacon/src/signed_beacon_block.rs | 51 - .../beacon/src/signed_beacon_block_header.rs | 26 +- .../src/signed_contribution_and_proof.rs | 73 - primitives/beacon/src/slot_epoch.rs | 14 +- primitives/beacon/src/subnet_id.rs | 125 -- primitives/beacon/src/sync_aggregate.rs | 33 +- .../src/sync_aggregator_selection_data.rs | 29 - primitives/beacon/src/sync_committee.rs | 24 +- .../beacon/src/sync_committee_contribution.rs | 119 -- .../beacon/src/sync_committee_message.rs | 61 - .../beacon/src/sync_committee_subscription.rs | 29 - primitives/beacon/src/sync_duty.rs | 83 -- primitives/beacon/src/sync_selection_proof.rs | 106 -- primitives/beacon/src/sync_subnet_id.rs | 97 -- primitives/beacon/src/validator.rs | 123 -- .../beacon/src/validator_registration_data.rs | 37 - .../beacon/src/validator_subscription.rs | 34 - primitives/beacon/src/voluntary_exit.rs | 24 +- primitives/ethereum/Cargo.toml | 3 + primitives/ethereum/src/lib.rs | 1 - primitives/ethereum/src/network_config.rs | 12 +- primitives/ethereum/src/serde_utils.rs | 29 - 65 files changed, 1302 insertions(+), 5329 deletions(-) create mode 100644 pallets/beacon-light-client/src/light_client.rs delete mode 100644 primitives/beacon/src/aggregate_and_proof.rs delete mode 100644 primitives/beacon/src/application_domain.rs delete mode 100644 primitives/beacon/src/attestation_duty.rs delete mode 100644 primitives/beacon/src/beacon_committee.rs rename primitives/{ethereum/src/beacon.rs => beacon/src/beacon_config.rs} (55%) delete mode 100644 primitives/beacon/src/builder_bid.rs delete mode 100644 primitives/beacon/src/chain_spec.rs delete mode 100644 primitives/beacon/src/config_and_preset.rs delete mode 100644 primitives/beacon/src/contribution_and_proof.rs delete mode 100644 primitives/beacon/src/deposit_message.rs delete mode 100644 primitives/beacon/src/deposit_tree_snapshot.rs delete mode 100644 primitives/beacon/src/enr_fork_id.rs delete mode 100644 primitives/beacon/src/execution_block_header.rs delete mode 100644 primitives/beacon/src/historical_batch.rs delete mode 100644 primitives/beacon/src/historical_summary.rs delete mode 100644 primitives/beacon/src/light_client_finality_update.rs create mode 100644 primitives/beacon/src/light_client_header.rs delete mode 100644 primitives/beacon/src/light_client_optimistic_update.rs delete mode 100644 primitives/beacon/src/participation_flags.rs delete mode 100644 primitives/beacon/src/participation_list.rs delete mode 100644 primitives/beacon/src/pending_attestation.rs delete mode 100644 primitives/beacon/src/preset.rs delete mode 100644 primitives/beacon/src/proposer_preparation_data.rs delete mode 100644 primitives/beacon/src/relative_epoch.rs delete mode 100644 primitives/beacon/src/selection_proof.rs delete mode 100644 primitives/beacon/src/shuffling_id.rs delete mode 100644 primitives/beacon/src/signed_aggregate_and_proof.rs delete mode 100644 primitives/beacon/src/signed_contribution_and_proof.rs delete mode 100644 primitives/beacon/src/subnet_id.rs delete mode 100644 primitives/beacon/src/sync_aggregator_selection_data.rs delete mode 100644 primitives/beacon/src/sync_committee_contribution.rs delete mode 100644 primitives/beacon/src/sync_committee_message.rs delete mode 100644 primitives/beacon/src/sync_committee_subscription.rs delete mode 100644 primitives/beacon/src/sync_duty.rs delete mode 100644 primitives/beacon/src/sync_selection_proof.rs delete mode 100644 primitives/beacon/src/sync_subnet_id.rs delete mode 100644 primitives/beacon/src/validator.rs delete mode 100644 primitives/beacon/src/validator_registration_data.rs delete mode 100644 primitives/beacon/src/validator_subscription.rs diff --git a/Cargo.lock b/Cargo.lock index b9455438..4ef54e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,7 @@ dependencies = [ "eth2_ssz_types", "ethereum-types", "hex", + "hex-literal", "itertools", "lazy_static", "maplit", @@ -1046,6 +1047,7 @@ dependencies = [ name = "ethereum-primitives" version = "0.1.0" dependencies = [ + "beacon", "ethabi", "ethash", "ethbloom", diff --git a/pallets/beacon-light-client/src/lib.rs b/pallets/beacon-light-client/src/lib.rs index da3f5651..8cd69158 100644 --- a/pallets/beacon-light-client/src/lib.rs +++ b/pallets/beacon-light-client/src/lib.rs @@ -1,994 +1,518 @@ //! # Ethereum Beacon Client #![cfg_attr(not(feature = "std"), no_std)] +pub mod light_client; pub mod weights; -pub const DOMAIN_SYNC_COMMITTEE: u32 = 7; +use core::marker::PhantomData; +use beacon::{ + light_client_bootstrap::LightClientBootstrap, light_client_update::LightClientUpdate, EthSpec, + EthSpecId, GnosisEthSpec, LightClientHeader, MainnetEthSpec, MinimalEthSpec, SyncCommittee, +}; +use bridge_types::EVMChainId; +use codec::{Decode, Encode, MaxEncodedLen}; +use light_client::LightClientStore; +use scale_info::TypeInfo; pub use weights::WeightInfo; -use frame_support::{dispatch::DispatchResult, log, traits::UnixTime, transactional}; +use frame_support::{dispatch::DispatchResult, traits::UnixTime, RuntimeDebug}; use frame_system::ensure_signed; use sp_core::H256; -use sp_io::hashing::sha2_256; use sp_std::prelude::*; -pub use pallet::*; +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericBootstrap { + Minimal(LightClientBootstrap), + Mainnet(LightClientBootstrap), + Gnosis(LightClientBootstrap), +} + +impl GenericBootstrap { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericUpdate { + Minimal(LightClientUpdate), + Mainnet(LightClientUpdate), + Gnosis(LightClientUpdate), +} + +impl GenericUpdate { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericHeader { + Minimal(LightClientHeader), + Mainnet(LightClientHeader), + Gnosis(LightClientHeader), +} + +impl GenericHeader { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Minimal(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Mainnet(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Gnosis(x) => Ok(x), + _ => Err(()), + } + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Minimal(value) + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Mainnet(value) + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Gnosis(value) + } +} -#[derive(codec::Decode, codec::Encode, codec::MaxEncodedLen, scale_info::TypeInfo)] -pub struct ExecutionHeaderState { - beacon_block_root: H256, - beacon_slot: beacon::Slot, - block_hash: H256, - block_number: u64, +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericSyncCommittee { + Minimal(SyncCommittee), + Mainnet(SyncCommittee), + Gnosis(SyncCommittee), } -#[derive(codec::Decode, codec::Encode, codec::MaxEncodedLen, scale_info::TypeInfo)] -pub struct FinalizedHeaderState { - import_time: u64, - beacon_block_root: H256, - beacon_slot: beacon::Slot, +impl GenericSyncCommittee { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Minimal(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Mainnet(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Gnosis(x) => Ok(x), + _ => Err(()), + } + } +} + +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Minimal(value) + } +} + +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Mainnet(value) + } } +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Gnosis(value) + } +} + +pub use pallet::*; + #[frame_support::pallet] pub mod pallet { use super::*; - use beacon::{ - light_client_bootstrap::LightClientBootstrap, - light_client_update::{ - LightClientUpdate, CURRENT_SYNC_COMMITTEE_INDEX, CURRENT_SYNC_COMMITTEE_PROOF_LEN, - FINALIZED_ROOT_INDEX, FINALIZED_ROOT_PROOF_LEN, NEXT_SYNC_COMMITTEE_INDEX, - NEXT_SYNC_COMMITTEE_PROOF_LEN, - }, - BeaconBlockHeader, BlindedBeaconBlock, ChainSpec, Epoch, EthSpec, ExecPayload, - ExecutionPayloadHeader, Hash256, LightClientFinalityUpdate, LightClientOptimisticUpdate, - Slot, SyncAggregate, SyncCommittee, + use beacon::{ConsensusConfig, Slot}; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + BoundedVec, }; - use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_runtime::DispatchError; - use tree_hash::TreeHash; - use bridge_types::{beacon::ForkVersion, network_config::NetworkConfig, traits::Verifier}; - use bridge_types::{ - types::{Message, Proof}, - EVMChainId, - }; - use ethereum_primitives::{Log, Receipt}; + use crate::light_client::BeaconLightClient; + use bridge_types::network_config::NetworkConfig; + use bridge_types::{network_config::Consensus, types::Message}; + use bridge_types::{traits::Verifier, EVMChainId}; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> - + IsType<::RuntimeEvent>; - type EthSpec: EthSpec; + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; type TimeProvider: UnixTime; type WeightInfo: WeightInfo; type WeakSubjectivityPeriodSeconds: Get; - type MaxFinalizedHeaderSlotArray: Get; + type MaxFinalizedStateRootArray: Get; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event, I: 'static = ()> { + pub enum Event { BeaconHeaderImported { block_hash: H256, slot: Slot }, ExecutionHeaderImported { block_hash: H256, block_number: u64 }, SyncCommitteeUpdated { period: u64 }, } #[pallet::error] - pub enum Error { - SyncCommitteeMissing, - SyncCommitteeParticipantsNotSupermajority, - InvalidHeaderMerkleProof, - InvalidSyncCommitteeMerkleProof, + pub enum Error { + InvalidMerkleBranch, SignatureVerificationFailed, - HeaderNotFinalized, - MissingHeader, - InvalidProof, - DecodeFailed, - BridgeBlocked, - InvalidSyncCommitteeHeaderUpdate, - InvalidSyncCommitteePeriodUpdateWithGap, - InvalidSyncCommitteePeriodUpdateWithDuplication, - InvalidFinalizedHeaderUpdate, - InvalidFinalizedPeriodUpdate, - InvalidExecutionHeaderUpdate, - FinalizedBeaconHeaderSlotsExceeded, - WrongBlockBodyHashTreeRoot, + DuplicateSyncCommitteeUpdate, + InvalidUpdate, ArithError, NetworkNotInitialized, InvalidPublicKeyBytes, - WrongConsensus, + InvalidConsensus, + InvalidSpecId, + NetworkAlreadyRegistered, + ZeroParticipants, + NotEnoughParticipants, } #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet {} + impl Hooks> for Pallet {} #[pallet::storage] - pub(super) type FinalizedBeaconHeaders, I: 'static = ()> = - StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, BeaconBlockHeader, OptionQuery>; + pub(super) type FinalizedLightClientHeader = + StorageMap<_, Identity, EVMChainId, GenericHeader, OptionQuery>; #[pallet::storage] - pub(super) type FinalizedBeaconHeaderSlots, I: 'static = ()> = StorageMap< - _, - Identity, - EVMChainId, - BoundedVec, - OptionQuery, - >; + pub(super) type OptimisticLightClientHeader = + StorageMap<_, Identity, EVMChainId, GenericHeader, OptionQuery>; #[pallet::storage] - pub(super) type FinalizedBeaconHeadersBlockRoot, I: 'static = ()> = - StorageDoubleMap<_, Identity, EVMChainId, Identity, H256, H256, OptionQuery>; + pub(super) type CurrentSyncCommittee = + StorageMap<_, Identity, EVMChainId, GenericSyncCommittee, OptionQuery>; #[pallet::storage] - pub(super) type ExecutionHeaders, I: 'static = ()> = StorageDoubleMap< - _, - Identity, - EVMChainId, - Identity, - H256, - ExecutionPayloadHeader, - OptionQuery, - >; + pub(super) type NextSyncCommittee = + StorageMap<_, Identity, EVMChainId, GenericSyncCommittee, OptionQuery>; - /// Current sync committee corresponding to the active header. - /// TODO prune older sync committees than xxx #[pallet::storage] - pub(super) type SyncCommittees, I: 'static = ()> = StorageDoubleMap< + pub(super) type LatestFinalizedExecutionStateRoots = StorageMap< _, Identity, EVMChainId, - Identity, - u64, - SyncCommittee, - OptionQuery, + BoundedVec, + ValueQuery, >; #[pallet::storage] - pub(super) type ValidatorsRoot, I: 'static = ()> = - StorageMap<_, Identity, EVMChainId, H256, OptionQuery>; - - #[pallet::storage] - pub(super) type LatestFinalizedHeaderState, I: 'static = ()> = - StorageMap<_, Identity, EVMChainId, FinalizedHeaderState, OptionQuery>; - - #[pallet::storage] - pub(super) type LatestExecutionHeaderState, I: 'static = ()> = - StorageMap<_, Identity, EVMChainId, ExecutionHeaderState, OptionQuery>; - - #[pallet::storage] - pub(super) type LatestSyncCommitteePeriod, I: 'static = ()> = - StorageMap<_, Identity, EVMChainId, u64, OptionQuery>; - - #[pallet::storage] - pub(super) type NetworkConfigs, I: 'static = ()> = + pub(super) type NetworkConfigs = StorageMap<_, Identity, EVMChainId, NetworkConfig, OptionQuery>; #[pallet::storage] - pub(super) type Blocked, I: 'static = ()> = - StorageMap<_, Identity, EVMChainId, bool, ValueQuery>; + pub(super) type LatestSyncCommitteeUpdate = + StorageMap<_, Identity, EVMChainId, u64, OptionQuery>; #[pallet::call] - impl, I: 'static> Pallet { + impl Pallet { #[pallet::weight(T::WeightInfo::initialize())] - #[transactional] pub fn initialize( origin: OriginFor, chain_id: EVMChainId, - bootstrap: LightClientBootstrap, - validators_root: H256, + network_config: NetworkConfig, + bootstrap: GenericBootstrap, ) -> DispatchResult { ensure_root(origin)?; - if let Err(err) = Self::initial_sync(chain_id, bootstrap, validators_root) { - log::error!( - target: "ethereum-beacon-client", - "💫 Sync committee period update failed with error {:?}", - err - ); - return Err(err); - } - Self::check_network_config(chain_id)?; - - log::info!( - target: "ethereum-beacon-client", - "💫 Network initialized", - ); - - Ok(()) - } - - #[pallet::weight(T::WeightInfo::sync_committee_period_update())] - #[transactional] - pub fn sync_committee_period_update( - origin: OriginFor, - chain_id: EVMChainId, - update: LightClientUpdate, - ) -> DispatchResult { - let _sender = ensure_signed(origin)?; - - Self::check_bridge_blocked_state(chain_id)?; - Self::check_network_config(chain_id)?; - - let signature_epoch = update.signature_slot.epoch_with_spec::(); - let sync_committee_period = signature_epoch - .sync_committee_period_with_spec::() - .map_err(|_| Error::::ArithError)?; - log::info!( - target: "ethereum-beacon-client", - "💫 Received sync committee update for period {}. Applying update", - sync_committee_period - ); - - if let Err(err) = Self::process_sync_committee_period_update(chain_id, update) { - log::error!( - target: "ethereum-beacon-client", - "💫 Sync committee period update failed with error {:?}", - err - ); - return Err(err); - } - - log::info!( - target: "ethereum-beacon-client", - "💫 Sync committee period update for period {} succeeded.", - sync_committee_period - ); - - Ok(()) - } - - #[pallet::weight(T::WeightInfo::import_finalized_header())] - #[transactional] - pub fn import_finalized_header( - origin: OriginFor, - chain_id: EVMChainId, - finalized_header_update: LightClientFinalityUpdate, - ) -> DispatchResult { - let _sender = ensure_signed(origin)?; - - Self::check_bridge_blocked_state(chain_id)?; - Self::check_network_config(chain_id)?; - - let slot = finalized_header_update.finalized_header.slot; - - log::info!( - target: "ethereum-beacon-client", - "💫 Received finalized header for slot {}.", - slot - ); - - if let Err(err) = Self::process_finalized_header(chain_id, finalized_header_update) { - log::error!( - target: "ethereum-beacon-client", - "💫 Finalized header update failed with error {:?}", - err - ); - return Err(err); - } - - log::info!( - target: "ethereum-beacon-client", - "💫 Stored finalized beacon header at slot {}.", - slot - ); - - Ok(()) - } - - #[pallet::weight(T::WeightInfo::import_execution_header())] - #[transactional] - pub fn import_execution_header( - origin: OriginFor, - chain_id: EVMChainId, - update: LightClientOptimisticUpdate, - block: BlindedBeaconBlock, - ) -> DispatchResult { - let _sender = ensure_signed(origin)?; - - Self::check_bridge_blocked_state(chain_id)?; - Self::check_network_config(chain_id)?; - - let slot = update.attested_header.slot; - let block_hash = update.attested_header.body_root; - - log::info!( - target: "ethereum-beacon-client", - "💫 Received header update for slot {}.", - slot - ); - - if let Err(err) = Self::process_header(chain_id, update, block) { - log::error!( - target: "ethereum-beacon-client", - "💫 Header update failed with error {:?}", - err - ); - return Err(err); - } - - log::info!( - target: "ethereum-beacon-client", - "💫 Stored execution header {} at beacon slot {}.", - block_hash, - slot - ); - - Ok(()) - } - - #[pallet::weight(T::WeightInfo::unblock_bridge())] - #[transactional] - pub fn unblock_bridge(origin: OriginFor, chain_id: EVMChainId) -> DispatchResult { - let _sender = ensure_root(origin)?; - - >::set(chain_id, false); - - log::info!(target: "ethereum-beacon-client","💫 syncing bridge from governance provided checkpoint."); - - Ok(()) - } - } - - impl, I: 'static> Pallet { - fn process_initial_sync( - chain_id: EVMChainId, - initial_sync: LightClientBootstrap, - validators_root: H256, - ) -> DispatchResult { - // initial_sync - Self::verify_sync_committee( - &initial_sync.current_sync_committee, - initial_sync.current_sync_committee_branch.into(), - initial_sync.header.state_root, - CURRENT_SYNC_COMMITTEE_PROOF_LEN, - CURRENT_SYNC_COMMITTEE_INDEX, - )?; - - let period = Self::compute_current_sync_period(initial_sync.header.slot)?; - - let block_root = initial_sync.header.tree_hash_root(); - - Self::store_sync_committee(chain_id, period, initial_sync.current_sync_committee); - Self::store_validators_root(chain_id, validators_root); - Self::store_finalized_header(chain_id, block_root, initial_sync.header)?; - Ok(()) - } - - fn process_sync_committee_period_update( - chain_id: EVMChainId, - update: LightClientUpdate, - ) -> DispatchResult { - ensure!( - update.signature_slot > update.attested_header.slot - && update.attested_header.slot >= update.finalized_header.slot, - Error::::InvalidSyncCommitteeHeaderUpdate - ); - Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; - Self::verify_sync_committee( - &update.next_sync_committee, - update.next_sync_committee_branch.into(), - update.attested_header.state_root, - NEXT_SYNC_COMMITTEE_PROOF_LEN, - NEXT_SYNC_COMMITTEE_INDEX, - )?; - - let block_root = update.finalized_header.tree_hash_root(); - Self::verify_header( - block_root, - update.finality_branch.into(), - update.attested_header.state_root, - FINALIZED_ROOT_PROOF_LEN, - FINALIZED_ROOT_INDEX, - )?; - - let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; - let latest_committee_period = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; ensure!( - >::contains_key(chain_id, current_period), - Error::::SyncCommitteeMissing + !CurrentSyncCommittee::::contains_key(chain_id), + Error::::NetworkAlreadyRegistered ); - let next_period = current_period + 1; - ensure!( - !>::contains_key(chain_id, next_period), - Error::::InvalidSyncCommitteePeriodUpdateWithDuplication - ); - ensure!( - (next_period == latest_committee_period + 1), - Error::::InvalidSyncCommitteePeriodUpdateWithGap - ); - - let current_sync_committee = - Self::get_sync_committee_for_period(chain_id, current_period)?; - let validators_root = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - - Self::verify_signed_header( - chain_id, - update.sync_aggregate, - current_sync_committee, - update.attested_header, - validators_root, - update.signature_slot, - )?; - Self::store_sync_committee(chain_id, next_period, update.next_sync_committee); - Self::store_finalized_header(chain_id, block_root, update.finalized_header)?; - - Ok(()) - } + let consensus = match network_config.consensus() { + Consensus::Beacon(consensus) => consensus, + _ => return Err(Error::::InvalidConsensus.into()), + }; - fn process_finalized_header( - chain_id: EVMChainId, - update: LightClientFinalityUpdate, - ) -> DispatchResult { - let last_finalized_header = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; ensure!( - update.signature_slot > update.attested_header.slot - && update.attested_header.slot >= update.finalized_header.slot - && update.finalized_header.slot > last_finalized_header.beacon_slot, - Error::::InvalidFinalizedHeaderUpdate - ); - let import_time = last_finalized_header.import_time; - let weak_subjectivity_period_check = - import_time + T::WeakSubjectivityPeriodSeconds::get(); - let time: u64 = T::TimeProvider::now().as_secs(); - - log::info!( - target: "ethereum-beacon-client", - "💫 Checking weak subjectivity period. Current time is :{:?} Weak subjectivity period check: {:?}.", - time, - weak_subjectivity_period_check + consensus.spec_id == bootstrap.spec_id(), + Error::::InvalidSpecId ); - if time > weak_subjectivity_period_check { - log::info!(target: "ethereum-beacon-client","💫 Weak subjectivity period exceeded, blocking bridge.",); - >::insert(chain_id, true); - return Err(Error::::BridgeBlocked.into()); + match bootstrap { + GenericBootstrap::Minimal(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, + GenericBootstrap::Mainnet(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, + GenericBootstrap::Gnosis(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, } - Self::sync_committee_participation_is_supermajority(&update.sync_aggregate)?; - - let block_root = update.finalized_header.tree_hash_root(); - - Self::verify_header( - block_root, - update.finality_branch.into(), - update.attested_header.state_root, - FINALIZED_ROOT_PROOF_LEN, - FINALIZED_ROOT_INDEX, - )?; - - let last_finalized_period = - Self::compute_current_sync_period(last_finalized_header.beacon_slot)?; - let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; - ensure!( - (current_period == last_finalized_period - || current_period == last_finalized_period + 1), - Error::::InvalidFinalizedPeriodUpdate - ); - let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; - - let validators_root = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - Self::verify_signed_header( - chain_id, - update.sync_aggregate, - sync_committee, - update.attested_header, - validators_root, - update.signature_slot, - )?; - - Self::store_finalized_header(chain_id, block_root, update.finalized_header)?; - Ok(()) } - fn body_tree_root_hash(block: &BlindedBeaconBlock) -> H256 { - match block { - BlindedBeaconBlock::Base(block) => block.body.tree_hash_root(), - BlindedBeaconBlock::Altair(block) => block.body.tree_hash_root(), - BlindedBeaconBlock::Merge(block) => block.body.tree_hash_root(), - BlindedBeaconBlock::Capella(block) => block.body.tree_hash_root(), - } - } - - fn process_header( + #[pallet::weight(T::WeightInfo::import_update())] + pub fn import_update( + origin: OriginFor, chain_id: EVMChainId, - update: LightClientOptimisticUpdate, - block: BlindedBeaconBlock, + update: GenericUpdate, ) -> DispatchResult { - let last_finalized_header = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - let latest_finalized_header_slot = last_finalized_header.beacon_slot; - let block_slot = update.attested_header.slot; - ensure!( - block_slot <= latest_finalized_header_slot, - Error::::HeaderNotFinalized - ); + // TODO: Weak subjectivity check + // TODO: State root update + let _sender = ensure_signed(origin)?; - let execution_header_state = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - let execution_payload = block - .body() - .execution_payload() - .map_err(|_| Error::::ArithError)? - .to_execution_payload_header(); - ensure!( - execution_payload.block_number() > execution_header_state.block_number, - Error::::InvalidExecutionHeaderUpdate - ); - let body_root_hash = Self::body_tree_root_hash(&block); + let consensus = Pallet::::consensus_config(chain_id)?; ensure!( - body_root_hash == update.attested_header.body_root, - Error::::WrongBlockBodyHashTreeRoot + consensus.spec_id == update.spec_id(), + Error::::InvalidSpecId ); - let current_period = Self::compute_current_sync_period(update.attested_header.slot)?; - let sync_committee = Self::get_sync_committee_for_period(chain_id, current_period)?; - - let validators_root = >::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - Self::verify_signed_header( - chain_id, - update.sync_aggregate, - sync_committee, - update.attested_header, - validators_root, - update.signature_slot, - )?; - - Self::store_execution_header(chain_id, execution_payload, block_slot, body_root_hash); - - Ok(()) - } - - fn check_bridge_blocked_state(chain_id: EVMChainId) -> DispatchResult { - if >::get(chain_id) { - return Err(Error::::BridgeBlocked.into()); + match update { + GenericUpdate::Minimal(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, + GenericUpdate::Mainnet(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, + GenericUpdate::Gnosis(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, } Ok(()) } + } - fn check_network_config(chain_id: EVMChainId) -> DispatchResult { - let network_config = NetworkConfigs::::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; + impl Pallet { + pub fn consensus_config(chain_id: EVMChainId) -> Result { + let network_config = + NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; match network_config.consensus() { - ethereum_primitives::network_config::Consensus::Beacon(_config) => Ok(()), - _ => Err(Error::::WrongConsensus.into()), - } - } - - pub(super) fn verify_signed_header( - chain_id: EVMChainId, - sync_aggregate: SyncAggregate, - sync_committee: SyncCommittee, - header: BeaconBlockHeader, - validators_root: H256, - signature_slot: Slot, - ) -> DispatchResult { - let mut participant_pubkeys: Vec = Vec::new(); - // Gathers all the pubkeys of the sync committee members that participated in signing - // the header. - for (bit, pubkey) in sync_aggregate - .sync_committee_bits - .iter() - .zip(sync_committee.pubkeys.iter()) - { - if bit { - participant_pubkeys.push( - pubkey - .decompress() - .map_err(|_| Error::::InvalidPublicKeyBytes)?, - ); - } - } - - let fork = Self::compute_fork_version( - chain_id, - signature_slot.epoch_with_spec::(), - )?; - // Domains are used for for seeds, for signatures, and for selecting aggregators. - let domain = ChainSpec::compute_domain_with_constant( - DOMAIN_SYNC_COMMITTEE, - fork, - validators_root, - ); - // Hash tree root of SigningData - object root + domain - let signing_root = Self::compute_signing_root(header, domain)?; - - // Verify sync committee aggregate signature. - if !sync_aggregate - .sync_committee_signature - .fast_aggregate_verify( - signing_root, - &participant_pubkeys.iter().collect::>(), - ) - { - return Err(Error::::SignatureVerificationFailed.into()); - } - Ok(()) - } - - pub(super) fn compute_signing_root( - beacon_header: BeaconBlockHeader, - domain: Hash256, - ) -> Result { - let beacon_header_root = beacon_header.tree_hash_root(); - let hash_root = beacon::SigningData { - domain, - object_root: beacon_header_root, + Consensus::Beacon(consensus) => Ok(consensus), + _ => Err(Error::::InvalidConsensus.into()), } - .tree_hash_root(); - Ok(hash_root) - } - - fn verify_sync_committee( - sync_committee: &SyncCommittee, - sync_committee_branch: Vec, - header_state_root: H256, - depth: usize, - index: usize, - ) -> DispatchResult { - let sync_committee_root = sync_committee.tree_hash_root(); - - ensure!( - Self::is_valid_merkle_branch( - sync_committee_root, - sync_committee_branch, - depth, - index, - header_state_root - ), - Error::::InvalidSyncCommitteeMerkleProof - ); - - Ok(()) - } - - fn verify_header( - block_root: H256, - proof_branch: Vec, - attested_header_state_root: H256, - depth: usize, - index: usize, - ) -> DispatchResult { - ensure!( - Self::is_valid_merkle_branch( - block_root, - proof_branch, - depth, - index, - attested_header_state_root - ), - Error::::InvalidHeaderMerkleProof - ); - - Ok(()) - } - - fn store_sync_committee( - chain_id: EVMChainId, - period: u64, - sync_committee: SyncCommittee, - ) { - >::insert(chain_id, period, sync_committee); - - log::trace!( - target: "ethereum-beacon-client", - "💫 Updated latest sync committee period stored to {}.", - period - ); - - >::insert(chain_id, period); - - Self::deposit_event(Event::SyncCommitteeUpdated { period }); - } - - fn store_finalized_header( - chain_id: EVMChainId, - block_root: Hash256, - header: BeaconBlockHeader, - ) -> DispatchResult { - let slot = header.slot; - - >::insert(chain_id, block_root, header); - Self::add_finalized_header_slot(chain_id, slot)?; - - log::info!( - target: "ethereum-beacon-client", - "💫 Updated latest finalized block root {} at slot {}.", - block_root, - slot - ); - - LatestFinalizedHeaderState::::insert( - chain_id, - FinalizedHeaderState { - import_time: T::TimeProvider::now().as_secs(), - beacon_block_root: block_root, - beacon_slot: slot, - }, - ); - - Self::deposit_event(Event::BeaconHeaderImported { - block_hash: block_root, - slot, - }); - - Ok(()) - } - - fn add_finalized_header_slot(chain_id: EVMChainId, slot: Slot) -> DispatchResult { - >::try_mutate(chain_id, |b_vec| { - let b_vec = b_vec.get_or_insert(Default::default()); - if b_vec.len() as u32 == T::MaxFinalizedHeaderSlotArray::get() { - b_vec.remove(0); - } - b_vec.try_push(slot) - }) - .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; - - Ok(()) - } - - fn store_execution_header( - chain_id: EVMChainId, - header: ExecutionPayloadHeader, - beacon_slot: Slot, - beacon_block_root: H256, - ) { - let block_number = header.block_number(); - let block_hash = header.block_hash().into_root(); - - >::insert(chain_id, block_hash, header); - - log::trace!( - target: "ethereum-beacon-client", - "💫 Updated latest execution block at {} to number {}.", - block_hash, - block_number - ); - - LatestExecutionHeaderState::::insert( - chain_id, - ExecutionHeaderState { - beacon_block_root, - beacon_slot, - block_hash, - block_number, - }, - ); - - Self::deposit_event(Event::ExecutionHeaderImported { - block_hash, - block_number, - }); - } - - fn store_validators_root(chain_id: EVMChainId, validators_root: H256) { - >::insert(chain_id, validators_root); } + } - pub(super) fn compute_current_sync_period(slot: Slot) -> Result> { - let period = slot - .epoch_with_spec::() - .sync_committee_period_with_spec::() - .map_err(|_| Error::::ArithError)?; - Ok(period) + impl Verifier for Pallet { + type Result = (H256, u64); + /// Verify a message by verifying the existence of the corresponding + /// Ethereum log in a block. Returns the log if successful. + fn verify(_chain_id: EVMChainId, _message: &Message) -> Result<(H256, u64), DispatchError> { + // TODO: Implement storage verification + todo!(); } + } +} - pub(super) fn is_valid_merkle_branch( - leaf: H256, - branch: Vec, - depth: usize, - index: usize, - root: H256, - ) -> bool { - if branch.len() != depth { - log::error!(target: "ethereum-beacon-client", "Merkle proof branch length doesn't match depth."); - - return false; - } - let mut value = leaf; - if leaf.as_bytes().len() < 32 as usize { - log::error!(target: "ethereum-beacon-client", "Merkle proof leaf not 32 bytes."); - - return false; - } - for i in 0..depth { - if branch[i as usize].as_bytes().len() < 32 as usize { - log::error!(target: "ethereum-beacon-client", "Merkle proof branch not 32 bytes."); - - return false; - } - if (index / 2usize.pow(i as u32) % 2) == 0 { - // left node - let mut data = [0u8; 64]; - data[0..32].copy_from_slice(&(value.0)); - data[32..64].copy_from_slice(&(branch[i as usize].0)); - value = sha2_256(&data).into(); - } else { - let mut data = [0u8; 64]; // right node - data[0..32].copy_from_slice(&(branch[i as usize].0)); - data[32..64].copy_from_slice(&(value.0)); - value = sha2_256(&data).into(); - } - } - - return value == root; - } - - pub(super) fn sync_committee_participation_is_supermajority( - sync_agg: &SyncAggregate, - ) -> DispatchResult { - let sync_committee_sum = sync_agg.sync_committee_bits.num_set_bits(); - let sync_committee_len = sync_agg.sync_committee_bits.len(); - ensure!( - (sync_committee_sum * 3 >= sync_committee_len * 2), - Error::::SyncCommitteeParticipantsNotSupermajority - ); - - Ok(()) +impl From for Error { + fn from(value: light_client::Error) -> Self { + use light_client::Error as E; + match value { + E::InvalidMerkleBranch => Error::InvalidMerkleBranch, + E::ArithError => Error::ArithError, + E::ZeroParticipants => Error::ZeroParticipants, + E::NotEnoughParticipants => Error::NotEnoughParticipants, + E::InvalidUpdate => Error::InvalidUpdate, + E::InvalidPublicKeyBytes => Error::InvalidPublicKeyBytes, + E::SignatureVerificationFailed => Error::SignatureVerificationFailed, + E::DuplicateSyncCommitteeUpdate => Error::DuplicateSyncCommitteeUpdate, + E::InvalidSpecId => Error::InvalidSpecId, + E::StoreNotInitialized => Error::NetworkNotInitialized, } + } +} - pub(super) fn get_sync_committee_for_period( - chain_id: EVMChainId, - period: u64, - ) -> Result, DispatchError> { - let sync_committee = >::get(chain_id, period); - - if let Some(sync_committee) = sync_committee { - Ok(sync_committee) - } else { - log::error!(target: "ethereum-beacon-client", "💫 Sync committee for period {} missing", period); - return Err(Error::::SyncCommitteeMissing.into()); - } - } +#[derive(Default)] +pub struct PalletLightClientStore { + chain_id: EVMChainId, + _phantom: PhantomData<(T, E)>, +} - pub(super) fn compute_fork_version( - chain_id: EVMChainId, - epoch: Epoch, - ) -> Result { - let network_config = NetworkConfigs::::get(chain_id) - .ok_or(Error::::NetworkNotInitialized)?; - match network_config.consensus() { - ethereum_primitives::network_config::Consensus::Beacon(config) => { - Ok(config.fork_version_from_epoch(epoch.as_u64())) - } - _ => Err(Error::::WrongConsensus.into()), - } +impl PalletLightClientStore { + pub fn new(chain_id: EVMChainId) -> Self { + Self { + chain_id, + _phantom: Default::default(), } + } +} - pub(super) fn initial_sync( - chain_id: EVMChainId, - initial_sync: LightClientBootstrap, - validators_root: H256, - ) -> DispatchResult { - log::info!( - target: "ethereum-beacon-client", - "💫 Received initial sync, starting processing.", - ); - - if let Err(err) = Self::process_initial_sync(chain_id, initial_sync, validators_root) { - log::error!( - target: "ethereum-beacon-client", - "Initial sync failed with error {:?}", - err - ); - return Err(err); - } +impl LightClientStore for PalletLightClientStore +where + T: Config, + E: EthSpec, + SyncCommittee: TryFrom + Into, + LightClientHeader: TryFrom + Into, +{ + fn get_current_sync_committee(&self) -> Result, light_client::Error> { + let sync_committee = CurrentSyncCommittee::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let sync_committee = sync_committee + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(sync_committee) + } - log::info!( - target: "ethereum-beacon-client", - "💫 Initial sync processing succeeded.", - ); + fn set_current_sync_committee( + &self, + sync_committee: SyncCommittee, + ) -> Result<(), light_client::Error> { + let sync_committee: GenericSyncCommittee = sync_committee.into(); + CurrentSyncCommittee::::insert(self.chain_id, sync_committee); + Ok(()) + } - Ok(()) + fn get_next_sync_committee(&self) -> Result>, light_client::Error> { + let sync_committee = NextSyncCommittee::::get(self.chain_id); + if let Some(sync_committee) = sync_committee { + let sync_committee = sync_committee + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(Some(sync_committee)) + } else { + Ok(None) } + } - // Verifies that the receipt encoded in proof.data is included - // in the block given by proof.block_hash. Inclusion is only - // recognized if the block has been finalized. - fn verify_receipt_inclusion( - stored_header: ExecutionPayloadHeader, - proof: &Proof, - ) -> Result { - let result = ethereum_primitives::Header::check_receipt_proof_with_root( - stored_header.receipts_root(), - &proof.data, - ) - .ok_or(Error::::InvalidProof)?; - - match result { - Ok(receipt) => Ok(receipt), - Err(err) => { - log::trace!( - target: "ethereum-beacon-client", - "💫 Failed to decode transaction receipt: {}", - err - ); - Err(Error::::InvalidProof.into()) - } - } + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), light_client::Error> { + if let Some(sync_committee) = sync_committee { + let sync_committee: GenericSyncCommittee = sync_committee.into(); + NextSyncCommittee::::insert(self.chain_id, sync_committee); + } else { + NextSyncCommittee::::set(self.chain_id, None); } + Ok(()) } - impl, I: 'static> Verifier for Pallet { - type Result = (Log, u64); - /// Verify a message by verifying the existence of the corresponding - /// Ethereum log in a block. Returns the log if successful. - fn verify(chain_id: EVMChainId, message: &Message) -> Result<(Log, u64), DispatchError> { - log::info!( - target: "ethereum-beacon-client", - "💫 Verifying message with block hash {}", - message.proof.block_hash, - ); - - let stored_header = >::get(chain_id, message.proof.block_hash) - .ok_or(Error::::MissingHeader)?; - - let block_number = stored_header.block_number(); - - let receipt = match Self::verify_receipt_inclusion(stored_header, &message.proof) { - Ok(receipt) => receipt, - Err(err) => { - log::error!( - target: "ethereum-beacon-client", - "💫 Verify receipt inclusion failed for block {}: {:?}", - message.proof.block_hash, - err - ); - return Err(err); - } - }; - - log::trace!( - target: "ethereum-beacon-client", - "💫 Verified receipt inclusion for transaction at index {} in block {}", - message.proof.tx_index, message.proof.block_hash, - ); - - let log = match rlp::decode(&message.data) { - Ok(log) => log, - Err(err) => { - log::error!( - target: "ethereum-beacon-client", - "💫 RLP log decoded failed {}: {:?}", - message.proof.block_hash, - err - ); - return Err(Error::::DecodeFailed.into()); - } - }; + fn get_finalized_header(&self) -> Result, light_client::Error> { + let header = FinalizedLightClientHeader::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let header = header + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(header) + } - if !receipt.contains_log(&log) { - log::error!( - target: "ethereum-beacon-client", - "💫 Event log not found in receipt for transaction at index {} in block {}", - message.proof.tx_index, message.proof.block_hash, - ); - return Err(Error::::InvalidProof.into()); - } + fn set_finalized_header( + &self, + header: LightClientHeader, + ) -> Result<(), light_client::Error> { + let header: GenericHeader = header.into(); + FinalizedLightClientHeader::::insert(self.chain_id, header); + Ok(()) + } - log::info!( - target: "ethereum-beacon-client", - "💫 Receipt verification successful for {}", - message.proof.block_hash, - ); + fn get_optimistic_header(&self) -> Result, light_client::Error> { + let header = OptimisticLightClientHeader::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let header = header + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(header) + } - Ok((log, block_number)) - } + fn set_optimistic_header( + &self, + header: LightClientHeader, + ) -> Result<(), light_client::Error> { + let header: GenericHeader = header.into(); + OptimisticLightClientHeader::::insert(self.chain_id, header); + Ok(()) } } diff --git a/pallets/beacon-light-client/src/light_client.rs b/pallets/beacon-light-client/src/light_client.rs new file mode 100644 index 00000000..3dba84d7 --- /dev/null +++ b/pallets/beacon-light-client/src/light_client.rs @@ -0,0 +1,477 @@ +use core::cell::RefCell; +use core::marker::PhantomData; + +use beacon::{ + light_client_bootstrap::LightClientBootstrap, + light_client_header::LightClientHeaderRef, + light_client_update::{ + LightClientUpdate, CURRENT_SYNC_COMMITTEE_INDEX, CURRENT_SYNC_COMMITTEE_PROOF_LEN, + EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN, FINALIZED_ROOT_INDEX, + FINALIZED_ROOT_PROOF_LEN, NEXT_SYNC_COMMITTEE_INDEX, NEXT_SYNC_COMMITTEE_PROOF_LEN, + }, + BeaconBlockHeader, Epoch, EthSpec, ForkVersion, LightClientHeader, Slot, SyncAggregate, + SyncCommittee, +}; +use beacon::{ForkSchedule, Unsigned}; +use bridge_types::H256; +use frame_support::log; +use sp_io::hashing::sha2_256; +use tree_hash::TreeHash; + +pub const DOMAIN_SYNC_COMMITTEE: u32 = 7; +pub const MIN_SYNC_COMMITTEE_PARTICIPANTS: usize = 1; + +pub enum Error { + InvalidMerkleBranch, + ArithError, + ZeroParticipants, + NotEnoughParticipants, + InvalidUpdate, + InvalidPublicKeyBytes, + SignatureVerificationFailed, + DuplicateSyncCommitteeUpdate, + InvalidSpecId, + StoreNotInitialized, +} + +impl From for Error { + fn from(_value: beacon::safe_arith::ArithError) -> Self { + Error::ArithError + } +} + +pub struct MemoryLightClientStore { + pub current_sync_committee: RefCell>>, + pub next_sync_committee: RefCell>>, + pub finalized_header: RefCell>>, + pub optimistic_header: RefCell>>, +} + +impl LightClientStore for MemoryLightClientStore { + fn get_current_sync_committee(&self) -> Result, Error> { + self.current_sync_committee + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_current_sync_committee(&self, sync_committee: SyncCommittee) -> Result<(), Error> { + log::debug!("Current sync committee updated"); + let mut value = self.current_sync_committee.borrow_mut(); + *value = Some(sync_committee); + Ok(()) + } + + fn get_next_sync_committee(&self) -> Result>, Error> { + Ok(self + .next_sync_committee + .borrow() + .as_ref() + .map(|x| x.clone())) + } + + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), Error> { + log::debug!("Next sync committee updated"); + let mut value = self.next_sync_committee.borrow_mut(); + *value = sync_committee; + Ok(()) + } + + fn get_finalized_header(&self) -> Result, Error> { + self.finalized_header + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_finalized_header(&self, header: LightClientHeader) -> Result<(), Error> { + log::debug!("Finalized header updated"); + let mut value = self.finalized_header.borrow_mut(); + *value = Some(header); + Ok(()) + } + + fn get_optimistic_header(&self) -> Result, Error> { + self.optimistic_header + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_optimistic_header(&self, header: LightClientHeader) -> Result<(), Error> { + log::debug!("Optimistic header updated"); + let mut value = self.optimistic_header.borrow_mut(); + *value = Some(header); + Ok(()) + } +} + +pub trait LightClientStore { + fn get_current_sync_committee(&self) -> Result, Error>; + fn set_current_sync_committee(&self, sync_committee: SyncCommittee) -> Result<(), Error>; + fn get_next_sync_committee(&self) -> Result>, Error>; + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), Error>; + fn get_finalized_header(&self) -> Result, Error>; + fn set_finalized_header(&self, header: LightClientHeader) -> Result<(), Error>; + fn get_optimistic_header(&self) -> Result, Error>; + fn set_optimistic_header(&self, header: LightClientHeader) -> Result<(), Error>; +} + +pub struct BeaconLightClient { + fork_schedule: ForkSchedule, + validators_root: H256, + store: S, + _phantom: PhantomData, +} + +impl BeaconLightClient +where + E: EthSpec, + S: LightClientStore, +{ + pub fn new(fork_schedule: ForkSchedule, validators_root: H256, store: S) -> Self { + Self { + fork_schedule, + validators_root, + store, + _phantom: Default::default(), + } + } + + pub fn initialize(&self, bootstrap: LightClientBootstrap) -> Result<(), Error> { + self.is_valid_header(bootstrap.header())?; + Self::is_valid_merkle_branch( + bootstrap.current_sync_committee().tree_hash_root(), + bootstrap.current_sync_committee_branch(), + CURRENT_SYNC_COMMITTEE_PROOF_LEN, + CURRENT_SYNC_COMMITTEE_INDEX, + bootstrap.header().beacon().state_root, + )?; + self.store + .set_current_sync_committee(bootstrap.current_sync_committee().clone())?; + self.store + .set_finalized_header(bootstrap.header().owned())?; + self.store + .set_optimistic_header(bootstrap.header().owned())?; + Ok(()) + } + + pub fn import_update(&self, update: LightClientUpdate) -> Result<(), Error> { + self.validate_update(&update)?; + let store_next_sync_committee = self.store.get_next_sync_committee()?; + let sync_committee_bits = update.sync_aggregate().sync_committee_bits.num_set_bits(); + let sync_committee_size = E::SyncCommitteeSize::to_usize(); + + let store_optimistic_slot = self.store.get_optimistic_header()?.beacon().slot; + let update_attested_slot = update.attested_header().beacon().slot; + let update_attested_period = update_attested_slot.sync_committee_period_with_spec::(); + let update_finalized_slot = if let Some(header) = update.finalized_header() { + header.beacon().slot + } else { + Default::default() + }; + let update_finalized_period = update_finalized_slot.sync_committee_period_with_spec::(); + let store_finalized_slot = self.store.get_finalized_header()?.beacon().slot; + let store_finalized_period = store_finalized_slot.sync_committee_period_with_spec::(); + + // Update optimistic header + // Requires at least 1/3 signatures + if sync_committee_bits * 3 >= sync_committee_size + && update_attested_slot > store_optimistic_slot + { + self.store + .set_optimistic_header(update.attested_header().owned())?; + } + + let update_has_finalized_next_sync_committee = store_next_sync_committee.is_none() + && update.is_sync_committee_update() + && update.is_finality_update() + && update_attested_period == update_finalized_period; + + if sync_committee_bits * 3 >= sync_committee_size * 2 + && (update_finalized_slot > store_finalized_slot + || update_has_finalized_next_sync_committee) + { + if let Some(next_sync_committee) = store_next_sync_committee { + if update_finalized_period == store_finalized_period + 1 { + self.store.set_current_sync_committee(next_sync_committee)?; + self.store + .set_next_sync_committee(update.next_sync_committee().clone())?; + } + } else { + if update_finalized_period != store_finalized_period { + return Err(Error::InvalidUpdate); + } + self.store + .set_next_sync_committee(update.next_sync_committee().clone())?; + } + if update_finalized_slot > store_finalized_slot { + if let Some(finalized_header) = update.finalized_header() { + self.store.set_finalized_header(finalized_header.owned())?; + } + } + } + + Ok(()) + } + + pub fn validate_update(&self, update: &LightClientUpdate) -> Result<(), Error> { + if update.sync_aggregate().sync_committee_bits.num_set_bits() + < MIN_SYNC_COMMITTEE_PARTICIPANTS + { + return Err(Error::ZeroParticipants); + } + + self.is_valid_header(update.attested_header())?; + + let update_finalized_slot = if let Some(header) = update.finalized_header() { + header.beacon().slot + } else { + Default::default() + }; + let update_attested_slot = update.attested_header().beacon().slot; + + // Sanity check + if update.signature_slot() <= update_attested_slot + || update_attested_slot < update_finalized_slot + { + return Err(Error::InvalidUpdate); + } + + let store_finalized_slot = self.store.get_finalized_header()?.beacon().slot; + let store_period = store_finalized_slot.sync_committee_period_with_spec::(); + let update_signature_period = update + .signature_slot() + .sync_committee_period_with_spec::(); + let next_sync_committee = self.store.get_next_sync_committee()?; + if next_sync_committee.is_some() { + if ![store_period, store_period + 1].contains(&update_signature_period) { + return Err(Error::InvalidUpdate); + } + } else { + if store_period != update_signature_period { + return Err(Error::InvalidUpdate); + } + } + + let update_attested_period = update + .attested_header() + .beacon() + .slot + .sync_committee_period_with_spec::(); + let update_has_next_sync_committee = next_sync_committee.is_none() + && update.next_sync_committee().is_some() + && update.next_sync_committee_branch().is_some() + && update_attested_period == store_period; + + if !update_has_next_sync_committee && update_attested_slot <= store_finalized_slot { + return Err(Error::InvalidUpdate); + } + + match (update.finalized_header(), update.finality_branch()) { + (Some(header), Some(branch)) => { + self.is_valid_header(header)?; + Self::is_valid_merkle_branch( + header.beacon().tree_hash_root(), + branch, + FINALIZED_ROOT_PROOF_LEN, + FINALIZED_ROOT_INDEX, + update.attested_header().beacon().state_root, + )?; + } + (None, None) => {} + _ => return Err(Error::InvalidUpdate), + } + + match ( + update.next_sync_committee(), + update.next_sync_committee_branch(), + ) { + (Some(sync_committee), Some(branch)) => { + if let Some(next_sync_committee) = &next_sync_committee { + if update_attested_period == store_period + && next_sync_committee != sync_committee + { + return Err(Error::InvalidUpdate); + } + } + Self::is_valid_merkle_branch( + sync_committee.tree_hash_root(), + branch, + NEXT_SYNC_COMMITTEE_PROOF_LEN, + NEXT_SYNC_COMMITTEE_INDEX, + update.attested_header().beacon().state_root, + )?; + } + (None, None) => {} + _ => return Err(Error::InvalidUpdate), + } + + // Verify sync committee aggregate signature + let sync_committee = if update_signature_period == store_period { + self.store.get_current_sync_committee()? + } else { + next_sync_committee.ok_or(Error::InvalidUpdate)? + }; + self.validate_signed_header( + update.sync_aggregate(), + &sync_committee, + update.attested_header().beacon(), + update.signature_slot(), + )?; + + Ok(()) + } + + pub fn validate_signed_header( + &self, + sync_aggregate: &SyncAggregate, + sync_committee: &SyncCommittee, + header: &BeaconBlockHeader, + signature_slot: Slot, + ) -> Result<(), Error> { + let mut participant_pubkeys: Vec = Vec::new(); + // Gathers all the pubkeys of the sync committee members that participated in signing + // the header. + for (bit, pubkey) in sync_aggregate + .sync_committee_bits + .iter() + .zip(sync_committee.pubkeys.iter()) + { + if bit { + participant_pubkeys.push( + pubkey + .decompress() + .map_err(|_| Error::InvalidPublicKeyBytes)?, + ); + } + } + + let fork = + self.compute_fork_version((signature_slot.saturating_sub(1u64)).epoch_with_spec::()); + + // Domains are used for for seeds, for signatures, and for selecting aggregators. + let domain = + Self::compute_domain_with_constant(DOMAIN_SYNC_COMMITTEE, fork, self.validators_root); + // Hash tree root of SigningData - object root + domain + let signing_root = Self::compute_signing_root(header, domain); + + // Verify sync committee aggregate signature. + if !sync_aggregate + .sync_committee_signature + .fast_aggregate_verify( + signing_root, + // TODO: Optimize + &participant_pubkeys.iter().collect::>(), + ) + { + return Err(Error::SignatureVerificationFailed.into()); + } + Ok(()) + } + + pub fn is_valid_header(&self, header: LightClientHeaderRef) -> Result<(), Error> { + if let Ok(execution) = header.execution() { + Self::is_valid_merkle_branch( + execution.tree_hash_root(), + header + .execution_branch() + .expect("execution branch exists if execution payload exists"), + EXECUTION_PAYLOAD_PROOF_LEN, + EXECUTION_PAYLOAD_INDEX, + header.beacon().body_root, + )?; + } + // Execution branch is not presented before Capella fork + Ok(()) + } + + pub fn is_valid_merkle_branch( + leaf: H256, + branch: &[H256], + depth: usize, + index: usize, + root: H256, + ) -> Result<(), Error> { + if branch.len() != depth { + log::error!(target: "ethereum-beacon-client", "Merkle proof branch length doesn't match depth."); + + return Err(Error::InvalidMerkleBranch); + } + let mut value = leaf; + for (i, item) in branch.iter().enumerate() { + if (index / 2usize.pow(i as u32) % 2) == 0 { + // left node + let mut data = [0u8; 64]; + data[0..32].copy_from_slice(&(value.0)); + data[32..64].copy_from_slice(&(item.0)); + value = sha2_256(&data).into(); + } else { + // right node + let mut data = [0u8; 64]; + data[0..32].copy_from_slice(&(item.0)); + data[32..64].copy_from_slice(&(value.0)); + value = sha2_256(&data).into(); + } + } + + if value != root { + return Err(Error::InvalidMerkleBranch); + } + Ok(()) + } + + pub fn compute_signing_root(beacon_header: &BeaconBlockHeader, domain: H256) -> H256 { + let beacon_header_root = beacon_header.tree_hash_root(); + let hash_root = beacon::SigningData { + domain, + object_root: beacon_header_root, + } + .tree_hash_root(); + hash_root + } + + pub fn compute_fork_version(&self, epoch: Epoch) -> ForkVersion { + self.fork_schedule.fork_version(epoch.as_u64()) + } + + /// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`. + /// + /// This is used primarily in signature domains to avoid collisions across forks/chains. + /// + /// Spec v0.12.1 + pub fn compute_fork_data_root(current_version: [u8; 4], genesis_validators_root: H256) -> H256 { + beacon::ForkData { + current_version, + genesis_validators_root, + } + .tree_hash_root() + } + + /// Compute a domain by applying the given `fork_version`. + pub fn compute_domain_with_constant( + domain_constant: u32, + fork_version: [u8; 4], + genesis_validators_root: H256, + ) -> H256 { + let mut domain = [0; 32]; + domain[0..4].copy_from_slice(&beacon::int_to_bytes::int_to_bytes4(domain_constant)); + domain[4..].copy_from_slice( + Self::compute_fork_data_root(fork_version, genesis_validators_root) + .as_bytes() + .get(..28) + .expect("fork has is 32 bytes so first 28 bytes should exist"), + ); + + H256::from(domain) + } +} diff --git a/pallets/beacon-light-client/src/weights.rs b/pallets/beacon-light-client/src/weights.rs index 8947f7e5..84d4cfb6 100644 --- a/pallets/beacon-light-client/src/weights.rs +++ b/pallets/beacon-light-client/src/weights.rs @@ -8,28 +8,15 @@ use sp_std::marker::PhantomData; /// Weight functions needed for ethereum_beacon_client. pub trait WeightInfo { fn initialize() -> Weight; - fn sync_committee_period_update() -> Weight; - fn import_finalized_header() -> Weight; - fn import_execution_header() -> Weight; - fn unblock_bridge() -> Weight; + fn import_update() -> Weight; } -/// Weights for ethereum_beacon_client using the Snowbridge node and recommended hardware. -pub struct SnowbridgeWeight(PhantomData); -impl WeightInfo for SnowbridgeWeight { +pub struct PalletWeight(PhantomData); +impl WeightInfo for PalletWeight { fn initialize() -> Weight { Default::default() } - fn sync_committee_period_update() -> Weight { - Default::default() - } - fn import_finalized_header() -> Weight { - Default::default() - } - fn import_execution_header() -> Weight { - Default::default() - } - fn unblock_bridge() -> Weight { + fn import_update() -> Weight { Default::default() } } @@ -39,16 +26,7 @@ impl WeightInfo for () { fn initialize() -> Weight { Default::default() } - fn sync_committee_period_update() -> Weight { - Default::default() - } - fn import_finalized_header() -> Weight { - Default::default() - } - fn import_execution_header() -> Weight { - Default::default() - } - fn unblock_bridge() -> Weight { + fn import_update() -> Weight { Default::default() } } diff --git a/primitives/beacon/Cargo.toml b/primitives/beacon/Cargo.toml index 5cfc41b9..09ec3c83 100644 --- a/primitives/beacon/Cargo.toml +++ b/primitives/beacon/Cargo.toml @@ -33,6 +33,7 @@ eth2_hashing = { git = "https://github.com/vovac12/lighthouse-ssz.git", version hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } maplit = "1.0.2" +hex-literal = { version = "0.3.1", default-features = false } [dev-dependencies] itertools = "0.10.5" diff --git a/primitives/beacon/src/aggregate_and_proof.rs b/primitives/beacon/src/aggregate_and_proof.rs deleted file mode 100644 index 51b11d16..00000000 --- a/primitives/beacon/src/aggregate_and_proof.rs +++ /dev/null @@ -1,93 +0,0 @@ -use super::{ - Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, SelectionProof, - Signature, SignedRoot, -}; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// A Validators aggregate attestation and selection proof. -/// -/// Spec v0.12.1 -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct AggregateAndProof { - /// The index of the validator that created the attestation. - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub aggregator_index: u64, - /// The aggregate attestation. - pub aggregate: Attestation, - /// A proof provided by the validator that permits them to publish on the - /// `beacon_aggregate_and_proof` gossipsub topic. - pub selection_proof: Signature, -} - -impl AggregateAndProof { - /// Produces a new `AggregateAndProof` with a `selection_proof` generated by signing - /// `aggregate.data.slot` with `secret_key`. - /// - /// If `selection_proof.is_none()` it will be computed locally. - pub fn from_aggregate( - aggregator_index: u64, - aggregate: Attestation, - selection_proof: Option, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let selection_proof = selection_proof - .unwrap_or_else(|| { - SelectionProof::new::( - aggregate.data.slot, - secret_key, - fork, - genesis_validators_root, - spec, - ) - }) - .into(); - - Self { - aggregator_index, - aggregate, - selection_proof, - } - } - - /// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`. - pub fn is_valid_selection_proof( - &self, - validator_pubkey: &PublicKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> bool { - let target_epoch = self.aggregate.data.slot.epoch(T::slots_per_epoch()); - let domain = spec.get_domain( - target_epoch, - Domain::SelectionProof, - fork, - genesis_validators_root, - ); - let message = self.aggregate.data.slot.signing_root(domain); - self.selection_proof.verify(validator_pubkey, message) - } -} - -impl SignedRoot for AggregateAndProof {} diff --git a/primitives/beacon/src/application_domain.rs b/primitives/beacon/src/application_domain.rs deleted file mode 100644 index 4d27bb25..00000000 --- a/primitives/beacon/src/application_domain.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// This value is an application index of 0 with the bitmask applied (so it's equivalent to the bit mask). -/// Little endian hex: 0x00000001, Binary: 1000000000000000000000000 -pub const APPLICATION_DOMAIN_BUILDER: u32 = 16777216; -#[cfg(not(feature = "std"))] -use crate::prelude::*; - -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum ApplicationDomain { - Builder, -} - -impl ApplicationDomain { - pub fn get_domain_constant(&self) -> u32 { - match self { - ApplicationDomain::Builder => APPLICATION_DOMAIN_BUILDER, - } - } -} diff --git a/primitives/beacon/src/attestation.rs b/primitives/beacon/src/attestation.rs index 3909a1ac..87934012 100644 --- a/primitives/beacon/src/attestation.rs +++ b/primitives/beacon/src/attestation.rs @@ -6,12 +6,9 @@ use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; use crate::slot_data::SlotData; -use crate::{Hash256, Slot}; +use crate::Slot; -use super::{ - AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey, - Signature, SignedRoot, -}; +use super::{AggregateSignature, AttestationData, BitList, EthSpec, Signature}; #[derive(Debug, PartialEq)] pub enum Error { @@ -65,28 +62,6 @@ impl Attestation { self.signature.add_assign_aggregate(&other.signature); } - /// Signs `self`, setting the `committee_position`'th bit of `aggregation_bits` to `true`. - /// - /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. - pub fn sign( - &mut self, - secret_key: &SecretKey, - committee_position: usize, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), Error> { - let domain = spec.get_domain( - self.data.target.epoch, - Domain::BeaconAttester, - fork, - genesis_validators_root, - ); - let message = self.data.signing_root(domain); - - self.add_signature(&secret_key.sign(message), committee_position) - } - /// Adds `signature` to `self` and sets the `committee_position`'th bit of `aggregation_bits` to `true`. /// /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. diff --git a/primitives/beacon/src/attestation_duty.rs b/primitives/beacon/src/attestation_duty.rs deleted file mode 100644 index 5e62a27b..00000000 --- a/primitives/beacon/src/attestation_duty.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::*; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize)] -pub struct AttestationDuty { - /// The slot during which the attester must attest. - pub slot: Slot, - /// The index of this committee within the committees in `slot`. - pub index: CommitteeIndex, - /// The position of the attester within the committee. - pub committee_position: usize, - /// The total number of attesters in the committee. - pub committee_len: usize, - /// The committee count at `attestation_slot`. - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub committees_at_slot: u64, -} diff --git a/primitives/beacon/src/beacon_block.rs b/primitives/beacon/src/beacon_block.rs index a116f037..6c651031 100644 --- a/primitives/beacon/src/beacon_block.rs +++ b/primitives/beacon/src/beacon_block.rs @@ -4,11 +4,9 @@ use crate::beacon_block_body::{ }; use crate::prelude::*; use crate::*; -use bls::Signature; -use core::marker::PhantomData; use derivative::Derivative; use serde::{Deserialize, Serialize}; -use ssz::{Decode, DecodeError}; +use ssz::Decode; use ssz_derive::{Decode, Encode}; use superstruct::superstruct; use tree_hash::TreeHash; @@ -93,42 +91,7 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload> SignedRoot { } -/// Empty block trait for each block variant to implement. -pub trait EmptyBlock { - /// Returns an empty block to be used during genesis. - fn empty(spec: &ChainSpec) -> Self; -} - impl> BeaconBlock { - /// Returns an empty block to be used during genesis. - pub fn empty(spec: &ChainSpec) -> Self { - map_fork_name!( - spec.fork_name_at_epoch(T::genesis_epoch()), - Self, - EmptyBlock::empty(spec) - ) - } - - /// Custom SSZ decoder that takes a `ChainSpec` as context. - pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { - let slot_len = ::ssz_fixed_len(); - let slot_bytes = bytes - .get(0..slot_len) - .ok_or(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: slot_len, - })?; - - let slot = Slot::from_ssz_bytes(slot_bytes)?; - let fork_at_slot = spec.fork_name_at_slot::(slot); - - Ok(map_fork_name!( - fork_at_slot, - Self, - <_>::from_ssz_bytes(bytes)? - )) - } - /// Try decoding each beacon block variant in sequence. /// /// This is *not* recommended unless you really have no idea what variant the block should be. @@ -181,51 +144,9 @@ impl> BeaconBlock { pub fn body_root(&self) -> Hash256 { self.to_ref().body_root() } - - /// Signs `self`, producing a `SignedBeaconBlock`. - pub fn sign( - self, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> SignedBeaconBlock { - let domain = spec.get_domain( - self.epoch(), - Domain::BeaconProposer, - fork, - genesis_validators_root, - ); - let message = self.signing_root(domain); - let signature = secret_key.sign(message); - SignedBeaconBlock::from_block(self, signature) - } } impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, T, Payload> { - /// Returns the name of the fork pertaining to `self`. - /// - /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork - /// dictated by `self.slot()`. - pub fn fork_name(&self, spec: &ChainSpec) -> Result { - let fork_at_slot = spec.fork_name_at_slot::(self.slot()); - let object_fork = match self { - BeaconBlockRef::Base { .. } => ForkName::Base, - BeaconBlockRef::Altair { .. } => ForkName::Altair, - BeaconBlockRef::Merge { .. } => ForkName::Merge, - BeaconBlockRef::Capella { .. } => ForkName::Capella, - }; - - if fork_at_slot == object_fork { - Ok(object_fork) - } else { - Err(InconsistentFork { - fork_at_slot, - object_fork, - }) - } - } - /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> { map_beacon_block_ref_into_beacon_block_body_ref!(&'a _, *self, |block, cons| cons( @@ -281,292 +202,6 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRefMut<'a, T, P } } -impl> EmptyBlock for BeaconBlockBase { - fn empty(spec: &ChainSpec) -> Self { - BeaconBlockBase { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyBase { - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), - _phantom: PhantomData, - }, - } - } -} - -impl> BeaconBlockBase { - /// Return a block where the block has maximum size. - pub fn full(spec: &ChainSpec) -> Self { - let header = BeaconBlockHeader { - slot: Slot::new(1), - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body_root: Hash256::zero(), - }; - - let signed_header = SignedBeaconBlockHeader { - message: header, - signature: Signature::empty(), - }; - let indexed_attestation: IndexedAttestation = IndexedAttestation { - attesting_indices: VariableList::new(vec![ - 0_u64; - T::MaxValidatorsPerCommittee::to_usize() - ]) - .unwrap(), - data: AttestationData::default(), - signature: AggregateSignature::empty(), - }; - - let deposit_data = DepositData { - pubkey: PublicKeyBytes::empty(), - withdrawal_credentials: Hash256::zero(), - amount: 0, - signature: SignatureBytes::empty(), - }; - let proposer_slashing = ProposerSlashing { - signed_header_1: signed_header.clone(), - signed_header_2: signed_header, - }; - - let attester_slashing = AttesterSlashing { - attestation_1: indexed_attestation.clone(), - attestation_2: indexed_attestation, - }; - - let attestation: Attestation = Attestation { - aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize()) - .unwrap(), - data: AttestationData::default(), - signature: AggregateSignature::empty(), - }; - - let deposit = Deposit { - proof: FixedVector::from_elem(Hash256::zero()), - data: deposit_data, - }; - - let voluntary_exit = VoluntaryExit { - epoch: Epoch::new(1), - validator_index: 1, - }; - - let signed_voluntary_exit = SignedVoluntaryExit { - message: voluntary_exit, - signature: Signature::empty(), - }; - - let mut block = BeaconBlockBase::::empty(spec); - for _ in 0..T::MaxProposerSlashings::to_usize() { - block - .body - .proposer_slashings - .push(proposer_slashing.clone()) - .unwrap(); - } - for _ in 0..T::MaxDeposits::to_usize() { - block.body.deposits.push(deposit.clone()).unwrap(); - } - for _ in 0..T::MaxVoluntaryExits::to_usize() { - block - .body - .voluntary_exits - .push(signed_voluntary_exit.clone()) - .unwrap(); - } - for _ in 0..T::MaxAttesterSlashings::to_usize() { - block - .body - .attester_slashings - .push(attester_slashing.clone()) - .unwrap(); - } - - for _ in 0..T::MaxAttestations::to_usize() { - block.body.attestations.push(attestation.clone()).unwrap(); - } - block - } -} - -impl> EmptyBlock for BeaconBlockAltair { - /// Returns an empty Altair block to be used during genesis. - fn empty(spec: &ChainSpec) -> Self { - BeaconBlockAltair { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyAltair { - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), - sync_aggregate: SyncAggregate::empty(), - _phantom: PhantomData, - }, - } - } -} - -impl> BeaconBlockAltair { - /// Return an Altair block where the block has maximum size. - pub fn full(spec: &ChainSpec) -> Self { - let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); - let sync_aggregate = SyncAggregate { - sync_committee_signature: AggregateSignature::empty(), - sync_committee_bits: BitVector::default(), - }; - BeaconBlockAltair { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyAltair { - proposer_slashings: base_block.body.proposer_slashings, - attester_slashings: base_block.body.attester_slashings, - attestations: base_block.body.attestations, - deposits: base_block.body.deposits, - voluntary_exits: base_block.body.voluntary_exits, - sync_aggregate, - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - _phantom: PhantomData, - }, - } - } -} - -impl> EmptyBlock for BeaconBlockMerge { - /// Returns an empty Merge block to be used during genesis. - fn empty(spec: &ChainSpec) -> Self { - BeaconBlockMerge { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyMerge { - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), - sync_aggregate: SyncAggregate::empty(), - execution_payload: Payload::Merge::default(), - }, - } - } -} - -impl> BeaconBlockCapella { - /// Return a Capella block where the block has maximum size. - pub fn full(spec: &ChainSpec) -> Self { - let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); - let bls_to_execution_changes = vec![ - SignedBlsToExecutionChange { - message: BlsToExecutionChange { - validator_index: 0, - from_bls_pubkey: PublicKeyBytes::empty(), - to_execution_address: Address::zero(), - }, - signature: Signature::empty() - }; - T::max_bls_to_execution_changes() - ] - .into(); - let sync_aggregate = SyncAggregate { - sync_committee_signature: AggregateSignature::empty(), - sync_committee_bits: BitVector::default(), - }; - BeaconBlockCapella { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyCapella { - proposer_slashings: base_block.body.proposer_slashings, - attester_slashings: base_block.body.attester_slashings, - attestations: base_block.body.attestations, - deposits: base_block.body.deposits, - voluntary_exits: base_block.body.voluntary_exits, - bls_to_execution_changes, - sync_aggregate, - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - execution_payload: Payload::Capella::default(), - }, - } - } -} - -impl> EmptyBlock for BeaconBlockCapella { - /// Returns an empty Capella block to be used during genesis. - fn empty(spec: &ChainSpec) -> Self { - BeaconBlockCapella { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyCapella { - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), - sync_aggregate: SyncAggregate::empty(), - execution_payload: Payload::Capella::default(), - bls_to_execution_changes: VariableList::empty(), - }, - } - } -} - // We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. impl From>> for BeaconBlockBase> diff --git a/primitives/beacon/src/beacon_block_header.rs b/primitives/beacon/src/beacon_block_header.rs index 43bed94c..17e7cbaf 100644 --- a/primitives/beacon/src/beacon_block_header.rs +++ b/primitives/beacon/src/beacon_block_header.rs @@ -43,22 +43,4 @@ impl BeaconBlockHeader { pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.tree_hash_root()[..]) } - - /// Signs `self`, producing a `SignedBeaconBlockHeader`. - pub fn sign( - self, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> SignedBeaconBlockHeader { - let epoch = self.slot.epoch(E::slots_per_epoch()); - let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork, genesis_validators_root); - let message = self.signing_root(domain); - let signature = secret_key.sign(message); - SignedBeaconBlockHeader { - message: self, - signature, - } - } } diff --git a/primitives/beacon/src/beacon_committee.rs b/primitives/beacon/src/beacon_committee.rs deleted file mode 100644 index 32f436ca..00000000 --- a/primitives/beacon/src/beacon_committee.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::*; - -#[derive(Default, Clone, Debug, PartialEq)] -pub struct BeaconCommittee<'a> { - pub slot: Slot, - pub index: CommitteeIndex, - pub committee: &'a [usize], -} - -impl<'a> BeaconCommittee<'a> { - pub fn into_owned(self) -> OwnedBeaconCommittee { - OwnedBeaconCommittee { - slot: self.slot, - index: self.index, - committee: self.committee.to_vec(), - } - } -} - -#[derive(Default, Clone, Debug, PartialEq)] -pub struct OwnedBeaconCommittee { - pub slot: Slot, - pub index: CommitteeIndex, - pub committee: Vec, -} diff --git a/primitives/ethereum/src/beacon.rs b/primitives/beacon/src/beacon_config.rs similarity index 55% rename from primitives/ethereum/src/beacon.rs rename to primitives/beacon/src/beacon_config.rs index b4767d02..0ebd503e 100644 --- a/primitives/ethereum/src/beacon.rs +++ b/primitives/beacon/src/beacon_config.rs @@ -1,33 +1,52 @@ use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::RuntimeDebug; +use ethereum_types::H256; use hex_literal::hex; use scale_info::TypeInfo; -pub type ForkVersion = [u8; 4]; +use crate::{EthSpecId, ForkVersion}; -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct BeaconFork { - #[cfg_attr( - feature = "std", - serde(with = "crate::serde_utils::serde_fork_version") - )] - version: [u8; 4], +#[derive( + Copy, + Clone, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ForkInfo { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + version: ForkVersion, epoch: u64, } -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct BeaconForkSchedule { - pub phase0: BeaconFork, - pub altair: BeaconFork, - pub bellatrix: BeaconFork, - pub capella: BeaconFork, +#[derive( + Copy, + Clone, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ForkSchedule { + pub phase0: ForkInfo, + pub altair: ForkInfo, + pub bellatrix: ForkInfo, + pub capella: ForkInfo, // Disabled forks // sharding: BeaconFork, } -impl BeaconForkSchedule { +impl ForkSchedule { pub fn fork_version(&self, epoch: u64) -> ForkVersion { if epoch >= self.capella.epoch { self.capella.version @@ -43,19 +62,19 @@ impl BeaconForkSchedule { // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/sepolia.ts pub fn sepolia() -> Self { Self { - phase0: BeaconFork { + phase0: ForkInfo { version: hex!("90000069"), epoch: 0, }, - altair: BeaconFork { + altair: ForkInfo { version: hex!("90000070"), epoch: 50, }, - bellatrix: BeaconFork { + bellatrix: ForkInfo { version: hex!("90000071"), epoch: 100, }, - capella: BeaconFork { + capella: ForkInfo { version: hex!("90000072"), epoch: 56832, }, @@ -65,19 +84,19 @@ impl BeaconForkSchedule { // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/goerli.ts pub fn goerli() -> Self { Self { - phase0: BeaconFork { + phase0: ForkInfo { version: hex!("00001020"), epoch: 0, }, - altair: BeaconFork { + altair: ForkInfo { version: hex!("01001020"), epoch: 36660, }, - bellatrix: BeaconFork { + bellatrix: ForkInfo { version: hex!("02001020"), epoch: 112260, }, - capella: BeaconFork { + capella: ForkInfo { version: hex!("03001020"), epoch: 162304, }, @@ -87,19 +106,19 @@ impl BeaconForkSchedule { // https://github.com/eth-clients/merge-testnets/blob/302fe27afdc7a9d15b1766a0c0a9d64319140255/mainnet-shadow-fork-13/config.yaml pub fn mainnet() -> Self { Self { - phase0: BeaconFork { + phase0: ForkInfo { version: hex!("00000000"), epoch: 0, }, - altair: BeaconFork { + altair: ForkInfo { version: hex!("01000000"), epoch: 74240, }, - bellatrix: BeaconFork { + bellatrix: ForkInfo { version: hex!("02000000"), epoch: 144896, }, - capella: BeaconFork { + capella: ForkInfo { version: hex!("04000000"), epoch: u64::MAX, }, @@ -108,19 +127,19 @@ impl BeaconForkSchedule { pub fn local() -> Self { Self { - phase0: BeaconFork { + phase0: ForkInfo { version: hex!("00000001"), epoch: 0, }, - altair: BeaconFork { + altair: ForkInfo { version: hex!("01000001"), epoch: 0, }, - bellatrix: BeaconFork { + bellatrix: ForkInfo { version: hex!("02000001"), epoch: 0, }, - capella: BeaconFork { + capella: ForkInfo { version: hex!("03000001"), epoch: u64::MAX, }, @@ -128,46 +147,53 @@ impl BeaconForkSchedule { } } -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub enum BeaconNetworkConfig { - Mainnet, - Minimal, +#[derive( + Clone, + Copy, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ConsensusConfig { + pub spec_id: EthSpecId, + pub fork_schedule: ForkSchedule, + pub genesis_validators_root: H256, } -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct BeaconConsensusConfig { - pub config: BeaconNetworkConfig, - pub fork_schedule: BeaconForkSchedule, -} - -impl BeaconConsensusConfig { +impl ConsensusConfig { pub fn mainnet() -> Self { Self { - config: BeaconNetworkConfig::Mainnet, - fork_schedule: BeaconForkSchedule::mainnet(), + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::mainnet(), + genesis_validators_root: H256(hex!( + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" + )), } } pub fn goerli() -> Self { Self { - config: BeaconNetworkConfig::Mainnet, - fork_schedule: BeaconForkSchedule::goerli(), + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::goerli(), + genesis_validators_root: H256(hex!( + "043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb" + )), } } pub fn sepolia() -> Self { Self { - config: BeaconNetworkConfig::Mainnet, - fork_schedule: BeaconForkSchedule::sepolia(), - } - } - - pub fn local() -> Self { - Self { - config: BeaconNetworkConfig::Minimal, - fork_schedule: BeaconForkSchedule::local(), + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::sepolia(), + genesis_validators_root: H256(hex!( + "d8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078" + )), } } diff --git a/primitives/beacon/src/beacon_state.rs b/primitives/beacon/src/beacon_state.rs index a55f99f4..a054524f 100644 --- a/primitives/beacon/src/beacon_state.rs +++ b/primitives/beacon/src/beacon_state.rs @@ -66,9 +66,7 @@ pub enum Error { initialized_epoch: Epoch, current_epoch: Epoch, }, - RelativeEpochError(RelativeEpochError), ExitCacheUninitialized, - CommitteeCacheUninitialized(Option), SyncCommitteeCacheUninitialized, BlsError(bls::Error), SszTypesError(ssz_types::Error), diff --git a/primitives/beacon/src/bls_to_execution_change.rs b/primitives/beacon/src/bls_to_execution_change.rs index 7e931de7..761b06c9 100644 --- a/primitives/beacon/src/bls_to_execution_change.rs +++ b/primitives/beacon/src/bls_to_execution_change.rs @@ -29,23 +29,3 @@ pub struct BlsToExecutionChange { } impl SignedRoot for BlsToExecutionChange {} - -impl BlsToExecutionChange { - pub fn sign( - self, - secret_key: &SecretKey, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> SignedBlsToExecutionChange { - let domain = spec.compute_domain( - Domain::BlsToExecutionChange, - spec.genesis_fork_version, - genesis_validators_root, - ); - let message = self.signing_root(domain); - SignedBlsToExecutionChange { - message: self, - signature: secret_key.sign(message), - } - } -} diff --git a/primitives/beacon/src/builder_bid.rs b/primitives/beacon/src/builder_bid.rs deleted file mode 100644 index fb2f0e11..00000000 --- a/primitives/beacon/src/builder_bid.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::prelude::*; -use crate::{ - AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot, - Uint256, -}; -#[cfg(feature = "std")] -use crate::{ForkName, ForkVersionDeserialize}; -use bls::PublicKeyBytes; -use bls::Signature; -use core::marker::PhantomData; -use serde::{Deserialize as De, Deserializer, Serialize as Ser, Serializer}; -use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DeserializeAs, SerializeAs}; -use tree_hash_derive::TreeHash; - -#[serde_as] -#[derive( - PartialEq, - Debug, - Serialize, - Deserialize, - TreeHash, - Clone, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "E: EthSpec, Payload: ExecPayload")] -#[scale_info(skip_type_params(T))] -pub struct BuilderBid> { - #[serde_as(as = "BlindedPayloadAsHeader")] - pub header: Payload, - #[serde(with = "eth2_serde_utils::quoted_u256")] - pub value: Uint256, - pub pubkey: PublicKeyBytes, - #[serde(skip)] - #[tree_hash(skip_hashing)] - _phantom_data: PhantomData, -} - -impl> SignedRoot for BuilderBid {} - -/// Validator registration, for use in interacting with servers implementing the builder API. -#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] -#[serde(bound = "E: EthSpec, Payload: ExecPayload")] -pub struct SignedBuilderBid> { - pub message: BuilderBid, - pub signature: Signature, -} - -#[cfg(feature = "std")] -impl> ForkVersionDeserialize - for BuilderBid -{ - fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( - value: serde_json::value::Value, - fork_name: ForkName, - ) -> Result { - let convert_err = |_| { - serde::de::Error::custom( - "BuilderBid failed to deserialize: unable to convert payload header to payload", - ) - }; - - #[derive(Deserialize)] - struct Helper { - header: serde_json::Value, - #[serde(with = "eth2_serde_utils::quoted_u256")] - value: Uint256, - pubkey: PublicKeyBytes, - } - let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?; - let payload_header = - ExecutionPayloadHeader::deserialize_by_fork::<'de, D>(helper.header, fork_name)?; - - Ok(Self { - header: Payload::try_from(payload_header).map_err(convert_err)?, - value: helper.value, - pubkey: helper.pubkey, - _phantom_data: Default::default(), - }) - } -} - -#[cfg(feature = "std")] -impl> ForkVersionDeserialize - for SignedBuilderBid -{ - fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( - value: serde_json::value::Value, - fork_name: ForkName, - ) -> Result { - #[derive(Deserialize)] - struct Helper { - pub message: serde_json::Value, - pub signature: Signature, - } - let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?; - - Ok(Self { - message: BuilderBid::deserialize_by_fork::<'de, D>(helper.message, fork_name)?, - signature: helper.signature, - }) - } -} - -struct BlindedPayloadAsHeader(PhantomData); - -impl> SerializeAs for BlindedPayloadAsHeader { - fn serialize_as(source: &Payload, serializer: S) -> Result - where - S: Serializer, - { - source.to_execution_payload_header().serialize(serializer) - } -} - -impl<'de, E: EthSpec, Payload: AbstractExecPayload> DeserializeAs<'de, Payload> - for BlindedPayloadAsHeader -{ - fn deserialize_as(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let payload_header = ExecutionPayloadHeader::deserialize(deserializer)?; - Payload::try_from(payload_header) - .map_err(|_| serde::de::Error::custom("unable to convert payload header to payload")) - } -} - -impl> SignedBuilderBid { - pub fn verify_signature(&self, spec: &ChainSpec) -> bool { - self.message - .pubkey - .decompress() - .map(|pubkey| { - let domain = spec.get_builder_domain(); - let message = self.message.signing_root(domain); - self.signature.verify(&pubkey, message) - }) - .unwrap_or(false) - } -} diff --git a/primitives/beacon/src/chain_spec.rs b/primitives/beacon/src/chain_spec.rs deleted file mode 100644 index 4dc9b365..00000000 --- a/primitives/beacon/src/chain_spec.rs +++ /dev/null @@ -1,1159 +0,0 @@ -use crate::application_domain::{ApplicationDomain, APPLICATION_DOMAIN_BUILDER}; -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::*; -use eth2_serde_utils::quoted_u64::MaybeQuoted; -use int_to_bytes::int_to_bytes4; -use serde::Deserialize; -use serde::{Deserializer, Serialize, Serializer}; - -use tree_hash::TreeHash; - -/// Each of the BLS signature domains. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum Domain { - BlsToExecutionChange, - BeaconProposer, - BeaconAttester, - BlobsSideCar, - Randao, - Deposit, - VoluntaryExit, - SelectionProof, - AggregateAndProof, - SyncCommittee, - ContributionAndProof, - SyncCommitteeSelectionProof, - ApplicationMask(ApplicationDomain), -} - -/// Lighthouse's internal configuration struct. -/// -/// Contains a mixture of "preset" and "config" values w.r.t to the EF definitions. -#[derive(PartialEq, Debug, Clone)] -pub struct ChainSpec { - /* - * Config name - */ - pub config_name: Option, - - /* - * Constants - */ - pub genesis_slot: Slot, - pub far_future_epoch: Epoch, - pub base_rewards_per_epoch: u64, - pub deposit_contract_tree_depth: u64, - - /* - * Misc - */ - pub max_committees_per_slot: usize, - pub target_committee_size: usize, - pub min_per_epoch_churn_limit: u64, - pub churn_limit_quotient: u64, - pub shuffle_round_count: u8, - pub min_genesis_active_validator_count: u64, - pub min_genesis_time: u64, - pub hysteresis_quotient: u64, - pub hysteresis_downward_multiplier: u64, - pub hysteresis_upward_multiplier: u64, - pub proportional_slashing_multiplier: u64, - - /* - * Gwei values - */ - pub min_deposit_amount: u64, - pub max_effective_balance: u64, - pub ejection_balance: u64, - pub effective_balance_increment: u64, - - /* - * Initial Values - */ - pub genesis_fork_version: [u8; 4], - pub bls_withdrawal_prefix_byte: u8, - pub eth1_address_withdrawal_prefix_byte: u8, - - /* - * Time parameters - */ - pub genesis_delay: u64, - pub seconds_per_slot: u64, - pub min_attestation_inclusion_delay: u64, - pub min_seed_lookahead: Epoch, - pub max_seed_lookahead: Epoch, - pub min_epochs_to_inactivity_penalty: u64, - pub min_validator_withdrawability_delay: Epoch, - pub shard_committee_period: u64, - - /* - * Reward and penalty quotients - */ - pub base_reward_factor: u64, - pub whistleblower_reward_quotient: u64, - pub proposer_reward_quotient: u64, - pub inactivity_penalty_quotient: u64, - pub min_slashing_penalty_quotient: u64, - - /* - * Signature domains - */ - pub(crate) domain_beacon_proposer: u32, - pub(crate) domain_beacon_attester: u32, - pub(crate) domain_blobs_sidecar: u32, - pub(crate) domain_randao: u32, - pub(crate) domain_deposit: u32, - pub(crate) domain_voluntary_exit: u32, - pub(crate) domain_selection_proof: u32, - pub(crate) domain_aggregate_and_proof: u32, - - /* - * Fork choice - */ - pub safe_slots_to_update_justified: u64, - pub proposer_score_boost: Option, - - /* - * Eth1 - */ - pub eth1_follow_distance: u64, - pub seconds_per_eth1_block: u64, - pub deposit_chain_id: u64, - pub deposit_network_id: u64, - pub deposit_contract_address: Address, - - /* - * Altair hard fork params - */ - pub inactivity_penalty_quotient_altair: u64, - pub min_slashing_penalty_quotient_altair: u64, - pub proportional_slashing_multiplier_altair: u64, - pub epochs_per_sync_committee_period: Epoch, - pub inactivity_score_bias: u64, - pub inactivity_score_recovery_rate: u64, - pub min_sync_committee_participants: u64, - pub(crate) domain_sync_committee: u32, - pub(crate) domain_sync_committee_selection_proof: u32, - pub(crate) domain_contribution_and_proof: u32, - pub altair_fork_version: [u8; 4], - /// The Altair fork epoch is optional, with `None` representing "Altair never happens". - pub altair_fork_epoch: Option, - - /* - * Merge hard fork params - */ - pub inactivity_penalty_quotient_bellatrix: u64, - pub min_slashing_penalty_quotient_bellatrix: u64, - pub proportional_slashing_multiplier_bellatrix: u64, - pub bellatrix_fork_version: [u8; 4], - /// The Merge fork epoch is optional, with `None` representing "Merge never happens". - pub bellatrix_fork_epoch: Option, - pub terminal_total_difficulty: Uint256, - pub terminal_block_hash: ExecutionBlockHash, - pub terminal_block_hash_activation_epoch: Epoch, - pub safe_slots_to_import_optimistically: u64, - - /* - * Capella hard fork params - */ - pub capella_fork_version: [u8; 4], - /// The Capella fork epoch is optional, with `None` representing "Capella never happens". - pub capella_fork_epoch: Option, - pub max_validators_per_withdrawals_sweep: u64, - - /* - * Networking - */ - pub boot_nodes: Vec, - pub network_id: u8, - pub attestation_propagation_slot_range: u64, - pub maximum_gossip_clock_disparity_millis: u64, - pub target_aggregators_per_committee: u64, - pub attestation_subnet_count: u64, - pub random_subnets_per_validator: u64, - pub epochs_per_random_subnet_subscription: u64, - pub subnets_per_node: u8, - pub epochs_per_subnet_subscription: u64, - attestation_subnet_extra_bits: u8, - - /* - * Application params - */ - pub(crate) domain_application_mask: u32, - - /* - * Capella params - */ - pub(crate) domain_bls_to_execution_change: u32, -} - -impl ChainSpec { - /// Construct a `ChainSpec` from a standard config. - pub fn from_config(config: &Config) -> Option { - let spec = T::default_spec(); - config.apply_to_chain_spec::(&spec) - } - - /// Returns an `EnrForkId` for the given `slot`. - pub fn enr_fork_id( - &self, - slot: Slot, - genesis_validators_root: Hash256, - ) -> EnrForkId { - EnrForkId { - fork_digest: self.fork_digest::(slot, genesis_validators_root), - next_fork_version: self.next_fork_version::(slot), - next_fork_epoch: self - .next_fork_epoch::(slot) - .map(|(_, e)| e) - .unwrap_or(self.far_future_epoch), - } - } - - /// Returns the `ForkDigest` for the given slot. - /// - /// If `self.altair_fork_epoch == None`, then this function returns the genesis fork digest - /// otherwise, returns the fork digest based on the slot. - pub fn fork_digest(&self, slot: Slot, genesis_validators_root: Hash256) -> [u8; 4] { - let fork_name = self.fork_name_at_slot::(slot); - Self::compute_fork_digest( - self.fork_version_for_name(fork_name), - genesis_validators_root, - ) - } - - /// Returns the `next_fork_version`. - /// - /// `next_fork_version = current_fork_version` if no future fork is planned, - pub fn next_fork_version(&self, slot: Slot) -> [u8; 4] { - match self.next_fork_epoch::(slot) { - Some((fork, _)) => self.fork_version_for_name(fork), - None => self.fork_version_for_name(self.fork_name_at_slot::(slot)), - } - } - - /// Returns the epoch of the next scheduled fork along with its corresponding `ForkName`. - /// - /// If no future forks are scheduled, this function returns `None`. - pub fn next_fork_epoch(&self, slot: Slot) -> Option<(ForkName, Epoch)> { - let current_fork_name = self.fork_name_at_slot::(slot); - let next_fork_name = current_fork_name.next_fork()?; - let fork_epoch = self.fork_epoch(next_fork_name)?; - Some((next_fork_name, fork_epoch)) - } - - /// Returns the name of the fork which is active at `slot`. - pub fn fork_name_at_slot(&self, slot: Slot) -> ForkName { - self.fork_name_at_epoch(slot.epoch(E::slots_per_epoch())) - } - - /// Returns the name of the fork which is active at `epoch`. - pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName { - match self.capella_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella, - _ => match self.bellatrix_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Merge, - _ => match self.altair_fork_epoch { - Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair, - _ => ForkName::Base, - }, - }, - } - } - - /// Returns the fork version for a named fork. - pub fn fork_version_for_name(&self, fork_name: ForkName) -> [u8; 4] { - match fork_name { - ForkName::Base => self.genesis_fork_version, - ForkName::Altair => self.altair_fork_version, - ForkName::Merge => self.bellatrix_fork_version, - ForkName::Capella => self.capella_fork_version, - } - } - - /// For a given fork name, return the epoch at which it activates. - pub fn fork_epoch(&self, fork_name: ForkName) -> Option { - match fork_name { - ForkName::Base => Some(Epoch::new(0)), - ForkName::Altair => self.altair_fork_epoch, - ForkName::Merge => self.bellatrix_fork_epoch, - ForkName::Capella => self.capella_fork_epoch, - } - } - - /// Returns a full `Fork` struct for a given epoch. - pub fn fork_at_epoch(&self, epoch: Epoch) -> Fork { - let current_fork_name = self.fork_name_at_epoch(epoch); - let previous_fork_name = current_fork_name.previous_fork().unwrap_or(ForkName::Base); - let epoch = self - .fork_epoch(current_fork_name) - .unwrap_or_else(|| Epoch::new(0)); - - Fork { - previous_version: self.fork_version_for_name(previous_fork_name), - current_version: self.fork_version_for_name(current_fork_name), - epoch, - } - } - - /// Returns a full `Fork` struct for a given `ForkName` or `None` if the fork does not yet have - /// an activation epoch. - pub fn fork_for_name(&self, fork_name: ForkName) -> Option { - let previous_fork_name = fork_name.previous_fork().unwrap_or(ForkName::Base); - let epoch = self.fork_epoch(fork_name)?; - - Some(Fork { - previous_version: self.fork_version_for_name(previous_fork_name), - current_version: self.fork_version_for_name(fork_name), - epoch, - }) - } - - /// Get the domain number, unmodified by the fork. - /// - /// Spec v0.12.1 - pub fn get_domain_constant(&self, domain: Domain) -> u32 { - match domain { - Domain::BeaconProposer => self.domain_beacon_proposer, - Domain::BeaconAttester => self.domain_beacon_attester, - Domain::BlobsSideCar => self.domain_blobs_sidecar, - Domain::Randao => self.domain_randao, - Domain::Deposit => self.domain_deposit, - Domain::VoluntaryExit => self.domain_voluntary_exit, - Domain::SelectionProof => self.domain_selection_proof, - Domain::AggregateAndProof => self.domain_aggregate_and_proof, - Domain::SyncCommittee => self.domain_sync_committee, - Domain::ContributionAndProof => self.domain_contribution_and_proof, - Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, - Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(), - Domain::BlsToExecutionChange => self.domain_bls_to_execution_change, - } - } - - /// Get the domain that represents the fork meta and signature domain. - /// - /// Spec v0.12.1 - pub fn get_domain( - &self, - epoch: Epoch, - domain: Domain, - fork: &Fork, - genesis_validators_root: Hash256, - ) -> Hash256 { - let fork_version = fork.get_fork_version(epoch); - self.compute_domain(domain, fork_version, genesis_validators_root) - } - - /// Get the domain for a deposit signature. - /// - /// Deposits are valid across forks, thus the deposit domain is computed - /// with the genesis fork version. - /// - /// Spec v0.12.1 - pub fn get_deposit_domain(&self) -> Hash256 { - self.compute_domain(Domain::Deposit, self.genesis_fork_version, Hash256::zero()) - } - - // This should be updated to include the current fork and the genesis validators root, but discussion is ongoing: - // - // https://github.com/ethereum/builder-specs/issues/14 - pub fn get_builder_domain(&self) -> Hash256 { - self.compute_domain( - Domain::ApplicationMask(ApplicationDomain::Builder), - self.genesis_fork_version, - Hash256::zero(), - ) - } - - /// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`. - /// - /// This is used primarily in signature domains to avoid collisions across forks/chains. - /// - /// Spec v0.12.1 - pub fn compute_fork_data_root( - current_version: [u8; 4], - genesis_validators_root: Hash256, - ) -> Hash256 { - ForkData { - current_version, - genesis_validators_root, - } - .tree_hash_root() - } - - /// Return the 4-byte fork digest for the `current_version` and `genesis_validators_root`. - /// - /// This is a digest primarily used for domain separation on the p2p layer. - /// 4-bytes suffices for practical separation of forks/chains. - pub fn compute_fork_digest( - current_version: [u8; 4], - genesis_validators_root: Hash256, - ) -> [u8; 4] { - let mut result = [0; 4]; - let root = Self::compute_fork_data_root(current_version, genesis_validators_root); - result.copy_from_slice( - root.as_bytes() - .get(0..4) - .expect("root hash is at least 4 bytes"), - ); - result - } - - /// Compute a domain by applying the given `fork_version`. - pub fn compute_domain( - &self, - domain: Domain, - fork_version: [u8; 4], - genesis_validators_root: Hash256, - ) -> Hash256 { - let domain_constant = self.get_domain_constant(domain); - Self::compute_domain_with_constant(domain_constant, fork_version, genesis_validators_root) - } - - pub fn compute_domain_with_constant( - domain_constant: u32, - fork_version: [u8; 4], - genesis_validators_root: Hash256, - ) -> Hash256 { - let mut domain = [0; 32]; - domain[0..4].copy_from_slice(&int_to_bytes4(domain_constant)); - domain[4..].copy_from_slice( - Self::compute_fork_data_root(fork_version, genesis_validators_root) - .as_bytes() - .get(..28) - .expect("fork has is 32 bytes so first 28 bytes should exist"), - ); - - Hash256::from(domain) - } - - #[allow(clippy::integer_arithmetic)] - pub const fn attestation_subnet_prefix_bits(&self) -> u32 { - // maybe use log2 when stable https://github.com/rust-lang/rust/issues/70887 - - // NOTE: this line is here simply to guarantee that if self.attestation_subnet_count type - // is changed, a compiler warning will be raised. This code depends on the type being u64. - let attestation_subnet_count: u64 = self.attestation_subnet_count; - let attestation_subnet_count_bits = if attestation_subnet_count == 0 { - 0 - } else { - 63 - attestation_subnet_count.leading_zeros() - }; - - self.attestation_subnet_extra_bits as u32 + attestation_subnet_count_bits - } - - /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. - pub fn mainnet() -> Self { - Self { - /* - * Config name - */ - config_name: Some("mainnet".to_string()), - /* - * Constants - */ - genesis_slot: Slot::new(0), - far_future_epoch: Epoch::new(u64::MAX), - base_rewards_per_epoch: 4, - deposit_contract_tree_depth: 32, - - /* - * Misc - */ - max_committees_per_slot: 64, - target_committee_size: 128, - min_per_epoch_churn_limit: 4, - churn_limit_quotient: 65_536, - shuffle_round_count: 90, - min_genesis_active_validator_count: 16_384, - min_genesis_time: 1606824000, // Dec 1, 2020 - hysteresis_quotient: 4, - hysteresis_downward_multiplier: 1, - hysteresis_upward_multiplier: 5, - - /* - * Gwei values - */ - min_deposit_amount: option_wrapper(|| { - u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - max_effective_balance: option_wrapper(|| { - u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - ejection_balance: option_wrapper(|| { - u64::checked_pow(2, 4)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - effective_balance_increment: option_wrapper(|| { - u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - - /* - * Initial Values - */ - genesis_fork_version: [0; 4], - bls_withdrawal_prefix_byte: 0x00, - eth1_address_withdrawal_prefix_byte: 0x01, - - /* - * Time parameters - */ - genesis_delay: 604800, // 7 days - seconds_per_slot: 12, - min_attestation_inclusion_delay: 1, - min_seed_lookahead: Epoch::new(1), - max_seed_lookahead: Epoch::new(4), - min_epochs_to_inactivity_penalty: 4, - min_validator_withdrawability_delay: Epoch::new(256), - shard_committee_period: 256, - - /* - * Reward and penalty quotients - */ - base_reward_factor: 64, - whistleblower_reward_quotient: 512, - proposer_reward_quotient: 8, - inactivity_penalty_quotient: u64::checked_pow(2, 26).expect("pow does not overflow"), - min_slashing_penalty_quotient: 128, - proportional_slashing_multiplier: 1, - - /* - * Signature domains - */ - domain_beacon_proposer: 0, - domain_beacon_attester: 1, - domain_randao: 2, - domain_deposit: 3, - domain_voluntary_exit: 4, - domain_selection_proof: 5, - domain_aggregate_and_proof: 6, - domain_blobs_sidecar: 10, // 0x0a000000 - - /* - * Fork choice - */ - safe_slots_to_update_justified: 8, - proposer_score_boost: Some(40), - - /* - * Eth1 - */ - eth1_follow_distance: 2048, - seconds_per_eth1_block: 14, - deposit_chain_id: 1, - deposit_network_id: 1, - deposit_contract_address: "00000000219ab540356cbb839cbe05303d7705fa" - .parse() - .expect("chain spec deposit contract address"), - - /* - * Altair hard fork params - */ - inactivity_penalty_quotient_altair: option_wrapper(|| { - u64::checked_pow(2, 24)?.checked_mul(3) - }) - .expect("calculation does not overflow"), - min_slashing_penalty_quotient_altair: u64::checked_pow(2, 6) - .expect("pow does not overflow"), - proportional_slashing_multiplier_altair: 2, - inactivity_score_bias: 4, - inactivity_score_recovery_rate: 16, - min_sync_committee_participants: 1, - epochs_per_sync_committee_period: Epoch::new(256), - domain_sync_committee: 7, - domain_sync_committee_selection_proof: 8, - domain_contribution_and_proof: 9, - altair_fork_version: [0x01, 0x00, 0x00, 0x00], - altair_fork_epoch: Some(Epoch::new(74240)), - - /* - * Merge hard fork params - */ - inactivity_penalty_quotient_bellatrix: u64::checked_pow(2, 24) - .expect("pow does not overflow"), - min_slashing_penalty_quotient_bellatrix: u64::checked_pow(2, 5) - .expect("pow does not overflow"), - proportional_slashing_multiplier_bellatrix: 3, - bellatrix_fork_version: [0x02, 0x00, 0x00, 0x00], - bellatrix_fork_epoch: Some(Epoch::new(144896)), - terminal_total_difficulty: Uint256::from_dec_str("58750000000000000000000") - .expect("terminal_total_difficulty is a valid integer"), - terminal_block_hash: ExecutionBlockHash::zero(), - terminal_block_hash_activation_epoch: Epoch::new(u64::MAX), - safe_slots_to_import_optimistically: 128u64, - - /* - * Capella hard fork params - */ - capella_fork_version: [0x03, 00, 00, 00], - capella_fork_epoch: None, - max_validators_per_withdrawals_sweep: 16384, - - /* - * Network specific - */ - boot_nodes: vec![], - network_id: 1, // mainnet network id - attestation_propagation_slot_range: 32, - attestation_subnet_count: 64, - random_subnets_per_validator: 1, - subnets_per_node: 1, - maximum_gossip_clock_disparity_millis: 500, - target_aggregators_per_committee: 16, - epochs_per_random_subnet_subscription: 256, - epochs_per_subnet_subscription: 256, - attestation_subnet_extra_bits: 6, - - /* - * Application specific - */ - domain_application_mask: APPLICATION_DOMAIN_BUILDER, - - /* - * Capella params - */ - domain_bls_to_execution_change: 10, - } - } - - /// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo. - pub fn minimal() -> Self { - // Note: bootnodes to be updated when static nodes exist. - let boot_nodes = vec![]; - - Self { - config_name: None, - max_committees_per_slot: 4, - target_committee_size: 4, - churn_limit_quotient: 32, - shuffle_round_count: 10, - min_genesis_active_validator_count: 64, - min_genesis_time: 1578009600, - eth1_follow_distance: 16, - genesis_fork_version: [0x00, 0x00, 0x00, 0x01], - shard_committee_period: 64, - genesis_delay: 300, - seconds_per_slot: 6, - inactivity_penalty_quotient: u64::checked_pow(2, 25).expect("pow does not overflow"), - min_slashing_penalty_quotient: 64, - proportional_slashing_multiplier: 2, - safe_slots_to_update_justified: 2, - // Altair - epochs_per_sync_committee_period: Epoch::new(8), - altair_fork_version: [0x01, 0x00, 0x00, 0x01], - altair_fork_epoch: None, - // Merge - bellatrix_fork_version: [0x02, 0x00, 0x00, 0x01], - bellatrix_fork_epoch: None, - terminal_total_difficulty: Uint256::MAX - .checked_sub(Uint256::from(2u64.pow(10))) - .expect("subtraction does not overflow") - // Add 1 since the spec declares `2**256 - 2**10` and we use - // `Uint256::MAX` which is `2*256- 1`. - .checked_add(Uint256::one()) - .expect("addition does not overflow"), - // Capella - capella_fork_version: [0x03, 0x00, 0x00, 0x01], - capella_fork_epoch: None, - max_validators_per_withdrawals_sweep: 16, - // Other - network_id: 2, // lighthouse testnet network id - deposit_chain_id: 5, - deposit_network_id: 5, - deposit_contract_address: "1234567890123456789012345678901234567890" - .parse() - .expect("minimal chain spec deposit address"), - boot_nodes, - ..ChainSpec::mainnet() - } - } - - /// Returns a `ChainSpec` compatible with the Gnosis Beacon Chain specification. - pub fn gnosis() -> Self { - Self { - config_name: Some("gnosis".to_string()), - /* - * Constants - */ - genesis_slot: Slot::new(0), - far_future_epoch: Epoch::new(u64::MAX), - base_rewards_per_epoch: 4, - deposit_contract_tree_depth: 32, - - /* - * Misc - */ - max_committees_per_slot: 64, - target_committee_size: 128, - min_per_epoch_churn_limit: 4, - churn_limit_quotient: 4_096, - shuffle_round_count: 90, - min_genesis_active_validator_count: 4_096, - min_genesis_time: 1638968400, // Dec 8, 2020 - hysteresis_quotient: 4, - hysteresis_downward_multiplier: 1, - hysteresis_upward_multiplier: 5, - - /* - * Gwei values - */ - min_deposit_amount: option_wrapper(|| { - u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - max_effective_balance: option_wrapper(|| { - u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - ejection_balance: option_wrapper(|| { - u64::checked_pow(2, 4)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - effective_balance_increment: option_wrapper(|| { - u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) - }) - .expect("calculation does not overflow"), - - /* - * Initial Values - */ - genesis_fork_version: [0x00, 0x00, 0x00, 0x64], - bls_withdrawal_prefix_byte: 0x00, - eth1_address_withdrawal_prefix_byte: 0x01, - - /* - * Time parameters - */ - genesis_delay: 6000, // 100 minutes - seconds_per_slot: 5, - min_attestation_inclusion_delay: 1, - min_seed_lookahead: Epoch::new(1), - max_seed_lookahead: Epoch::new(4), - min_epochs_to_inactivity_penalty: 4, - min_validator_withdrawability_delay: Epoch::new(256), - shard_committee_period: 256, - - /* - * Reward and penalty quotients - */ - base_reward_factor: 25, - whistleblower_reward_quotient: 512, - proposer_reward_quotient: 8, - inactivity_penalty_quotient: u64::checked_pow(2, 26).expect("pow does not overflow"), - min_slashing_penalty_quotient: 128, - proportional_slashing_multiplier: 1, - - /* - * Signature domains - */ - domain_beacon_proposer: 0, - domain_beacon_attester: 1, - domain_randao: 2, - domain_deposit: 3, - domain_voluntary_exit: 4, - domain_selection_proof: 5, - domain_aggregate_and_proof: 6, - domain_blobs_sidecar: 10, - - /* - * Fork choice - */ - safe_slots_to_update_justified: 8, - proposer_score_boost: Some(40), - - /* - * Eth1 - */ - eth1_follow_distance: 1024, - seconds_per_eth1_block: 6, - deposit_chain_id: 100, - deposit_network_id: 100, - deposit_contract_address: "0B98057eA310F4d31F2a452B414647007d1645d9" - .parse() - .expect("chain spec deposit contract address"), - - /* - * Altair hard fork params - */ - inactivity_penalty_quotient_altair: option_wrapper(|| { - u64::checked_pow(2, 24)?.checked_mul(3) - }) - .expect("calculation does not overflow"), - min_slashing_penalty_quotient_altair: u64::checked_pow(2, 6) - .expect("pow does not overflow"), - proportional_slashing_multiplier_altair: 2, - inactivity_score_bias: 4, - inactivity_score_recovery_rate: 16, - min_sync_committee_participants: 1, - epochs_per_sync_committee_period: Epoch::new(512), - domain_sync_committee: 7, - domain_sync_committee_selection_proof: 8, - domain_contribution_and_proof: 9, - altair_fork_version: [0x01, 0x00, 0x00, 0x64], - altair_fork_epoch: Some(Epoch::new(512)), - - /* - * Merge hard fork params - */ - inactivity_penalty_quotient_bellatrix: u64::checked_pow(2, 24) - .expect("pow does not overflow"), - min_slashing_penalty_quotient_bellatrix: u64::checked_pow(2, 5) - .expect("pow does not overflow"), - proportional_slashing_multiplier_bellatrix: 3, - bellatrix_fork_version: [0x02, 0x00, 0x00, 0x64], - bellatrix_fork_epoch: Some(Epoch::new(385536)), - terminal_total_difficulty: Uint256::from_dec_str( - "8626000000000000000000058750000000000000000000", - ) - .expect("terminal_total_difficulty is a valid integer"), - terminal_block_hash: ExecutionBlockHash::zero(), - terminal_block_hash_activation_epoch: Epoch::new(u64::MAX), - safe_slots_to_import_optimistically: 128u64, - - /* - * Capella hard fork params - */ - capella_fork_version: [0x03, 0x00, 0x00, 0x64], - capella_fork_epoch: None, - max_validators_per_withdrawals_sweep: 16384, - - /* - * Network specific - */ - boot_nodes: vec![], - network_id: 100, // Gnosis Chain network id - attestation_propagation_slot_range: 32, - attestation_subnet_count: 64, - random_subnets_per_validator: 1, - subnets_per_node: 1, - maximum_gossip_clock_disparity_millis: 500, - target_aggregators_per_committee: 16, - epochs_per_random_subnet_subscription: 256, - epochs_per_subnet_subscription: 256, - attestation_subnet_extra_bits: 6, - - /* - * Application specific - */ - domain_application_mask: APPLICATION_DOMAIN_BUILDER, - - /* - * Capella params - */ - domain_bls_to_execution_change: 10, - } - } -} - -impl Default for ChainSpec { - fn default() -> Self { - Self::mainnet() - } -} - -/// Exact implementation of the *config* object from the Ethereum spec (YAML/JSON). -/// -/// Fields relevant to hard forks after Altair should be optional so that we can continue -/// to parse Altair configs. This default approach turns out to be much simpler than trying to -/// make `Config` a superstruct because of the hassle of deserializing an untagged enum. -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all = "UPPERCASE")] -pub struct Config { - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub config_name: Option, - - #[serde(default)] - pub preset_base: String, - - #[serde(default = "default_terminal_total_difficulty")] - #[serde(with = "eth2_serde_utils::quoted_u256")] - pub terminal_total_difficulty: Uint256, - #[serde(default = "default_terminal_block_hash")] - pub terminal_block_hash: ExecutionBlockHash, - #[serde(default = "default_terminal_block_hash_activation_epoch")] - pub terminal_block_hash_activation_epoch: Epoch, - #[serde(default = "default_safe_slots_to_import_optimistically")] - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub safe_slots_to_import_optimistically: u64, - - #[serde(with = "eth2_serde_utils::quoted_u64")] - min_genesis_active_validator_count: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - min_genesis_time: u64, - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - genesis_fork_version: [u8; 4], - #[serde(with = "eth2_serde_utils::quoted_u64")] - genesis_delay: u64, - - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - altair_fork_version: [u8; 4], - #[serde(serialize_with = "serialize_fork_epoch")] - #[serde(deserialize_with = "deserialize_fork_epoch")] - pub altair_fork_epoch: Option>, - - #[serde(default = "default_bellatrix_fork_version")] - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - bellatrix_fork_version: [u8; 4], - #[serde(default)] - #[serde(serialize_with = "serialize_fork_epoch")] - #[serde(deserialize_with = "deserialize_fork_epoch")] - pub bellatrix_fork_epoch: Option>, - - #[serde(default = "default_capella_fork_version")] - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - capella_fork_version: [u8; 4], - #[serde(default)] - #[serde(serialize_with = "serialize_fork_epoch")] - #[serde(deserialize_with = "deserialize_fork_epoch")] - pub capella_fork_epoch: Option>, - - #[serde(with = "eth2_serde_utils::quoted_u64")] - seconds_per_slot: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - seconds_per_eth1_block: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - min_validator_withdrawability_delay: Epoch, - #[serde(with = "eth2_serde_utils::quoted_u64")] - shard_committee_period: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - eth1_follow_distance: u64, - - #[serde(with = "eth2_serde_utils::quoted_u64")] - inactivity_score_bias: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - inactivity_score_recovery_rate: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - ejection_balance: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - min_per_epoch_churn_limit: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - churn_limit_quotient: u64, - - #[serde(skip_serializing_if = "Option::is_none")] - proposer_score_boost: Option>, - - #[serde(with = "eth2_serde_utils::quoted_u64")] - deposit_chain_id: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - deposit_network_id: u64, - deposit_contract_address: Address, -} - -fn default_bellatrix_fork_version() -> [u8; 4] { - // This value shouldn't be used. - [0xff, 0xff, 0xff, 0xff] -} - -fn default_capella_fork_version() -> [u8; 4] { - // TODO: determine if the bellatrix example should be copied like this - [0xff, 0xff, 0xff, 0xff] -} - -/// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912). -/// -/// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16 -const fn default_terminal_total_difficulty() -> Uint256 { - ethereum_types::U256([ - 18446744073709550592, - 18446744073709551615, - 18446744073709551615, - 18446744073709551615, - ]) -} - -fn default_terminal_block_hash() -> ExecutionBlockHash { - ExecutionBlockHash::zero() -} - -fn default_terminal_block_hash_activation_epoch() -> Epoch { - Epoch::new(u64::MAX) -} - -fn default_safe_slots_to_import_optimistically() -> u64 { - 128u64 -} - -impl Default for Config { - fn default() -> Self { - let chain_spec = MainnetEthSpec::default_spec(); - Config::from_chain_spec::(&chain_spec) - } -} - -/// Util function to serialize a `None` fork epoch value -/// as `Epoch::max_value()`. -fn serialize_fork_epoch(val: &Option>, s: S) -> Result -where - S: Serializer, -{ - match val { - None => MaybeQuoted { - value: Epoch::max_value(), - } - .serialize(s), - Some(epoch) => epoch.serialize(s), - } -} - -/// Util function to deserialize a u64::max() fork epoch as `None`. -fn deserialize_fork_epoch<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let decoded: Option> = serde::de::Deserialize::deserialize(deserializer)?; - if let Some(fork_epoch) = decoded { - if fork_epoch.value != Epoch::max_value() { - return Ok(Some(fork_epoch)); - } - } - Ok(None) -} - -impl Config { - /// Maps `self` to an identifier for an `EthSpec` instance. - /// - /// Returns `None` if there is no match. - pub fn eth_spec_id(&self) -> Option { - match self.preset_base.as_str() { - "minimal" => Some(EthSpecId::Minimal), - "mainnet" => Some(EthSpecId::Mainnet), - "gnosis" => Some(EthSpecId::Gnosis), - _ => None, - } - } - - pub fn from_chain_spec(spec: &ChainSpec) -> Self { - Self { - config_name: spec.config_name.clone(), - preset_base: T::spec_name().to_string(), - - terminal_total_difficulty: spec.terminal_total_difficulty, - terminal_block_hash: spec.terminal_block_hash, - terminal_block_hash_activation_epoch: spec.terminal_block_hash_activation_epoch, - safe_slots_to_import_optimistically: spec.safe_slots_to_import_optimistically, - - min_genesis_active_validator_count: spec.min_genesis_active_validator_count, - min_genesis_time: spec.min_genesis_time, - genesis_fork_version: spec.genesis_fork_version, - genesis_delay: spec.genesis_delay, - - altair_fork_version: spec.altair_fork_version, - altair_fork_epoch: spec - .altair_fork_epoch - .map(|epoch| MaybeQuoted { value: epoch }), - bellatrix_fork_version: spec.bellatrix_fork_version, - bellatrix_fork_epoch: spec - .bellatrix_fork_epoch - .map(|epoch| MaybeQuoted { value: epoch }), - capella_fork_version: spec.capella_fork_version, - capella_fork_epoch: spec - .capella_fork_epoch - .map(|epoch| MaybeQuoted { value: epoch }), - - seconds_per_slot: spec.seconds_per_slot, - seconds_per_eth1_block: spec.seconds_per_eth1_block, - min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay, - shard_committee_period: spec.shard_committee_period, - eth1_follow_distance: spec.eth1_follow_distance, - - inactivity_score_bias: spec.inactivity_score_bias, - inactivity_score_recovery_rate: spec.inactivity_score_recovery_rate, - ejection_balance: spec.ejection_balance, - churn_limit_quotient: spec.churn_limit_quotient, - min_per_epoch_churn_limit: spec.min_per_epoch_churn_limit, - - proposer_score_boost: spec.proposer_score_boost.map(|value| MaybeQuoted { value }), - - deposit_chain_id: spec.deposit_chain_id, - deposit_network_id: spec.deposit_network_id, - deposit_contract_address: spec.deposit_contract_address, - } - } - - pub fn apply_to_chain_spec(&self, chain_spec: &ChainSpec) -> Option { - // Pattern match here to avoid missing any fields. - let &Config { - ref config_name, - ref preset_base, - terminal_total_difficulty, - terminal_block_hash, - terminal_block_hash_activation_epoch, - safe_slots_to_import_optimistically, - min_genesis_active_validator_count, - min_genesis_time, - genesis_fork_version, - genesis_delay, - altair_fork_version, - altair_fork_epoch, - bellatrix_fork_epoch, - bellatrix_fork_version, - capella_fork_epoch, - capella_fork_version, - seconds_per_slot, - seconds_per_eth1_block, - min_validator_withdrawability_delay, - shard_committee_period, - eth1_follow_distance, - inactivity_score_bias, - inactivity_score_recovery_rate, - ejection_balance, - min_per_epoch_churn_limit, - churn_limit_quotient, - proposer_score_boost, - deposit_chain_id, - deposit_network_id, - deposit_contract_address, - } = self; - - if preset_base != T::spec_name().to_string().as_str() { - return None; - } - - Some(ChainSpec { - config_name: config_name.clone(), - min_genesis_active_validator_count, - min_genesis_time, - genesis_fork_version, - genesis_delay, - altair_fork_version, - altair_fork_epoch: altair_fork_epoch.map(|q| q.value), - bellatrix_fork_epoch: bellatrix_fork_epoch.map(|q| q.value), - bellatrix_fork_version, - capella_fork_epoch: capella_fork_epoch.map(|q| q.value), - capella_fork_version, - seconds_per_slot, - seconds_per_eth1_block, - min_validator_withdrawability_delay, - shard_committee_period, - eth1_follow_distance, - inactivity_score_bias, - inactivity_score_recovery_rate, - ejection_balance, - min_per_epoch_churn_limit, - churn_limit_quotient, - proposer_score_boost: proposer_score_boost.map(|q| q.value), - deposit_chain_id, - deposit_network_id, - deposit_contract_address, - terminal_total_difficulty, - terminal_block_hash, - terminal_block_hash_activation_epoch, - safe_slots_to_import_optimistically, - ..chain_spec.clone() - }) - } -} - -/// A simple wrapper to permit the in-line use of `?`. -fn option_wrapper(f: F) -> Option -where - F: Fn() -> Option, -{ - f() -} diff --git a/primitives/beacon/src/config_and_preset.rs b/primitives/beacon/src/config_and_preset.rs deleted file mode 100644 index 82a22175..00000000 --- a/primitives/beacon/src/config_and_preset.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[cfg(feature = "std")] -use crate::consts::altair; -use crate::prelude::*; -use crate::*; -use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use serde_json::Value; -use superstruct::superstruct; - -/// Fusion of a runtime-config with the compile-time preset values. -/// -/// Mostly useful for the API. -#[superstruct( - variants(Bellatrix, Capella), - variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone)) -)] -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(untagged)] -pub struct ConfigAndPreset { - #[serde(flatten)] - pub config: Config, - - #[serde(flatten)] - pub base_preset: BasePreset, - #[serde(flatten)] - pub altair_preset: AltairPreset, - #[serde(flatten)] - pub bellatrix_preset: BellatrixPreset, - #[superstruct(only(Capella))] - #[serde(flatten)] - pub capella_preset: CapellaPreset, - /// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks. - #[serde(flatten)] - #[cfg(feature = "std")] - pub extra_fields: BTreeMap, -} - -impl ConfigAndPreset { - #[cfg(feature = "std")] - pub fn from_chain_spec(spec: &ChainSpec, fork_name: Option) -> Self { - let config = Config::from_chain_spec::(spec); - let base_preset = BasePreset::from_chain_spec::(spec); - let altair_preset = AltairPreset::from_chain_spec::(spec); - let bellatrix_preset = BellatrixPreset::from_chain_spec::(spec); - let extra_fields = get_extra_fields(spec); - - if spec.capella_fork_epoch.is_some() - || fork_name.is_none() - || fork_name == Some(ForkName::Capella) - { - let capella_preset = CapellaPreset::from_chain_spec::(spec); - - ConfigAndPreset::Capella(ConfigAndPresetCapella { - config, - base_preset, - altair_preset, - bellatrix_preset, - capella_preset, - extra_fields, - }) - } else { - ConfigAndPreset::Bellatrix(ConfigAndPresetBellatrix { - config, - base_preset, - altair_preset, - bellatrix_preset, - extra_fields, - }) - } - } -} - -/// Get a hashmap of constants to add to the `PresetAndConfig` -#[cfg(feature = "std")] -pub fn get_extra_fields(spec: &ChainSpec) -> BTreeMap { - let hex_string = |value: &[u8]| format!("0x{}", hex::encode(value)).into(); - let u32_hex = |v: u32| hex_string(&v.to_le_bytes()); - let u8_hex = |v: u8| hex_string(&v.to_le_bytes()); - maplit::btreemap! { - "bls_withdrawal_prefix".to_uppercase() => u8_hex(spec.bls_withdrawal_prefix_byte), - "domain_beacon_proposer".to_uppercase() => u32_hex(spec.domain_beacon_proposer), - "domain_beacon_attester".to_uppercase() => u32_hex(spec.domain_beacon_attester), - "domain_blobs_sidecar".to_uppercase() => u32_hex(spec.domain_blobs_sidecar), - "domain_randao".to_uppercase()=> u32_hex(spec.domain_randao), - "domain_deposit".to_uppercase()=> u32_hex(spec.domain_deposit), - "domain_voluntary_exit".to_uppercase() => u32_hex(spec.domain_voluntary_exit), - "domain_selection_proof".to_uppercase() => u32_hex(spec.domain_selection_proof), - "domain_aggregate_and_proof".to_uppercase() => u32_hex(spec.domain_aggregate_and_proof), - "domain_application_mask".to_uppercase()=> u32_hex(spec.domain_application_mask), - "target_aggregators_per_committee".to_uppercase() => - spec.target_aggregators_per_committee.to_string().into(), - "random_subnets_per_validator".to_uppercase() => - spec.random_subnets_per_validator.to_string().into(), - "epochs_per_random_subnet_subscription".to_uppercase() => - spec.epochs_per_random_subnet_subscription.to_string().into(), - "domain_contribution_and_proof".to_uppercase() => - u32_hex(spec.domain_contribution_and_proof), - "domain_sync_committee".to_uppercase() => u32_hex(spec.domain_sync_committee), - "domain_sync_committee_selection_proof".to_uppercase() => - u32_hex(spec.domain_sync_committee_selection_proof), - "sync_committee_subnet_count".to_uppercase() => - altair::SYNC_COMMITTEE_SUBNET_COUNT.to_string().into(), - "target_aggregators_per_sync_subcommittee".to_uppercase() => - altair::TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE.to_string().into(), - } -} diff --git a/primitives/beacon/src/contribution_and_proof.rs b/primitives/beacon/src/contribution_and_proof.rs deleted file mode 100644 index b64b9f78..00000000 --- a/primitives/beacon/src/contribution_and_proof.rs +++ /dev/null @@ -1,73 +0,0 @@ -use super::{ - ChainSpec, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, SyncCommitteeContribution, - SyncSelectionProof, -}; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// A Validators aggregate sync committee contribution and selection proof. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct ContributionAndProof { - /// The index of the validator that created the sync contribution. - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub aggregator_index: u64, - /// The aggregate contribution. - pub contribution: SyncCommitteeContribution, - /// A proof provided by the validator that permits them to publish on the - /// `sync_committee_contribution_and_proof` gossipsub topic. - pub selection_proof: Signature, -} - -impl ContributionAndProof { - /// Produces a new `ContributionAndProof` with a `selection_proof` generated by signing - /// `SyncAggregatorSelectionData` with `secret_key`. - /// - /// If `selection_proof.is_none()` it will be computed locally. - pub fn from_aggregate( - aggregator_index: u64, - contribution: SyncCommitteeContribution, - selection_proof: Option, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let selection_proof = selection_proof - .unwrap_or_else(|| { - SyncSelectionProof::new::( - contribution.slot, - contribution.subcommittee_index, - secret_key, - fork, - genesis_validators_root, - spec, - ) - }) - .into(); - - Self { - aggregator_index, - contribution, - selection_proof, - } - } -} - -impl SignedRoot for ContributionAndProof {} diff --git a/primitives/beacon/src/deposit_data.rs b/primitives/beacon/src/deposit_data.rs index 498c4f63..b4b06fde 100644 --- a/primitives/beacon/src/deposit_data.rs +++ b/primitives/beacon/src/deposit_data.rs @@ -31,26 +31,3 @@ pub struct DepositData { pub amount: u64, pub signature: SignatureBytes, } - -impl DepositData { - /// Create a `DepositMessage` corresponding to this `DepositData`, for signature verification. - /// - /// Spec v0.12.1 - pub fn as_deposit_message(&self) -> DepositMessage { - DepositMessage { - pubkey: self.pubkey, - withdrawal_credentials: self.withdrawal_credentials, - amount: self.amount, - } - } - - /// Generate the signature for a given DepositData details. - /// - /// Spec v0.12.1 - pub fn create_signature(&self, secret_key: &SecretKey, spec: &ChainSpec) -> SignatureBytes { - let domain = spec.get_deposit_domain(); - let msg = self.as_deposit_message().signing_root(domain); - - SignatureBytes::from(secret_key.sign(msg)) - } -} diff --git a/primitives/beacon/src/deposit_message.rs b/primitives/beacon/src/deposit_message.rs deleted file mode 100644 index 0a5a8046..00000000 --- a/primitives/beacon/src/deposit_message.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::*; - -use crate::prelude::*; -use bls::PublicKeyBytes; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// The data supplied by the user to the deposit contract. -/// -/// Spec v0.12.1 -#[derive( - Debug, - PartialEq, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct DepositMessage { - pub pubkey: PublicKeyBytes, - pub withdrawal_credentials: Hash256, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub amount: u64, -} - -impl SignedRoot for DepositMessage {} diff --git a/primitives/beacon/src/deposit_tree_snapshot.rs b/primitives/beacon/src/deposit_tree_snapshot.rs deleted file mode 100644 index 630f7d0b..00000000 --- a/primitives/beacon/src/deposit_tree_snapshot.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::*; -use eth2_hashing::{hash32_concat, ZERO_HASHES}; -use int_to_bytes::int_to_bytes32; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use DEPOSIT_TREE_DEPTH; - -#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq)] -pub struct FinalizedExecutionBlock { - pub deposit_root: Hash256, - pub deposit_count: u64, - pub block_hash: Hash256, - pub block_height: u64, -} - -impl From<&DepositTreeSnapshot> for FinalizedExecutionBlock { - fn from(snapshot: &DepositTreeSnapshot) -> Self { - Self { - deposit_root: snapshot.deposit_root, - deposit_count: snapshot.deposit_count, - block_hash: snapshot.execution_block_hash, - block_height: snapshot.execution_block_height, - } - } -} - -#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq)] -pub struct DepositTreeSnapshot { - pub finalized: Vec, - pub deposit_root: Hash256, - pub deposit_count: u64, - pub execution_block_hash: Hash256, - pub execution_block_height: u64, -} - -impl Default for DepositTreeSnapshot { - fn default() -> Self { - let mut result = Self { - finalized: vec![], - deposit_root: Hash256::default(), - deposit_count: 0, - execution_block_hash: Hash256::zero(), - execution_block_height: 0, - }; - // properly set the empty deposit root - result.deposit_root = result.calculate_root().unwrap(); - result - } -} - -impl DepositTreeSnapshot { - // Calculates the deposit tree root from the hashes in the snapshot - pub fn calculate_root(&self) -> Option { - let mut size = self.deposit_count; - let mut index = self.finalized.len(); - let mut deposit_root = [0; 32]; - for height in 0..DEPOSIT_TREE_DEPTH { - deposit_root = if (size & 1) == 1 { - index = index.checked_sub(1)?; - hash32_concat(self.finalized.get(index)?.as_bytes(), &deposit_root) - } else { - hash32_concat(&deposit_root, ZERO_HASHES.get(height)?) - }; - size /= 2; - } - // add mix-in-length - deposit_root = hash32_concat(&deposit_root, &int_to_bytes32(self.deposit_count)); - - Some(Hash256::from_slice(&deposit_root)) - } - pub fn is_valid(&self) -> bool { - self.calculate_root() - .map_or(false, |calculated| self.deposit_root == calculated) - } -} diff --git a/primitives/beacon/src/enr_fork_id.rs b/primitives/beacon/src/enr_fork_id.rs deleted file mode 100644 index c5ea3be1..00000000 --- a/primitives/beacon/src/enr_fork_id.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::Epoch; - -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// Specifies a fork which allows nodes to identify each other on the network. This fork is used in -/// a nodes local ENR. -/// -/// Spec v0.11 -#[derive( - Debug, - Clone, - PartialEq, - Default, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct EnrForkId { - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - pub fork_digest: [u8; 4], - #[serde(with = "eth2_serde_utils::bytes_4_hex")] - pub next_fork_version: [u8; 4], - pub next_fork_epoch: Epoch, -} diff --git a/primitives/beacon/src/eth_spec.rs b/primitives/beacon/src/eth_spec.rs index db5572e7..89cd82bf 100644 --- a/primitives/beacon/src/eth_spec.rs +++ b/primitives/beacon/src/eth_spec.rs @@ -1,6 +1,5 @@ use crate::*; -#[cfg(not(feature = "std"))] use crate::prelude::*; use core::fmt::{self, Debug}; use core::str::FromStr; @@ -18,7 +17,19 @@ const MINIMAL: &str = "minimal"; pub const GNOSIS: &str = "gnosis"; /// Used to identify one of the `EthSpec` instances defined here. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + Eq, + TypeInfo, + MaxEncodedLen, +)] #[serde(rename_all = "lowercase")] pub enum EthSpecId { Mainnet, @@ -121,31 +132,12 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + /// Must be set to `SyncCommitteeSize / SyncCommitteeSubnetCount`. type SyncSubcommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; - fn default_spec() -> ChainSpec; - fn spec_name() -> EthSpecId; fn genesis_epoch() -> Epoch { Epoch::new(Self::GenesisEpoch::to_u64()) } - /// Return the number of committees per slot. - /// - /// Note: the number of committees per slot is constant in each epoch, and depends only on - /// the `active_validator_count` during the slot's epoch. - /// - /// Spec v0.12.1 - fn get_committee_count_per_slot( - active_validator_count: usize, - spec: &ChainSpec, - ) -> Result { - Self::get_committee_count_per_slot_with( - active_validator_count, - spec.max_committees_per_slot, - spec.target_committee_size, - ) - } - fn get_committee_count_per_slot_with( active_validator_count: usize, max_committees_per_slot: usize, @@ -286,10 +278,6 @@ impl EthSpec for MainnetEthSpec { type MaxWithdrawalsPerPayload = U16; type EpochsPerSyncCommitteePeriod = U256; - fn default_spec() -> ChainSpec { - ChainSpec::mainnet() - } - fn spec_name() -> EthSpecId { EthSpecId::Mainnet } @@ -334,10 +322,6 @@ impl EthSpec for MinimalEthSpec { MaxBlsToExecutionChanges }); - fn default_spec() -> ChainSpec { - ChainSpec::minimal() - } - fn spec_name() -> EthSpecId { EthSpecId::Minimal } @@ -379,10 +363,6 @@ impl EthSpec for GnosisEthSpec { type MaxWithdrawalsPerPayload = U16; type EpochsPerSyncCommitteePeriod = U512; - fn default_spec() -> ChainSpec { - ChainSpec::gnosis() - } - fn spec_name() -> EthSpecId { EthSpecId::Gnosis } diff --git a/primitives/beacon/src/execution_block_header.rs b/primitives/beacon/src/execution_block_header.rs deleted file mode 100644 index a2158c00..00000000 --- a/primitives/beacon/src/execution_block_header.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2022 Reth Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -use crate::prelude::*; -use crate::{Address, EthSpec, ExecutionPayloadRef, Hash256, Hash64, Uint256}; - -/// Execution block header as used for RLP encoding and Keccak hashing. -/// -/// Credit to Reth for the type definition. -#[derive(Debug, Clone, PartialEq, Eq, Hash, ScaleDecode, ScaleEncode, TypeInfo)] -pub struct ExecutionBlockHeader { - pub parent_hash: Hash256, - pub ommers_hash: Hash256, - pub beneficiary: Address, - pub state_root: Hash256, - pub transactions_root: Hash256, - pub receipts_root: Hash256, - pub logs_bloom: Vec, - pub difficulty: Uint256, - pub number: Uint256, - pub gas_limit: Uint256, - pub gas_used: Uint256, - pub timestamp: u64, - pub extra_data: Vec, - pub mix_hash: Hash256, - pub nonce: Hash64, - pub base_fee_per_gas: Uint256, - pub withdrawals_root: Option, -} - -impl ExecutionBlockHeader { - pub fn from_payload( - payload: ExecutionPayloadRef, - rlp_empty_list_root: Hash256, - rlp_transactions_root: Hash256, - rlp_withdrawals_root: Option, - ) -> Self { - // Most of these field mappings are defined in EIP-3675 except for `mixHash`, which is - // defined in EIP-4399. - ExecutionBlockHeader { - parent_hash: payload.parent_hash().into_root(), - ommers_hash: rlp_empty_list_root, - beneficiary: payload.fee_recipient(), - state_root: payload.state_root(), - transactions_root: rlp_transactions_root, - receipts_root: payload.receipts_root(), - logs_bloom: payload.logs_bloom().clone().into(), - difficulty: Uint256::zero(), - number: payload.block_number().into(), - gas_limit: payload.gas_limit().into(), - gas_used: payload.gas_used().into(), - timestamp: payload.timestamp(), - extra_data: payload.extra_data().clone().into(), - mix_hash: payload.prev_randao(), - nonce: Hash64::zero(), - base_fee_per_gas: payload.base_fee_per_gas(), - withdrawals_root: rlp_withdrawals_root, - } - } -} diff --git a/primitives/beacon/src/fork_name.rs b/primitives/beacon/src/fork_name.rs index e640c22a..87be9635 100644 --- a/primitives/beacon/src/fork_name.rs +++ b/primitives/beacon/src/fork_name.rs @@ -1,6 +1,5 @@ #[cfg(not(feature = "std"))] use crate::prelude::*; -use crate::{ChainSpec, Epoch}; use core::convert::TryFrom; use core::fmt::{self, Display, Formatter}; use core::str::FromStr; @@ -26,38 +25,6 @@ impl ForkName { ] } - /// Set the activation slots in the given `ChainSpec` so that the fork named by `self` - /// is the only fork in effect from genesis. - pub fn make_genesis_spec(&self, mut spec: ChainSpec) -> ChainSpec { - // Assumes GENESIS_EPOCH = 0, which is safe because it's a constant. - match self { - ForkName::Base => { - spec.altair_fork_epoch = None; - spec.bellatrix_fork_epoch = None; - spec.capella_fork_epoch = None; - spec - } - ForkName::Altair => { - spec.altair_fork_epoch = Some(Epoch::new(0)); - spec.bellatrix_fork_epoch = None; - spec.capella_fork_epoch = None; - spec - } - ForkName::Merge => { - spec.altair_fork_epoch = Some(Epoch::new(0)); - spec.bellatrix_fork_epoch = Some(Epoch::new(0)); - spec.capella_fork_epoch = None; - spec - } - ForkName::Capella => { - spec.altair_fork_epoch = Some(Epoch::new(0)); - spec.bellatrix_fork_epoch = Some(Epoch::new(0)); - spec.capella_fork_epoch = Some(Epoch::new(0)); - spec - } - } - } - /// Return the name of the fork immediately prior to the current one. /// /// If `self` is `ForkName::Base` then `Base` is returned. diff --git a/primitives/beacon/src/historical_batch.rs b/primitives/beacon/src/historical_batch.rs deleted file mode 100644 index c8ac500b..00000000 --- a/primitives/beacon/src/historical_batch.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::*; - -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use ssz_types::FixedVector; -use tree_hash_derive::TreeHash; - -/// Historical block and state roots. -/// -/// Spec v0.12.1 -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[scale_info(skip_type_params(T))] -pub struct HistoricalBatch { - pub block_roots: FixedVector, - pub state_roots: FixedVector, -} diff --git a/primitives/beacon/src/historical_summary.rs b/primitives/beacon/src/historical_summary.rs deleted file mode 100644 index bf840703..00000000 --- a/primitives/beacon/src/historical_summary.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::prelude::*; -use crate::Hash256; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch` -/// making the two hash_tree_root-compatible. This struct is introduced into the beacon state -/// in the Capella hard fork. -/// -/// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary -#[derive( - Debug, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - Clone, - Copy, - Default, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct HistoricalSummary { - block_summary_root: Hash256, - state_summary_root: Hash256, -} diff --git a/primitives/beacon/src/lib.rs b/primitives/beacon/src/lib.rs index 58ccc9d3..82c37c10 100644 --- a/primitives/beacon/src/lib.rs +++ b/primitives/beacon/src/lib.rs @@ -27,39 +27,39 @@ mod prelude { pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; pub use core::fmt::Debug; pub use core::hash::Hash; + pub use derivative::Derivative; pub use scale_info::TypeInfo; + pub use ssz_derive::{Decode, Encode}; + pub use superstruct::superstruct; + pub use tree_hash::TreeHash; + pub use tree_hash_derive::TreeHash; } #[cfg(feature = "std")] mod prelude { pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; + pub use derivative::Derivative; pub use scale_info::TypeInfo; + pub use ssz_derive::{Decode, Encode}; pub use std::borrow::Cow; pub use std::collections::{BTreeMap, BTreeSet}; + pub use superstruct::superstruct; + pub use tree_hash::TreeHash; + pub use tree_hash_derive::TreeHash; } -pub mod aggregate_and_proof; -pub mod application_domain; pub mod attestation; pub mod attestation_data; -pub mod attestation_duty; pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; pub mod beacon_block_header; -pub mod beacon_committee; pub mod beacon_state; pub mod bls_to_execution_change; -pub mod builder_bid; -pub mod chain_spec; pub mod checkpoint; pub mod consts; -pub mod contribution_and_proof; pub mod deposit; pub mod deposit_data; -pub mod deposit_message; -pub mod deposit_tree_snapshot; -pub mod enr_fork_id; pub mod eth1_data; pub mod eth_spec; pub mod execution_block_hash; @@ -70,91 +70,54 @@ pub mod fork_data; pub mod fork_name; pub mod fork_versioned_response; pub mod graffiti; -pub mod historical_batch; -pub mod historical_summary; pub mod indexed_attestation; pub mod light_client_bootstrap; -pub mod light_client_finality_update; -pub mod light_client_optimistic_update; pub mod light_client_update; -pub mod pending_attestation; -pub mod proposer_preparation_data; pub mod proposer_slashing; -pub mod relative_epoch; -pub mod selection_proof; -pub mod shuffling_id; -pub mod signed_aggregate_and_proof; pub mod signed_beacon_block; pub mod signed_beacon_block_header; pub mod signed_bls_to_execution_change; -pub mod signed_contribution_and_proof; pub mod signed_voluntary_exit; pub mod signing_data; -pub mod sync_committee_subscription; -pub mod sync_duty; -pub mod validator; -pub mod validator_subscription; pub mod voluntary_exit; #[macro_use] pub mod slot_epoch_macros; -pub mod config_and_preset; -pub mod execution_block_header; pub mod int_to_bytes; -pub mod participation_flags; -pub mod participation_list; pub mod payload; -pub mod preset; pub mod slot_epoch; -pub mod subnet_id; pub mod sync_aggregate; -pub mod sync_aggregator_selection_data; pub mod sync_committee; -pub mod sync_committee_contribution; -pub mod sync_committee_message; -pub mod sync_selection_proof; -pub mod sync_subnet_id; -pub mod validator_registration_data; pub mod withdrawal; pub mod slot_data; +pub mod beacon_config; +pub mod light_client_header; pub mod safe_arith; use ethereum_types::{H160, H256}; -pub use crate::aggregate_and_proof::AggregateAndProof; pub use crate::attestation::{Attestation, Error as AttestationError}; pub use crate::attestation_data::AttestationData; -pub use crate::attestation_duty::AttestationDuty; pub use crate::attester_slashing::AttesterSlashing; pub use crate::beacon_block::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockMerge, - BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, EmptyBlock, + BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, }; pub use crate::beacon_block_body::{ BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyCapella, BeaconBlockBodyMerge, BeaconBlockBodyRef, BeaconBlockBodyRefMut, }; pub use crate::beacon_block_header::BeaconBlockHeader; -pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee}; pub use crate::beacon_state::{Error as BeaconStateError, *}; pub use crate::bls_to_execution_change::BlsToExecutionChange; -pub use crate::chain_spec::{ChainSpec, Config, Domain}; pub use crate::checkpoint::Checkpoint; -pub use crate::config_and_preset::{ - ConfigAndPreset, ConfigAndPresetBellatrix, ConfigAndPresetCapella, -}; -pub use crate::contribution_and_proof::ContributionAndProof; pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH}; pub use crate::deposit_data::DepositData; -pub use crate::deposit_message::DepositMessage; -pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock}; -pub use crate::enr_fork_id::EnrForkId; pub use crate::eth1_data::Eth1Data; pub use crate::eth_spec::EthSpecId; pub use crate::eth_spec::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; pub use crate::execution_block_hash::ExecutionBlockHash; -pub use crate::execution_block_header::ExecutionBlockHeader; pub use crate::execution_payload::{ ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge, ExecutionPayloadRef, Transaction, Transactions, Withdrawals, @@ -172,50 +135,30 @@ pub use crate::fork_versioned_response::{ ExecutionOptimisticForkVersionedResponse, ForkVersionedResponse, }; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; -pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; -pub use crate::light_client_finality_update::LightClientFinalityUpdate; -pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; -pub use crate::participation_flags::ParticipationFlags; -pub use crate::participation_list::ParticipationList; +pub use crate::light_client_header::{ + LightClientHeader, LightClientHeaderCapella, LightClientHeaderMerge, +}; pub use crate::payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadCapella, BlindedPayloadMerge, BlindedPayloadRef, BlockType, ExecPayload, FullPayload, FullPayloadCapella, FullPayloadMerge, FullPayloadRef, OwnedExecPayload, }; -pub use crate::pending_attestation::PendingAttestation; -pub use crate::preset::{AltairPreset, BasePreset, BellatrixPreset, CapellaPreset}; -pub use crate::proposer_preparation_data::ProposerPreparationData; pub use crate::proposer_slashing::ProposerSlashing; -pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; -pub use crate::selection_proof::SelectionProof; -pub use crate::shuffling_id::AttestationShufflingId; -pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof; pub use crate::signed_beacon_block::{ SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockCapella, SignedBeaconBlockHash, SignedBeaconBlockMerge, SignedBlindedBeaconBlock, }; pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader; pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; -pub use crate::signed_contribution_and_proof::SignedContributionAndProof; pub use crate::signed_voluntary_exit::SignedVoluntaryExit; pub use crate::signing_data::{SignedRoot, SigningData}; pub use crate::slot_epoch::{Epoch, Slot}; -pub use crate::subnet_id::SubnetId; pub use crate::sync_aggregate::SyncAggregate; -pub use crate::sync_aggregator_selection_data::SyncAggregatorSelectionData; pub use crate::sync_committee::SyncCommittee; -pub use crate::sync_committee_contribution::{SyncCommitteeContribution, SyncContributionData}; -pub use crate::sync_committee_message::SyncCommitteeMessage; -pub use crate::sync_committee_subscription::SyncCommitteeSubscription; -pub use crate::sync_duty::SyncDuty; -pub use crate::sync_selection_proof::SyncSelectionProof; -pub use crate::sync_subnet_id::SyncSubnetId; -pub use crate::validator::Validator; -pub use crate::validator_registration_data::*; -pub use crate::validator_subscription::ValidatorSubscription; pub use crate::voluntary_exit::VoluntaryExit; pub use crate::withdrawal::Withdrawal; +pub use beacon_config::{ConsensusConfig, ForkInfo, ForkSchedule}; pub type CommitteeIndex = u64; pub type Hash256 = H256; diff --git a/primitives/beacon/src/light_client_bootstrap.rs b/primitives/beacon/src/light_client_bootstrap.rs index 5401db83..a4145e55 100644 --- a/primitives/beacon/src/light_client_bootstrap.rs +++ b/primitives/beacon/src/light_client_bootstrap.rs @@ -1,31 +1,103 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, SyncCommittee}; +use super::{EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::light_client_header::LightClientHeaderRef; use crate::light_client_update::*; use crate::prelude::*; +use crate::LightClientHeaderCapella; +use crate::LightClientHeaderMerge; use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; /// A LightClientBootstrap is the initializer we send over to lightclient nodes /// that are trying to generate their basic storage when booting up. +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] #[derive( Debug, Clone, - PartialEq, Serialize, Deserialize, - Encode, - Decode, + Derivative, ScaleEncode, ScaleDecode, TypeInfo, MaxEncodedLen, )] -#[serde(bound = "T: EthSpec")] +#[derivative(PartialEq)] +#[serde(bound = "T: EthSpec", untagged)] #[scale_info(skip_type_params(T))] pub struct LightClientBootstrap { /// Requested beacon block header. - pub header: BeaconBlockHeader, + #[superstruct(only(Merge), partial_getter(rename = "header_merge"))] + pub header: LightClientHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "header_capella"))] + pub header: LightClientHeaderCapella, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: SyncCommittee, /// Merkle proof for sync committee pub current_sync_committee_branch: FixedVector, } + +impl LightClientBootstrap { + pub fn header(&self) -> LightClientHeaderRef { + match self { + Self::Merge(update) => LightClientHeaderRef::Merge(&update.header), + Self::Capella(update) => LightClientHeaderRef::Capella(&update.header), + } + } +} + +impl<'a, T: EthSpec> LightClientBootstrapRef<'a, T> { + pub fn owned(&self) -> LightClientBootstrap { + match *self { + Self::Merge(update) => LightClientBootstrap::Merge(update.clone()), + Self::Capella(update) => LightClientBootstrap::Capella(update.clone()), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientBootstrap { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/light_client_finality_update.rs b/primitives/beacon/src/light_client_finality_update.rs deleted file mode 100644 index 270440b3..00000000 --- a/primitives/beacon/src/light_client_finality_update.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; -use crate::light_client_update::*; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - -/// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that -/// signal a new finalized beacon block header for the light client sync protocol. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct LightClientFinalityUpdate { - /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, - /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, - /// Merkle proof attesting finalized header. - pub finality_branch: FixedVector, - /// current sync aggreggate - pub sync_aggregate: SyncAggregate, - /// Slot of the sync aggregated singature - pub signature_slot: Slot, -} diff --git a/primitives/beacon/src/light_client_header.rs b/primitives/beacon/src/light_client_header.rs new file mode 100644 index 00000000..6bcde9e0 --- /dev/null +++ b/primitives/beacon/src/light_client_header.rs @@ -0,0 +1,93 @@ +use core::marker::PhantomData; + +use super::BeaconBlockHeader; +use crate::{ + light_client_update::ExecutionPayloadProofLen, prelude::*, EthSpec, + ExecutionPayloadHeaderCapella, Hash256, +}; +use serde::{Deserialize, Serialize}; +use ssz_types::FixedVector; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[scale_info(skip_type_params(T))] +pub struct LightClientHeader { + pub beacon: BeaconBlockHeader, + #[superstruct(only(Capella))] + pub execution: ExecutionPayloadHeaderCapella, + #[superstruct(only(Capella))] + pub execution_branch: FixedVector, + #[superstruct(only(Merge))] + #[serde(skip)] + _phantom: PhantomData, +} + +impl<'a, T: EthSpec> LightClientHeaderRef<'a, T> { + pub fn owned(&self) -> LightClientHeader { + match *self { + Self::Merge(update) => LightClientHeader::Merge(update.clone()), + Self::Capella(update) => LightClientHeader::Capella(update.clone()), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientHeader { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/light_client_optimistic_update.rs b/primitives/beacon/src/light_client_optimistic_update.rs deleted file mode 100644 index 6623f9a8..00000000 --- a/primitives/beacon/src/light_client_optimistic_update.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - -/// A LightClientOptimisticUpdate is the update we send on each slot, -/// it is based off the current unfinalized epoch is verified only against BLS signature. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct LightClientOptimisticUpdate { - /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, - /// current sync aggreggate - pub sync_aggregate: SyncAggregate, - /// Slot of the sync aggregated singature - pub signature_slot: Slot, -} diff --git a/primitives/beacon/src/light_client_update.rs b/primitives/beacon/src/light_client_update.rs index b094bb4c..e043215a 100644 --- a/primitives/beacon/src/light_client_update.rs +++ b/primitives/beacon/src/light_client_update.rs @@ -1,22 +1,28 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; +use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::beacon_state; +use crate::light_client_header::LightClientHeaderRef; use crate::prelude::*; use crate::safe_arith::ArithError; +use crate::LightClientHeaderCapella; +use crate::LightClientHeaderMerge; use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::U4; use ssz_types::typenum::{U5, U6}; -pub const FINALIZED_ROOT_INDEX: usize = 105; -pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; -pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55; +pub const FINALIZED_ROOT_INDEX: usize = 41; +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 22; +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 23; +pub const EXECUTION_PAYLOAD_INDEX: usize = 9; pub type FinalizedRootProofLen = U6; pub type CurrentSyncCommitteeProofLen = U5; pub type NextSyncCommitteeProofLen = U5; +pub type ExecutionPayloadProofLen = U4; pub const FINALIZED_ROOT_PROOF_LEN: usize = 6; pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const EXECUTION_PAYLOAD_PROOF_LEN: usize = 4; #[derive(Debug, PartialEq, Clone)] pub enum Error { @@ -50,34 +56,125 @@ impl From for Error { /// A LightClientUpdate is the update we request solely to either complete the bootstraping process, /// or to sync up to the last committee period, we need to have one ready for each ALTAIR period /// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] #[derive( Debug, Clone, - PartialEq, Serialize, Deserialize, - Encode, - Decode, + Derivative, ScaleEncode, ScaleDecode, TypeInfo, MaxEncodedLen, )] -#[serde(bound = "T: EthSpec")] +#[derivative(PartialEq)] +#[serde(bound = "T: EthSpec", untagged)] #[scale_info(skip_type_params(T))] pub struct LightClientUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + #[superstruct(only(Merge), partial_getter(rename = "attested_header_merge"))] + pub attested_header: LightClientHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))] + pub attested_header: LightClientHeaderCapella, /// The `SyncCommittee` used in the next period. - pub next_sync_committee: SyncCommittee, + #[serde(default)] + pub next_sync_committee: Option>, /// Merkle proof for next sync committee - pub next_sync_committee_branch: FixedVector, + #[serde(default)] + pub next_sync_committee_branch: Option>, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + #[superstruct(only(Merge), partial_getter(rename = "finalized_header_merge"))] + #[serde(default)] + pub finalized_header: Option>, + #[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))] + #[serde(default)] + pub finalized_header: Option>, /// Merkle proof attesting finalized header. - pub finality_branch: FixedVector, + #[serde(default)] + pub finality_branch: Option>, /// current sync aggreggate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature + #[superstruct(getter(copy))] pub signature_slot: Slot, } + +impl LightClientUpdate { + pub fn is_sync_committee_update(&self) -> bool { + self.next_sync_committee().is_some() && self.next_sync_committee_branch().is_some() + } + + pub fn is_finality_update(&self) -> bool { + self.finalized_header().is_some() && self.finality_branch().is_some() + } + + pub fn finalized_header(&self) -> Option> { + match self { + Self::Merge(update) => update + .finalized_header + .as_ref() + .map(|x| LightClientHeaderRef::Merge(x)), + Self::Capella(update) => update + .finalized_header + .as_ref() + .map(|x| LightClientHeaderRef::Capella(x)), + } + } + + pub fn attested_header(&self) -> LightClientHeaderRef { + match self { + Self::Merge(update) => LightClientHeaderRef::Merge(&update.attested_header), + Self::Capella(update) => LightClientHeaderRef::Capella(&update.attested_header), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientUpdate { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/participation_flags.rs b/primitives/beacon/src/participation_flags.rs deleted file mode 100644 index 62094222..00000000 --- a/primitives/beacon/src/participation_flags.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::{consts::altair::NUM_FLAG_INDICES, Hash256}; -use serde::{Deserialize, Serialize}; -use ssz::{Decode, DecodeError, Encode}; -use tree_hash::{PackedEncoding, TreeHash, TreeHashType}; - -#[derive( - Debug, - Default, - Clone, - Copy, - PartialEq, - Deserialize, - Serialize, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(transparent)] -pub struct ParticipationFlags { - #[serde(with = "eth2_serde_utils::quoted_u8")] - bits: u8, -} - -impl ParticipationFlags { - pub fn add_flag(&mut self, flag_index: usize) -> Result<(), ArithError> { - if flag_index >= NUM_FLAG_INDICES { - return Err(ArithError::Overflow); - } - self.bits |= 1u8.safe_shl(flag_index as u32)?; - Ok(()) - } - - pub fn has_flag(&self, flag_index: usize) -> Result { - if flag_index >= NUM_FLAG_INDICES { - return Err(ArithError::Overflow); - } - let mask = 1u8.safe_shl(flag_index as u32)?; - Ok(self.bits & mask == mask) - } - - pub fn into_u8(self) -> u8 { - self.bits - } -} - -/// Decode implementation that transparently behaves like the inner `u8`. -impl Decode for ParticipationFlags { - fn is_ssz_fixed_len() -> bool { - ::is_ssz_fixed_len() - } - - fn ssz_fixed_len() -> usize { - ::ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - u8::from_ssz_bytes(bytes).map(|bits| Self { bits }) - } -} - -/// Encode implementation that transparently behaves like the inner `u8`. -impl Encode for ParticipationFlags { - fn is_ssz_fixed_len() -> bool { - ::is_ssz_fixed_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - self.bits.ssz_append(buf); - } - - fn ssz_fixed_len() -> usize { - ::ssz_fixed_len() - } - - fn ssz_bytes_len(&self) -> usize { - self.bits.ssz_bytes_len() - } - - fn as_ssz_bytes(&self) -> Vec { - self.bits.as_ssz_bytes() - } -} - -impl TreeHash for ParticipationFlags { - fn tree_hash_type() -> TreeHashType { - u8::tree_hash_type() - } - - fn tree_hash_packed_encoding(&self) -> PackedEncoding { - self.bits.tree_hash_packed_encoding() - } - - fn tree_hash_packing_factor() -> usize { - u8::tree_hash_packing_factor() - } - - fn tree_hash_root(&self) -> Hash256 { - self.bits.tree_hash_root() - } -} diff --git a/primitives/beacon/src/participation_list.rs b/primitives/beacon/src/participation_list.rs deleted file mode 100644 index 1145f584..00000000 --- a/primitives/beacon/src/participation_list.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![allow(clippy::integer_arithmetic)] - -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::{ParticipationFlags, Unsigned, VariableList}; - -/// Wrapper type allowing the implementation of `CachedTreeHash`. -#[derive(Debug)] -pub struct ParticipationList<'a, N: Unsigned> { - pub inner: &'a VariableList, -} - -impl<'a, N: Unsigned> ParticipationList<'a, N> { - pub fn new(inner: &'a VariableList) -> Self { - Self { inner } - } -} diff --git a/primitives/beacon/src/pending_attestation.rs b/primitives/beacon/src/pending_attestation.rs deleted file mode 100644 index 0c04f7f1..00000000 --- a/primitives/beacon/src/pending_attestation.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{AttestationData, BitList, EthSpec}; - -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// An attestation that has been included in the state but not yet fully processed. -/// -/// Spec v0.12.1 -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[scale_info(skip_type_params(T))] -pub struct PendingAttestation { - pub aggregation_bits: BitList, - pub data: AttestationData, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub inclusion_delay: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub proposer_index: u64, -} diff --git a/primitives/beacon/src/preset.rs b/primitives/beacon/src/preset.rs deleted file mode 100644 index fb527dcd..00000000 --- a/primitives/beacon/src/preset.rs +++ /dev/null @@ -1,208 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::{ChainSpec, Epoch, EthSpec, Unsigned}; -use serde::{Deserialize, Serialize}; - -/// Value-level representation of an Ethereum consensus "preset". -/// -/// This should only be used to check consistency of the compile-time constants -/// with a preset YAML file, or to make preset values available to the API. Prefer -/// the constants on `EthSpec` or the fields on `ChainSpec` to constructing and using -/// one of these structs. -/// -/// https://github.com/ethereum/eth2.0-specs/blob/dev/presets/mainnet/phase0.yaml -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub struct BasePreset { - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_committees_per_slot: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub target_committee_size: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_validators_per_committee: u64, - #[serde(with = "eth2_serde_utils::quoted_u8")] - pub shuffle_round_count: u8, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub hysteresis_quotient: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub hysteresis_downward_multiplier: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub hysteresis_upward_multiplier: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub safe_slots_to_update_justified: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_deposit_amount: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_effective_balance: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub effective_balance_increment: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_attestation_inclusion_delay: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub slots_per_epoch: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_seed_lookahead: Epoch, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_seed_lookahead: Epoch, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub epochs_per_eth1_voting_period: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub slots_per_historical_root: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_epochs_to_inactivity_penalty: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub epochs_per_historical_vector: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub epochs_per_slashings_vector: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub historical_roots_limit: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub validator_registry_limit: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub base_reward_factor: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub whistleblower_reward_quotient: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub proposer_reward_quotient: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub inactivity_penalty_quotient: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_slashing_penalty_quotient: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub proportional_slashing_multiplier: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_proposer_slashings: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_attester_slashings: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_attestations: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_deposits: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_voluntary_exits: u64, -} - -impl BasePreset { - pub fn from_chain_spec(spec: &ChainSpec) -> Self { - Self { - max_committees_per_slot: spec.max_committees_per_slot as u64, - target_committee_size: spec.target_committee_size as u64, - max_validators_per_committee: T::MaxValidatorsPerCommittee::to_u64(), - shuffle_round_count: spec.shuffle_round_count, - hysteresis_quotient: spec.hysteresis_quotient, - hysteresis_downward_multiplier: spec.hysteresis_downward_multiplier, - hysteresis_upward_multiplier: spec.hysteresis_upward_multiplier, - safe_slots_to_update_justified: spec.safe_slots_to_update_justified, - min_deposit_amount: spec.min_deposit_amount, - max_effective_balance: spec.max_effective_balance, - effective_balance_increment: spec.effective_balance_increment, - min_attestation_inclusion_delay: spec.min_attestation_inclusion_delay, - slots_per_epoch: T::SlotsPerEpoch::to_u64(), - min_seed_lookahead: spec.min_seed_lookahead, - max_seed_lookahead: spec.max_seed_lookahead, - epochs_per_eth1_voting_period: T::EpochsPerEth1VotingPeriod::to_u64(), - slots_per_historical_root: T::SlotsPerHistoricalRoot::to_u64(), - min_epochs_to_inactivity_penalty: spec.min_epochs_to_inactivity_penalty, - epochs_per_historical_vector: T::EpochsPerHistoricalVector::to_u64(), - epochs_per_slashings_vector: T::EpochsPerSlashingsVector::to_u64(), - historical_roots_limit: T::HistoricalRootsLimit::to_u64(), - validator_registry_limit: T::ValidatorRegistryLimit::to_u64(), - base_reward_factor: spec.base_reward_factor, - whistleblower_reward_quotient: spec.whistleblower_reward_quotient, - proposer_reward_quotient: spec.proposer_reward_quotient, - inactivity_penalty_quotient: spec.inactivity_penalty_quotient, - min_slashing_penalty_quotient: spec.min_slashing_penalty_quotient, - proportional_slashing_multiplier: spec.proportional_slashing_multiplier, - max_proposer_slashings: T::MaxProposerSlashings::to_u64(), - max_attester_slashings: T::MaxAttesterSlashings::to_u64(), - max_attestations: T::MaxAttestations::to_u64(), - max_deposits: T::MaxDeposits::to_u64(), - max_voluntary_exits: T::MaxVoluntaryExits::to_u64(), - } - } -} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub struct AltairPreset { - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub inactivity_penalty_quotient_altair: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_slashing_penalty_quotient_altair: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub proportional_slashing_multiplier_altair: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub sync_committee_size: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub epochs_per_sync_committee_period: Epoch, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_sync_committee_participants: u64, -} - -impl AltairPreset { - pub fn from_chain_spec(spec: &ChainSpec) -> Self { - Self { - inactivity_penalty_quotient_altair: spec.inactivity_penalty_quotient_altair, - min_slashing_penalty_quotient_altair: spec.min_slashing_penalty_quotient_altair, - proportional_slashing_multiplier_altair: spec.proportional_slashing_multiplier_altair, - sync_committee_size: T::SyncCommitteeSize::to_u64(), - epochs_per_sync_committee_period: spec.epochs_per_sync_committee_period, - min_sync_committee_participants: spec.min_sync_committee_participants, - } - } -} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub struct BellatrixPreset { - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub inactivity_penalty_quotient_bellatrix: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub min_slashing_penalty_quotient_bellatrix: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub proportional_slashing_multiplier_bellatrix: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_bytes_per_transaction: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_transactions_per_payload: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub bytes_per_logs_bloom: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_extra_data_bytes: u64, -} - -impl BellatrixPreset { - pub fn from_chain_spec(spec: &ChainSpec) -> Self { - Self { - inactivity_penalty_quotient_bellatrix: spec.inactivity_penalty_quotient_bellatrix, - min_slashing_penalty_quotient_bellatrix: spec.min_slashing_penalty_quotient_bellatrix, - proportional_slashing_multiplier_bellatrix: spec - .proportional_slashing_multiplier_bellatrix, - max_bytes_per_transaction: T::max_bytes_per_transaction() as u64, - max_transactions_per_payload: T::max_transactions_per_payload() as u64, - bytes_per_logs_bloom: T::bytes_per_logs_bloom() as u64, - max_extra_data_bytes: T::max_extra_data_bytes() as u64, - } - } -} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub struct CapellaPreset { - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_bls_to_execution_changes: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_withdrawals_per_payload: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub max_validators_per_withdrawals_sweep: u64, -} - -impl CapellaPreset { - pub fn from_chain_spec(spec: &ChainSpec) -> Self { - Self { - max_bls_to_execution_changes: T::max_bls_to_execution_changes() as u64, - max_withdrawals_per_payload: T::max_withdrawals_per_payload() as u64, - max_validators_per_withdrawals_sweep: spec.max_validators_per_withdrawals_sweep, - } - } -} diff --git a/primitives/beacon/src/proposer_preparation_data.rs b/primitives/beacon/src/proposer_preparation_data.rs deleted file mode 100644 index c02d4874..00000000 --- a/primitives/beacon/src/proposer_preparation_data.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::*; -use serde::{Deserialize, Serialize}; - -/// A proposer preparation, created when a validator prepares the beacon node for potential proposers -/// by supplying information required when proposing blocks for the given validators. -#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] -pub struct ProposerPreparationData { - /// The validators index. - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub validator_index: u64, - /// The fee-recipient address. - pub fee_recipient: Address, -} diff --git a/primitives/beacon/src/relative_epoch.rs b/primitives/beacon/src/relative_epoch.rs deleted file mode 100644 index 6ec52fbc..00000000 --- a/primitives/beacon/src/relative_epoch.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::*; - -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum Error { - EpochTooLow { base: Epoch, other: Epoch }, - EpochTooHigh { base: Epoch, other: Epoch }, - ArithError(ArithError), -} - -impl From for Error { - fn from(e: ArithError) -> Self { - Self::ArithError(e) - } -} - -/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior -/// to and following some epoch. -/// -/// Spec v0.12.1 -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum RelativeEpoch { - /// The prior epoch. - Previous, - /// The current epoch. - Current, - /// The next epoch. - Next, -} - -impl RelativeEpoch { - /// Returns the `epoch` that `self` refers to, with respect to the `base` epoch. - /// - /// Spec v0.12.1 - pub fn into_epoch(self, base: Epoch) -> Epoch { - match self { - // Due to saturating nature of epoch, check for current first. - RelativeEpoch::Current => base, - RelativeEpoch::Previous => base.saturating_sub(1u64), - RelativeEpoch::Next => base.saturating_add(1u64), - } - } - - /// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base` - /// - /// ## Errors - /// Returns an error when: - /// - `EpochTooLow` when `other` is more than 1 prior to `base`. - /// - `EpochTooHigh` when `other` is more than 1 after `base`. - /// - /// Spec v0.12.1 - pub fn from_epoch(base: Epoch, other: Epoch) -> Result { - if other == base { - Ok(RelativeEpoch::Current) - } else if other.safe_add(1)? == base { - Ok(RelativeEpoch::Previous) - } else if other == base.safe_add(1)? { - Ok(RelativeEpoch::Next) - } else if other < base { - Err(Error::EpochTooLow { base, other }) - } else { - Err(Error::EpochTooHigh { base, other }) - } - } - - /// Convenience function for `Self::from_epoch` where both slots are converted into epochs. - pub fn from_slot(base: Slot, other: Slot, slots_per_epoch: u64) -> Result { - Self::from_epoch(base.epoch(slots_per_epoch), other.epoch(slots_per_epoch)) - } -} diff --git a/primitives/beacon/src/selection_proof.rs b/primitives/beacon/src/selection_proof.rs deleted file mode 100644 index 34a7814b..00000000 --- a/primitives/beacon/src/selection_proof.rs +++ /dev/null @@ -1,93 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::{ - ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot, -}; -use core::cmp; -use core::convert::TryInto; -use eth2_hashing::hash; -use ssz::Encode; - -#[derive(PartialEq, Debug, Clone)] -pub struct SelectionProof(Signature); - -impl SelectionProof { - pub fn new( - slot: Slot, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let domain = spec.get_domain( - slot.epoch(T::slots_per_epoch()), - Domain::SelectionProof, - fork, - genesis_validators_root, - ); - let message = slot.signing_root(domain); - - Self(secret_key.sign(message)) - } - - /// Returns the "modulo" used for determining if a `SelectionProof` elects an aggregator. - pub fn modulo(committee_len: usize, spec: &ChainSpec) -> Result { - Ok(cmp::max( - 1, - (committee_len as u64).safe_div(spec.target_aggregators_per_committee)?, - )) - } - - pub fn is_aggregator( - &self, - committee_len: usize, - spec: &ChainSpec, - ) -> Result { - self.is_aggregator_from_modulo(Self::modulo(committee_len, spec)?) - } - - pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result { - let signature_hash = hash(&self.0.as_ssz_bytes()); - let signature_hash_int = u64::from_le_bytes( - signature_hash - .get(0..8) - .expect("hash is 32 bytes") - .try_into() - .expect("first 8 bytes of signature should always convert to fixed array"), - ); - - signature_hash_int.safe_rem(modulo).map(|rem| rem == 0) - } - - pub fn verify( - &self, - slot: Slot, - pubkey: &PublicKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> bool { - let domain = spec.get_domain( - slot.epoch(T::slots_per_epoch()), - Domain::SelectionProof, - fork, - genesis_validators_root, - ); - let message = slot.signing_root(domain); - - self.0.verify(pubkey, message) - } -} - -impl Into for SelectionProof { - fn into(self) -> Signature { - self.0 - } -} - -impl From for SelectionProof { - fn from(sig: Signature) -> Self { - Self(sig) - } -} diff --git a/primitives/beacon/src/shuffling_id.rs b/primitives/beacon/src/shuffling_id.rs deleted file mode 100644 index a757224f..00000000 --- a/primitives/beacon/src/shuffling_id.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::prelude::*; -use crate::*; -use core::hash::Hash; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - -/// Can be used to key (ID) the shuffling in some chain, in some epoch. -/// -/// ## Reasoning -/// -/// We say that the ID of some shuffling is always equal to a 2-tuple: -/// -/// - The epoch for which the shuffling should be effective. -/// - A block root, where this is the root at the *last* slot of the penultimate epoch. I.e., the -/// final block which contributed a randao reveal to the seed for the shuffling. -/// -/// The struct stores exactly that 2-tuple. -#[derive( - Debug, - PartialEq, - Eq, - Clone, - Hash, - Serialize, - Deserialize, - Encode, - Decode, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct AttestationShufflingId { - pub shuffling_epoch: Epoch, - pub shuffling_decision_block: Hash256, -} - -impl AttestationShufflingId { - pub fn from_components(shuffling_epoch: Epoch, shuffling_decision_block: Hash256) -> Self { - Self { - shuffling_epoch, - shuffling_decision_block, - } - } -} diff --git a/primitives/beacon/src/signed_aggregate_and_proof.rs b/primitives/beacon/src/signed_aggregate_and_proof.rs deleted file mode 100644 index 42fe6d87..00000000 --- a/primitives/beacon/src/signed_aggregate_and_proof.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::{ - AggregateAndProof, Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, - SelectionProof, Signature, SignedRoot, -}; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// A Validators signed aggregate proof to publish on the `beacon_aggregate_and_proof` -/// gossipsub topic. -/// -/// Spec v0.12.1 -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct SignedAggregateAndProof { - /// The `AggregateAndProof` that was signed. - pub message: AggregateAndProof, - /// The aggregate attestation. - pub signature: Signature, -} - -impl SignedAggregateAndProof { - /// Produces a new `SignedAggregateAndProof` with a `selection_proof` generated by signing - /// `aggregate.data.slot` with `secret_key`. - /// - /// If `selection_proof.is_none()` it will be computed locally. - pub fn from_aggregate( - aggregator_index: u64, - aggregate: Attestation, - selection_proof: Option, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let message = AggregateAndProof::from_aggregate( - aggregator_index, - aggregate, - selection_proof, - secret_key, - fork, - genesis_validators_root, - spec, - ); - - let target_epoch = message.aggregate.data.slot.epoch(T::slots_per_epoch()); - let domain = spec.get_domain( - target_epoch, - Domain::AggregateAndProof, - fork, - genesis_validators_root, - ); - let signing_message = message.signing_root(domain); - - SignedAggregateAndProof { - message, - signature: secret_key.sign(signing_message), - } - } -} diff --git a/primitives/beacon/src/signed_beacon_block.rs b/primitives/beacon/src/signed_beacon_block.rs index d4f73838..0de9167a 100644 --- a/primitives/beacon/src/signed_beacon_block.rs +++ b/primitives/beacon/src/signed_beacon_block.rs @@ -94,19 +94,6 @@ pub struct SignedBeaconBlock = FullP pub type SignedBlindedBeaconBlock = SignedBeaconBlock>; impl> SignedBeaconBlock { - /// Returns the name of the fork pertaining to `self`. - /// - /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork - /// dictated by `self.slot()`. - pub fn fork_name(&self, spec: &ChainSpec) -> Result { - self.message().fork_name(spec) - } - - /// SSZ decode with fork variant determined by slot. - pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { - Self::from_ssz_bytes_with(bytes, |bytes| BeaconBlock::from_ssz_bytes(bytes, spec)) - } - /// SSZ decode which attempts to decode all variants (slow). pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { Self::from_ssz_bytes_with(bytes, BeaconBlock::any_from_ssz_bytes) @@ -179,44 +166,6 @@ impl> SignedBeaconBlock ) } - /// Verify `self.signature`. - /// - /// If the root of `block.message` is already known it can be passed in via `object_root_opt`. - /// Otherwise, it will be computed locally. - pub fn verify_signature( - &self, - object_root_opt: Option, - pubkey: &PublicKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> bool { - // Refuse to verify the signature of a block if its structure does not match the fork at - // `self.slot()`. - if self.fork_name(spec).is_err() { - return false; - } - - let domain = spec.get_domain( - self.slot().epoch(E::slots_per_epoch()), - Domain::BeaconProposer, - fork, - genesis_validators_root, - ); - - let message = if let Some(object_root) = object_root_opt { - SigningData { - object_root, - domain, - } - .tree_hash_root() - } else { - self.message().signing_root(domain) - }; - - self.signature().verify(pubkey, message) - } - /// Produce a signed beacon block header corresponding to this block. pub fn signed_block_header(&self) -> SignedBeaconBlockHeader { SignedBeaconBlockHeader { diff --git a/primitives/beacon/src/signed_beacon_block_header.rs b/primitives/beacon/src/signed_beacon_block_header.rs index d69a86f1..bd4f8602 100644 --- a/primitives/beacon/src/signed_beacon_block_header.rs +++ b/primitives/beacon/src/signed_beacon_block_header.rs @@ -1,7 +1,5 @@ use crate::prelude::*; -use crate::{ - BeaconBlockHeader, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, Signature, SignedRoot, -}; +use crate::{BeaconBlockHeader, Signature}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; @@ -29,25 +27,3 @@ pub struct SignedBeaconBlockHeader { pub message: BeaconBlockHeader, pub signature: Signature, } - -impl SignedBeaconBlockHeader { - /// Verify that this block header was signed by `pubkey`. - pub fn verify_signature( - &self, - pubkey: &PublicKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> bool { - let domain = spec.get_domain( - self.message.slot.epoch(E::slots_per_epoch()), - Domain::BeaconProposer, - fork, - genesis_validators_root, - ); - - let message = self.message.signing_root(domain); - - self.signature.verify(pubkey, message) - } -} diff --git a/primitives/beacon/src/signed_contribution_and_proof.rs b/primitives/beacon/src/signed_contribution_and_proof.rs deleted file mode 100644 index 8d37a019..00000000 --- a/primitives/beacon/src/signed_contribution_and_proof.rs +++ /dev/null @@ -1,73 +0,0 @@ -use super::{ - ChainSpec, ContributionAndProof, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, - SignedRoot, SyncCommitteeContribution, SyncSelectionProof, -}; -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// A Validators signed contribution proof to publish on the `sync_committee_contribution_and_proof` -/// gossipsub topic. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct SignedContributionAndProof { - /// The `ContributionAndProof` that was signed. - pub message: ContributionAndProof, - /// The validator's signature of `message`. - pub signature: Signature, -} - -impl SignedContributionAndProof { - /// Produces a new `SignedContributionAndProof` with a `selection_proof` generated by signing - /// `aggregate.data.slot` with `secret_key`. - /// - /// If `selection_proof.is_none()` it will be computed locally. - pub fn from_aggregate( - aggregator_index: u64, - contribution: SyncCommitteeContribution, - selection_proof: Option, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let message = ContributionAndProof::from_aggregate( - aggregator_index, - contribution, - selection_proof, - secret_key, - fork, - genesis_validators_root, - spec, - ); - - let epoch = message.contribution.slot.epoch(T::slots_per_epoch()); - let domain = spec.get_domain( - epoch, - Domain::ContributionAndProof, - fork, - genesis_validators_root, - ); - let signing_message = message.signing_root(domain); - - SignedContributionAndProof { - message, - signature: secret_key.sign(signing_message), - } - } -} diff --git a/primitives/beacon/src/slot_epoch.rs b/primitives/beacon/src/slot_epoch.rs index 6ede2a75..55d4f0b5 100644 --- a/primitives/beacon/src/slot_epoch.rs +++ b/primitives/beacon/src/slot_epoch.rs @@ -79,6 +79,12 @@ impl Slot { self.epoch(T::slots_per_epoch()) } + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period_with_spec(&self) -> u64 { + self.epoch_with_spec::() + .sync_committee_period_with_spec::() + } + pub fn max_value() -> Slot { Slot(u64::max_value()) } @@ -130,10 +136,10 @@ impl Epoch { } /// Compute the sync committee period for an epoch. - pub fn sync_committee_period_with_spec(&self) -> Result { - Ok(self - .safe_div(Epoch::new(T::EpochsPerSyncCommitteePeriod::to_u64()))? - .as_u64()) + pub fn sync_committee_period_with_spec(&self) -> u64 { + self.safe_div(Epoch::new(T::EpochsPerSyncCommitteePeriod::to_u64())) + .expect("epoch per sync committee period is not 0") + .as_u64() } pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter { diff --git a/primitives/beacon/src/subnet_id.rs b/primitives/beacon/src/subnet_id.rs deleted file mode 100644 index c423d534..00000000 --- a/primitives/beacon/src/subnet_id.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! Identifies each shard by an integer identifier. -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::{AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot}; -use core::ops::{Deref, DerefMut}; -use serde::{Deserialize, Serialize}; - -const MAX_SUBNET_ID: usize = 64; - -lazy_static::lazy_static! { - static ref SUBNET_ID_TO_STRING: Vec = { - let mut v = Vec::with_capacity(MAX_SUBNET_ID); - - for i in 0..MAX_SUBNET_ID { - v.push(i.to_string()); - } - v - }; -} - -#[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - Hash, - Serialize, - Deserialize, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(transparent)] -pub struct SubnetId(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); - -pub fn subnet_id_to_string(i: u64) -> &'static str { - if i < MAX_SUBNET_ID as u64 { - SUBNET_ID_TO_STRING - .get(i as usize) - .expect("index below MAX_SUBNET_ID") - } else { - "subnet id out of range" - } -} - -impl SubnetId { - pub fn new(id: u64) -> Self { - id.into() - } - - /// Compute the subnet for an attestation with `attestation_data` where each slot in the - /// attestation epoch contains `committee_count_per_slot` committees. - pub fn compute_subnet_for_attestation_data( - attestation_data: &AttestationData, - committee_count_per_slot: u64, - spec: &ChainSpec, - ) -> Result { - Self::compute_subnet::( - attestation_data.slot, - attestation_data.index, - committee_count_per_slot, - spec, - ) - } - - /// Compute the subnet for an attestation with `attestation.data.slot == slot` and - /// `attestation.data.index == committee_index` where each slot in the attestation epoch - /// contains `committee_count_at_slot` committees. - pub fn compute_subnet( - slot: Slot, - committee_index: CommitteeIndex, - committee_count_at_slot: u64, - spec: &ChainSpec, - ) -> Result { - let slots_since_epoch_start: u64 = slot.as_u64().safe_rem(T::slots_per_epoch())?; - - let committees_since_epoch_start = - committee_count_at_slot.safe_mul(slots_since_epoch_start)?; - - Ok(committees_since_epoch_start - .safe_add(committee_index)? - .safe_rem(spec.attestation_subnet_count)? - .into()) - } -} - -impl Deref for SubnetId { - type Target = u64; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SubnetId { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for SubnetId { - fn from(x: u64) -> Self { - Self(x) - } -} - -impl Into for SubnetId { - fn into(self) -> u64 { - self.0 - } -} - -impl Into for &SubnetId { - fn into(self) -> u64 { - self.0 - } -} - -impl AsRef for SubnetId { - fn as_ref(&self) -> &str { - subnet_id_to_string(self.0) - } -} diff --git a/primitives/beacon/src/sync_aggregate.rs b/primitives/beacon/src/sync_aggregate.rs index 861f60b6..fdd0360a 100644 --- a/primitives/beacon/src/sync_aggregate.rs +++ b/primitives/beacon/src/sync_aggregate.rs @@ -1,7 +1,6 @@ -use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::{AggregateSignature, BitVector, EthSpec, SyncCommitteeContribution}; +use crate::safe_arith::ArithError; +use crate::{AggregateSignature, BitVector, EthSpec}; use derivative::Derivative; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -51,34 +50,6 @@ impl SyncAggregate { } } - /// Create a `SyncAggregate` from a slice of `SyncCommitteeContribution`s. - /// - /// Equivalent to `process_sync_committee_contributions` from the spec. - pub fn from_contributions( - contributions: &[SyncCommitteeContribution], - ) -> Result, Error> { - let mut sync_aggregate = Self::new(); - let sync_subcommittee_size = - T::sync_committee_size().safe_div(SYNC_COMMITTEE_SUBNET_COUNT as usize)?; - for contribution in contributions { - for (index, participated) in contribution.aggregation_bits.iter().enumerate() { - if participated { - let participant_index = sync_subcommittee_size - .safe_mul(contribution.subcommittee_index as usize)? - .safe_add(index)?; - sync_aggregate - .sync_committee_bits - .set(participant_index, true) - .map_err(Error::SszTypesError)?; - } - } - sync_aggregate - .sync_committee_signature - .add_assign_aggregate(&contribution.signature); - } - Ok(sync_aggregate) - } - /// Empty aggregate to be used at genesis. /// /// Contains an empty signature and should *not* be used as the starting point for aggregation, diff --git a/primitives/beacon/src/sync_aggregator_selection_data.rs b/primitives/beacon/src/sync_aggregator_selection_data.rs deleted file mode 100644 index 8b6e6c7e..00000000 --- a/primitives/beacon/src/sync_aggregator_selection_data.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{SignedRoot, Slot}; - -use crate::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -#[derive( - Debug, - PartialEq, - Clone, - Serialize, - Deserialize, - Hash, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct SyncAggregatorSelectionData { - pub slot: Slot, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub subcommittee_index: u64, -} - -impl SignedRoot for SyncAggregatorSelectionData {} diff --git a/primitives/beacon/src/sync_committee.rs b/primitives/beacon/src/sync_committee.rs index 1c9f7a9b..8d094434 100644 --- a/primitives/beacon/src/sync_committee.rs +++ b/primitives/beacon/src/sync_committee.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::safe_arith::{ArithError, SafeArith}; use crate::typenum::Unsigned; -use crate::{EthSpec, FixedVector, SyncSubnetId}; +use crate::{EthSpec, FixedVector}; use bls::PublicKeyBytes; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -74,28 +74,6 @@ impl SyncCommittee { .map(|s| s.to_vec()) } - /// For a given `pubkey`, finds all subcommittees that it is included in, and maps the - /// subcommittee index (typed as `SyncSubnetId`) to all positions this `pubkey` is associated - /// with within the subcommittee. - pub fn subcommittee_positions_for_public_key( - &self, - pubkey: &PublicKeyBytes, - ) -> Result>, Error> { - let mut subnet_positions = BTreeMap::new(); - for (committee_index, validator_pubkey) in self.pubkeys.iter().enumerate() { - if pubkey == validator_pubkey { - let subcommittee_index = committee_index.safe_div(T::sync_subcommittee_size())?; - let position_in_subcommittee = - committee_index.safe_rem(T::sync_subcommittee_size())?; - subnet_positions - .entry(SyncSubnetId::new(subcommittee_index as u64)) - .or_insert_with(Vec::new) - .push(position_in_subcommittee); - } - } - Ok(subnet_positions) - } - /// Returns `true` if the pubkey exists in the `SyncCommittee`. pub fn contains(&self, pubkey: &PublicKeyBytes) -> bool { self.pubkeys.contains(pubkey) diff --git a/primitives/beacon/src/sync_committee_contribution.rs b/primitives/beacon/src/sync_committee_contribution.rs deleted file mode 100644 index f4081bd5..00000000 --- a/primitives/beacon/src/sync_committee_contribution.rs +++ /dev/null @@ -1,119 +0,0 @@ -use super::{AggregateSignature, EthSpec, SignedRoot}; -use crate::prelude::*; -use crate::safe_arith::ArithError; -use crate::slot_data::SlotData; -use crate::{BitVector, Hash256, Slot, SyncCommitteeMessage}; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -#[derive(Debug, PartialEq)] -pub enum Error { - SszTypesError(ssz_types::Error), - AlreadySigned(usize), - SubnetCountIsZero(ArithError), -} - -/// An aggregation of `SyncCommitteeMessage`s, used in creating a `SignedContributionAndProof`. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -#[serde(bound = "T: EthSpec")] -#[scale_info(skip_type_params(T))] -pub struct SyncCommitteeContribution { - pub slot: Slot, - pub beacon_block_root: Hash256, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub subcommittee_index: u64, - pub aggregation_bits: BitVector, - pub signature: AggregateSignature, -} - -impl SyncCommitteeContribution { - /// Create a `SyncCommitteeContribution` from: - /// - /// - `message`: A single `SyncCommitteeMessage`. - /// - `subcommittee_index`: The subcommittee this contribution pertains to out of the broader - /// sync committee. This can be determined from the `SyncSubnetId` of the gossip subnet - /// this message was seen on. - /// - `validator_sync_committee_index`: The index of the validator **within** the subcommittee. - pub fn from_message( - message: &SyncCommitteeMessage, - subcommittee_index: u64, - validator_sync_committee_index: usize, - ) -> Result { - let mut bits = BitVector::new(); - bits.set(validator_sync_committee_index, true) - .map_err(Error::SszTypesError)?; - Ok(Self { - slot: message.slot, - beacon_block_root: message.beacon_block_root, - subcommittee_index, - aggregation_bits: bits, - signature: AggregateSignature::from(&message.signature), - }) - } - - /// Are the aggregation bitfields of these sync contribution disjoint? - pub fn signers_disjoint_from(&self, other: &Self) -> bool { - self.aggregation_bits - .intersection(&other.aggregation_bits) - .is_zero() - } - - /// Aggregate another `SyncCommitteeContribution` into this one. - /// - /// The aggregation bitfields must be disjoint, and the data must be the same. - pub fn aggregate(&mut self, other: &Self) { - debug_assert_eq!(self.slot, other.slot); - debug_assert_eq!(self.beacon_block_root, other.beacon_block_root); - debug_assert_eq!(self.subcommittee_index, other.subcommittee_index); - debug_assert!(self.signers_disjoint_from(other)); - - self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); - self.signature.add_assign_aggregate(&other.signature); - } -} - -impl SignedRoot for Hash256 {} - -/// This is not in the spec, but useful for determining uniqueness of sync committee contributions -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash)] -pub struct SyncContributionData { - pub slot: Slot, - pub beacon_block_root: Hash256, - pub subcommittee_index: u64, -} - -impl SyncContributionData { - pub fn from_contribution(signing_data: &SyncCommitteeContribution) -> Self { - Self { - slot: signing_data.slot, - beacon_block_root: signing_data.beacon_block_root, - subcommittee_index: signing_data.subcommittee_index, - } - } -} - -impl SlotData for SyncCommitteeContribution { - fn get_slot(&self) -> Slot { - self.slot - } -} - -impl SlotData for SyncContributionData { - fn get_slot(&self) -> Slot { - self.slot - } -} diff --git a/primitives/beacon/src/sync_committee_message.rs b/primitives/beacon/src/sync_committee_message.rs deleted file mode 100644 index 7a0c52f8..00000000 --- a/primitives/beacon/src/sync_committee_message.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, Slot}; - -use crate::prelude::*; -use crate::slot_data::SlotData; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// The data upon which a `SyncCommitteeContribution` is based. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct SyncCommitteeMessage { - pub slot: Slot, - pub beacon_block_root: Hash256, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub validator_index: u64, - // Signature by the validator over `beacon_block_root`. - pub signature: Signature, -} - -impl SyncCommitteeMessage { - /// Equivalent to `get_sync_committee_message` from the spec. - pub fn new( - slot: Slot, - beacon_block_root: Hash256, - validator_index: u64, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let epoch = slot.epoch(E::slots_per_epoch()); - let domain = spec.get_domain(epoch, Domain::SyncCommittee, fork, genesis_validators_root); - let message = beacon_block_root.signing_root(domain); - let signature = secret_key.sign(message); - Self { - slot, - beacon_block_root, - validator_index, - signature, - } - } -} - -impl SlotData for SyncCommitteeMessage { - fn get_slot(&self) -> Slot { - self.slot - } -} diff --git a/primitives/beacon/src/sync_committee_subscription.rs b/primitives/beacon/src/sync_committee_subscription.rs deleted file mode 100644 index 708b4f9f..00000000 --- a/primitives/beacon/src/sync_committee_subscription.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::prelude::*; -use crate::Epoch; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - -/// A sync committee subscription created when a validator subscribes to sync committee subnets to perform -/// sync committee duties. -#[derive( - PartialEq, - Debug, - Serialize, - Deserialize, - Clone, - Encode, - Decode, - ScaleEncode, - ScaleDecode, - TypeInfo, -)] -pub struct SyncCommitteeSubscription { - /// The validators index. - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub validator_index: u64, - /// The sync committee indices. - #[serde(with = "eth2_serde_utils::quoted_u64_vec")] - pub sync_committee_indices: Vec, - /// Epoch until which this subscription is required. - pub until_epoch: Epoch, -} diff --git a/primitives/beacon/src/sync_duty.rs b/primitives/beacon/src/sync_duty.rs deleted file mode 100644 index d47ef9bd..00000000 --- a/primitives/beacon/src/sync_duty.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::prelude::*; -use crate::safe_arith::ArithError; -use crate::{EthSpec, SyncCommittee, SyncSubnetId}; -use bls::PublicKeyBytes; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SyncDuty { - pub pubkey: PublicKeyBytes, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub validator_index: u64, - #[serde(with = "eth2_serde_utils::quoted_u64_vec")] - pub validator_sync_committee_indices: Vec, -} - -impl SyncDuty { - /// Create a new `SyncDuty` from the list of validator indices in a sync committee. - pub fn from_sync_committee_indices( - validator_index: u64, - pubkey: PublicKeyBytes, - sync_committee_indices: &[usize], - ) -> Option { - // Positions of the `validator_index` within the committee. - let validator_sync_committee_indices = sync_committee_indices - .iter() - .enumerate() - .filter_map(|(i, &v)| { - if validator_index == v as u64 { - Some(i as u64) - } else { - None - } - }) - .collect(); - Self::new(validator_index, pubkey, validator_sync_committee_indices) - } - - /// Create a new `SyncDuty` from a `SyncCommittee`, which contains the pubkeys but not the - /// indices. - pub fn from_sync_committee( - validator_index: u64, - pubkey: PublicKeyBytes, - sync_committee: &SyncCommittee, - ) -> Option { - let validator_sync_committee_indices = sync_committee - .pubkeys - .iter() - .enumerate() - .filter_map(|(i, committee_pubkey)| { - if &pubkey == committee_pubkey { - Some(i as u64) - } else { - None - } - }) - .collect(); - Self::new(validator_index, pubkey, validator_sync_committee_indices) - } - - /// Create a duty if the `validator_sync_committee_indices` is non-empty. - fn new( - validator_index: u64, - pubkey: PublicKeyBytes, - validator_sync_committee_indices: Vec, - ) -> Option { - if !validator_sync_committee_indices.is_empty() { - Some(SyncDuty { - pubkey, - validator_index, - validator_sync_committee_indices, - }) - } else { - None - } - } - - /// Get the set of subnet IDs for this duty. - pub fn subnet_ids(&self) -> Result, ArithError> { - SyncSubnetId::compute_subnets_for_sync_committee::( - &self.validator_sync_committee_indices, - ) - } -} diff --git a/primitives/beacon/src/sync_selection_proof.rs b/primitives/beacon/src/sync_selection_proof.rs deleted file mode 100644 index ca8ab465..00000000 --- a/primitives/beacon/src/sync_selection_proof.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::consts::altair::{ - SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, -}; -#[cfg(not(feature = "std"))] -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::{ - ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot, - SyncAggregatorSelectionData, -}; -use core::cmp; -use core::convert::TryInto; -use eth2_hashing::hash; -use ssz::Encode; -use ssz_types::typenum::Unsigned; - -#[derive(PartialEq, Debug, Clone)] -pub struct SyncSelectionProof(Signature); - -impl SyncSelectionProof { - pub fn new( - slot: Slot, - subcommittee_index: u64, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Self { - let domain = spec.get_domain( - slot.epoch(T::slots_per_epoch()), - Domain::SyncCommitteeSelectionProof, - fork, - genesis_validators_root, - ); - let message = SyncAggregatorSelectionData { - slot, - subcommittee_index, - } - .signing_root(domain); - - Self(secret_key.sign(message)) - } - - /// Returns the "modulo" used for determining if a `SyncSelectionProof` elects an aggregator. - pub fn modulo() -> Result { - Ok(cmp::max( - 1, - (T::SyncCommitteeSize::to_u64()) - .safe_div(SYNC_COMMITTEE_SUBNET_COUNT)? - .safe_div(TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE)?, - )) - } - - pub fn is_aggregator(&self) -> Result { - self.is_aggregator_from_modulo(Self::modulo::()?) - } - - pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result { - let signature_hash = hash(&self.0.as_ssz_bytes()); - let signature_hash_int = u64::from_le_bytes( - signature_hash - .get(0..8) - .expect("hash is 32 bytes") - .try_into() - .expect("first 8 bytes of signature should always convert to fixed array"), - ); - - signature_hash_int.safe_rem(modulo).map(|rem| rem == 0) - } - - pub fn verify( - &self, - slot: Slot, - subcommittee_index: u64, - pubkey: &PublicKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> bool { - let domain = spec.get_domain( - slot.epoch(T::slots_per_epoch()), - Domain::SyncCommitteeSelectionProof, - fork, - genesis_validators_root, - ); - let message = SyncAggregatorSelectionData { - slot, - subcommittee_index, - } - .signing_root(domain); - - self.0.verify(pubkey, message) - } -} - -impl Into for SyncSelectionProof { - fn into(self) -> Signature { - self.0 - } -} - -impl From for SyncSelectionProof { - fn from(sig: Signature) -> Self { - Self(sig) - } -} diff --git a/primitives/beacon/src/sync_subnet_id.rs b/primitives/beacon/src/sync_subnet_id.rs deleted file mode 100644 index 4df5610f..00000000 --- a/primitives/beacon/src/sync_subnet_id.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Identifies each sync committee subnet by an integer identifier. -use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; -use crate::prelude::*; -use crate::safe_arith::{ArithError, SafeArith}; -use crate::EthSpec; -use core::fmt::{self, Display}; -use core::ops::{Deref, DerefMut}; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use ssz_types::typenum::Unsigned; - -lazy_static! { - static ref SYNC_SUBNET_ID_TO_STRING: Vec = { - let mut v = Vec::with_capacity(SYNC_COMMITTEE_SUBNET_COUNT as usize); - - for i in 0..SYNC_COMMITTEE_SUBNET_COUNT { - v.push(i.to_string()); - } - v - }; -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] -#[serde(transparent)] -pub struct SyncSubnetId(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); - -pub fn sync_subnet_id_to_string(i: u64) -> &'static str { - if i < SYNC_COMMITTEE_SUBNET_COUNT { - SYNC_SUBNET_ID_TO_STRING - .get(i as usize) - .expect("index below SYNC_COMMITTEE_SUBNET_COUNT") - } else { - "sync subnet id out of range" - } -} - -impl SyncSubnetId { - pub fn new(id: u64) -> Self { - id.into() - } - - /// Compute required subnets to subscribe to given the sync committee indices. - pub fn compute_subnets_for_sync_committee( - sync_committee_indices: &[u64], - ) -> Result, ArithError> { - let subcommittee_size = T::SyncSubcommitteeSize::to_u64(); - - sync_committee_indices - .iter() - .map(|index| index.safe_div(subcommittee_size).map(Self::new)) - .collect() - } -} - -impl Display for SyncSubnetId { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{}", self.0) - } -} - -impl Deref for SyncSubnetId { - type Target = u64; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SyncSubnetId { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for SyncSubnetId { - fn from(x: u64) -> Self { - Self(x) - } -} - -impl Into for SyncSubnetId { - fn into(self) -> u64 { - self.0 - } -} - -impl Into for &SyncSubnetId { - fn into(self) -> u64 { - self.0 - } -} - -impl AsRef for SyncSubnetId { - fn as_ref(&self) -> &str { - sync_subnet_id_to_string(self.0) - } -} diff --git a/primitives/beacon/src/validator.rs b/primitives/beacon/src/validator.rs deleted file mode 100644 index 805321d0..00000000 --- a/primitives/beacon/src/validator.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::prelude::*; -use crate::{Address, ChainSpec, Epoch, Hash256, PublicKeyBytes}; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// Information about a `BeaconChain` validator. -/// -/// Spec v0.12.1 -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct Validator { - pub pubkey: PublicKeyBytes, - pub withdrawal_credentials: Hash256, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub effective_balance: u64, - pub slashed: bool, - pub activation_eligibility_epoch: Epoch, - pub activation_epoch: Epoch, - pub exit_epoch: Epoch, - pub withdrawable_epoch: Epoch, -} - -impl Validator { - /// Returns `true` if the validator is considered active at some epoch. - pub fn is_active_at(&self, epoch: Epoch) -> bool { - self.activation_epoch <= epoch && epoch < self.exit_epoch - } - - /// Returns `true` if the validator is slashable at some epoch. - pub fn is_slashable_at(&self, epoch: Epoch) -> bool { - !self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch - } - - /// Returns `true` if the validator is considered exited at some epoch. - pub fn is_exited_at(&self, epoch: Epoch) -> bool { - self.exit_epoch <= epoch - } - - /// Returns `true` if the validator is able to withdraw at some epoch. - pub fn is_withdrawable_at(&self, epoch: Epoch) -> bool { - epoch >= self.withdrawable_epoch - } - - /// Returns `true` if the validator is eligible to join the activation queue. - /// - /// Spec v0.12.1 - pub fn is_eligible_for_activation_queue(&self, spec: &ChainSpec) -> bool { - self.activation_eligibility_epoch == spec.far_future_epoch - && self.effective_balance == spec.max_effective_balance - } - - /// Returns `true` if the validator has eth1 withdrawal credential. - pub fn has_eth1_withdrawal_credential(&self, spec: &ChainSpec) -> bool { - self.withdrawal_credentials - .as_bytes() - .first() - .map(|byte| *byte == spec.eth1_address_withdrawal_prefix_byte) - .unwrap_or(false) - } - - /// Get the eth1 withdrawal address if this validator has one initialized. - pub fn get_eth1_withdrawal_address(&self, spec: &ChainSpec) -> Option
{ - self.has_eth1_withdrawal_credential(spec) - .then(|| { - self.withdrawal_credentials - .as_bytes() - .get(12..) - .map(Address::from_slice) - }) - .flatten() - } - - /// Changes withdrawal credentials to the provided eth1 execution address. - /// - /// WARNING: this function does NO VALIDATION - it just does it! - pub fn change_withdrawal_credentials(&mut self, execution_address: &Address, spec: &ChainSpec) { - let mut bytes = [0u8; 32]; - bytes[0] = spec.eth1_address_withdrawal_prefix_byte; - bytes[12..].copy_from_slice(execution_address.as_bytes()); - self.withdrawal_credentials = Hash256::from(bytes); - } - - /// Returns `true` if the validator is fully withdrawable at some epoch. - pub fn is_fully_withdrawable_at(&self, balance: u64, epoch: Epoch, spec: &ChainSpec) -> bool { - self.has_eth1_withdrawal_credential(spec) && self.withdrawable_epoch <= epoch && balance > 0 - } - - /// Returns `true` if the validator is partially withdrawable. - pub fn is_partially_withdrawable_validator(&self, balance: u64, spec: &ChainSpec) -> bool { - self.has_eth1_withdrawal_credential(spec) - && self.effective_balance == spec.max_effective_balance - && balance > spec.max_effective_balance - } -} - -impl Default for Validator { - /// Yields a "default" `Validator`. Primarily used for testing. - fn default() -> Self { - Self { - pubkey: PublicKeyBytes::empty(), - withdrawal_credentials: Hash256::default(), - activation_eligibility_epoch: Epoch::from(core::u64::MAX), - activation_epoch: Epoch::from(core::u64::MAX), - exit_epoch: Epoch::from(core::u64::MAX), - withdrawable_epoch: Epoch::from(core::u64::MAX), - slashed: false, - effective_balance: core::u64::MAX, - } - } -} diff --git a/primitives/beacon/src/validator_registration_data.rs b/primitives/beacon/src/validator_registration_data.rs deleted file mode 100644 index 40b8de64..00000000 --- a/primitives/beacon/src/validator_registration_data.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::prelude::*; -use crate::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash_derive::TreeHash; - -/// Validator registration, for use in interacting with servers implementing the builder API. -#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] -pub struct SignedValidatorRegistrationData { - pub message: ValidatorRegistrationData, - pub signature: Signature, -} - -#[derive( - PartialEq, - Debug, - Serialize, - Deserialize, - Clone, - Encode, - Decode, - TreeHash, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct ValidatorRegistrationData { - pub fee_recipient: Address, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub gas_limit: u64, - #[serde(with = "eth2_serde_utils::quoted_u64")] - pub timestamp: u64, - pub pubkey: PublicKeyBytes, -} - -impl SignedRoot for ValidatorRegistrationData {} diff --git a/primitives/beacon/src/validator_subscription.rs b/primitives/beacon/src/validator_subscription.rs deleted file mode 100644 index 610fe6fc..00000000 --- a/primitives/beacon/src/validator_subscription.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::prelude::*; -use crate::*; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - -/// A validator subscription, created when a validator subscribes to a slot to perform optional aggregation -/// duties. -#[derive( - PartialEq, - Debug, - Serialize, - Deserialize, - Clone, - Encode, - Decode, - ScaleEncode, - ScaleDecode, - TypeInfo, - MaxEncodedLen, -)] -pub struct ValidatorSubscription { - /// The validators index. - pub validator_index: u64, - /// The index of the committee within `slot` of which the validator is a member. Used by the - /// beacon node to quickly evaluate the associated `SubnetId`. - pub attestation_committee_index: CommitteeIndex, - /// The slot in which to subscribe. - pub slot: Slot, - /// Committee count at slot to subscribe. - pub committee_count_at_slot: u64, - /// If true, the validator is an aggregator and the beacon node should aggregate attestations - /// for this slot. - pub is_aggregator: bool, -} diff --git a/primitives/beacon/src/voluntary_exit.rs b/primitives/beacon/src/voluntary_exit.rs index ebbdab17..c54f1c50 100644 --- a/primitives/beacon/src/voluntary_exit.rs +++ b/primitives/beacon/src/voluntary_exit.rs @@ -1,4 +1,4 @@ -use crate::{ChainSpec, Domain, Epoch, Fork, Hash256, SecretKey, SignedRoot, SignedVoluntaryExit}; +use crate::{Epoch, SignedRoot}; use crate::prelude::*; use serde::{Deserialize, Serialize}; @@ -31,25 +31,3 @@ pub struct VoluntaryExit { } impl SignedRoot for VoluntaryExit {} - -impl VoluntaryExit { - pub fn sign( - self, - secret_key: &SecretKey, - fork: &Fork, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> SignedVoluntaryExit { - let domain = spec.get_domain( - self.epoch, - Domain::VoluntaryExit, - fork, - genesis_validators_root, - ); - let message = self.signing_root(domain); - SignedVoluntaryExit { - message: self, - signature: secret_key.sign(message), - } - } -} diff --git a/primitives/ethereum/Cargo.toml b/primitives/ethereum/Cargo.toml index 5e7720a7..ea8aa602 100644 --- a/primitives/ethereum/Cargo.toml +++ b/primitives/ethereum/Cargo.toml @@ -36,6 +36,8 @@ ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0 ethash = { git = "https://github.com/sora-xor/ethash.git", branch = "sora-v1.6.0", default-features = false } serde_json = { version = "1.0", optional = true } +beacon = {path = "../beacon", default-features = false} + [dev-dependencies] wasm-bindgen-test = "0.3.19" rand = "0.7.3" @@ -61,6 +63,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "beacon/std" ] runtime-benchmarks = ["frame-support/runtime-benchmarks"] diff --git a/primitives/ethereum/src/lib.rs b/primitives/ethereum/src/lib.rs index de6b15e5..651d29e0 100644 --- a/primitives/ethereum/src/lib.rs +++ b/primitives/ethereum/src/lib.rs @@ -1,6 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod beacon; pub mod difficulty; pub mod ethashdata; pub mod ethashproof; diff --git a/primitives/ethereum/src/network_config.rs b/primitives/ethereum/src/network_config.rs index 62433921..381fcaa0 100644 --- a/primitives/ethereum/src/network_config.rs +++ b/primitives/ethereum/src/network_config.rs @@ -29,7 +29,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - beacon::BeaconConsensusConfig, difficulty::{ClassicForkConfig, ForkConfig}, EVMChainId, }; @@ -47,7 +46,7 @@ pub enum Consensus { Ethash { fork_config: ForkConfig }, Etchash { fork_config: ClassicForkConfig }, Clique { period: u64, epoch: u64 }, - Beacon(BeaconConsensusConfig), + Beacon(beacon::ConsensusConfig), } impl Consensus { @@ -71,7 +70,6 @@ pub enum NetworkConfig { Goerli, Classic, Mordor, - Local, Custom { chain_id: EVMChainId, consensus: Consensus, @@ -93,7 +91,6 @@ impl NetworkConfig { NetworkConfig::Goerli => 5u32.into(), NetworkConfig::Classic => 61u32.into(), NetworkConfig::Mordor => 63u32.into(), - NetworkConfig::Local => 4224u32.into(), NetworkConfig::Custom { chain_id, .. } => *chain_id, #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] NetworkConfig::RopstenEthash => 3u32.into(), @@ -106,10 +103,9 @@ impl NetworkConfig { pub fn consensus(&self) -> Consensus { match self { - NetworkConfig::Mainnet => Consensus::Beacon(BeaconConsensusConfig::mainnet()), - NetworkConfig::Goerli => Consensus::Beacon(BeaconConsensusConfig::goerli()), - NetworkConfig::Sepolia => Consensus::Beacon(BeaconConsensusConfig::sepolia()), - NetworkConfig::Local => Consensus::Beacon(BeaconConsensusConfig::local()), + NetworkConfig::Mainnet => Consensus::Beacon(beacon::ConsensusConfig::mainnet()), + NetworkConfig::Goerli => Consensus::Beacon(beacon::ConsensusConfig::goerli()), + NetworkConfig::Sepolia => Consensus::Beacon(beacon::ConsensusConfig::sepolia()), NetworkConfig::Classic => Consensus::Etchash { fork_config: ClassicForkConfig::classic(), }, diff --git a/primitives/ethereum/src/serde_utils.rs b/primitives/ethereum/src/serde_utils.rs index d54cc30e..e5f22fc7 100644 --- a/primitives/ethereum/src/serde_utils.rs +++ b/primitives/ethereum/src/serde_utils.rs @@ -1,32 +1,3 @@ -pub mod serde_fork_version { - use serde::Deserialize; - - use crate::beacon::ForkVersion; - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?; - let value = if value.len() > 1 && value[0..2] == *"0x" { - &value[2..] - } else { - &value - }; - let res = hex::decode(value).map_err(|err| { - serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) - })?; - let res = res.try_into().map_err(|err| { - serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) - })?; - Ok(res) - } - - pub fn serialize(value: &[u8], serializer: S) -> Result { - serializer.serialize_str(&format!("0x{}", hex::encode(value))) - } -} - pub mod serde_str { use core::str::FromStr; use serde::Deserialize;