From ae15e9bd41ac767d3875fa2c1ad9597e6861181b Mon Sep 17 00:00:00 2001 From: mksh Date: Sat, 24 Aug 2024 00:20:31 +0300 Subject: [PATCH] Use single library for SSZ, bls-to-execution-change and presigned-exit-message (#54) * Make bls-to-execution-change command work * [STACKED] Command to generate presigned exit message (#55) * Command to generate presigned exit message * Docs for presigned-exit-message * Code review feedback * Add support for sending signed payloads to beacon node * Add note on backwards compatibility policy * Unify regex usage * Adjustments for single payload cmds - Fix presigned-exit-message format & beacon node sending - Rename Operator traits -> validator - Rename operator module -> operations * Fix README --- Cargo.lock | 1100 +++++++++++++-------- Cargo.toml | 11 +- README.md | 65 +- src/beacon_node.rs | 44 + src/bls_to_execution_change.rs | 388 -------- src/bls_to_execution_change/mod.rs | 56 ++ src/bls_to_execution_change/operations.rs | 85 ++ src/bls_to_execution_change/test.rs | 56 ++ src/chain_spec.rs | 31 +- src/cli/bls_to_execution_change.rs | 88 +- src/cli/mod.rs | 1 + src/cli/presigned_exit_message.rs | 112 +++ src/deposit.rs | 4 +- src/key_material.rs | 2 +- src/lib.rs | 7 +- src/main.rs | 28 +- src/networks.rs | 46 + src/utils.rs | 25 +- src/validators.rs | 17 +- src/voluntary_exit/mod.rs | 37 + src/voluntary_exit/operations.rs | 53 + src/voluntary_exit/test.rs | 24 + tests/e2e/bls_to_execution_change.rs | 107 +- tests/e2e/mod.rs | 1 + tests/e2e/new_mnemonic.rs | 14 +- tests/e2e/presigned_exit_message.rs | 151 +++ 26 files changed, 1673 insertions(+), 880 deletions(-) create mode 100644 src/beacon_node.rs delete mode 100644 src/bls_to_execution_change.rs create mode 100644 src/bls_to_execution_change/mod.rs create mode 100644 src/bls_to_execution_change/operations.rs create mode 100644 src/bls_to_execution_change/test.rs create mode 100644 src/cli/presigned_exit_message.rs create mode 100644 src/voluntary_exit/mod.rs create mode 100644 src/voluntary_exit/operations.rs create mode 100644 src/voluntary_exit/test.rs create mode 100644 tests/e2e/presigned_exit_message.rs diff --git a/Cargo.lock b/Cargo.lock index 906e8c9..f7d0460 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,37 +97,6 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "alloy-primitives" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c234f92024707f224510ff82419b2be0e1d8e1fd911defcac5a085cd7f83898" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more", - "hex-literal", - "itoa", - "keccak-asm", - "proptest", - "rand", - "ruint", - "serde", - "tiny-keccak", -] - -[[package]] -name = "alloy-rlp" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" -dependencies = [ - "arrayvec", - "bytes", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -214,173 +183,273 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "ark-ff" -version = "0.3.0" +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", + "term", ] [[package]] -name = "ark-ff" -version = "0.4.2" +name = "asn1_der" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest 0.10.7", - "itertools", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.4.0", - "zeroize", + "serde", + "serde_json", ] [[package]] -name = "ark-ff-asm" -version = "0.3.0" +name = "assert_cmd" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ - "quote", - "syn 1.0.109", + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] [[package]] -name = "ark-ff-asm" -version = "0.4.2" +name = "async-attributes" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", "syn 1.0.109", ] [[package]] -name = "ark-ff-macros" -version = "0.3.0" +name = "async-channel" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", ] [[package]] -name = "ark-ff-macros" -version = "0.4.2" +name = "async-channel" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "ark-serialize" -version = "0.3.0" +name = "async-executor" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", ] [[package]] -name = "ark-serialize" -version = "0.4.2" +name = "async-global-executor" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.4", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", ] [[package]] -name = "ark-std" -version = "0.3.0" +name = "async-io" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "num-traits", - "rand", + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", ] [[package]] -name = "ark-std" -version = "0.4.0" +name = "async-io" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "num-traits", - "rand", + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.3", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.59.0", ] [[package]] -name = "arrayref" -version = "0.3.8" +name = "async-lock" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] [[package]] -name = "arrayvec" -version = "0.7.4" +name = "async-lock" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] [[package]] -name = "asn1_der" -version = "0.7.6" +name = "async-object-pool" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +dependencies = [ + "async-std", +] [[package]] -name = "assert_cmd" -version = "2.0.15" +name = "async-process" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc65048dd435533bb1baf2ed9956b9a278fbfdcf90301b39ee117f06c0199d37" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ - "anstyle", - "bstr", - "doc-comment", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.34", + "windows-sys 0.48.0", ] [[package]] -name = "auto_impl" -version = "1.2.0" +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io 2.3.4", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -432,6 +501,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "bindgen" version = "0.66.1" @@ -451,7 +531,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.75", "which", ] @@ -512,6 +592,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "bls" version = "0.2.0" @@ -585,9 +678,6 @@ name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" -dependencies = [ - "serde", -] [[package]] name = "bzip2" @@ -640,12 +730,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.7" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -707,9 +798,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.13" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -717,9 +808,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -736,7 +827,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -756,7 +847,7 @@ name = "compare_fields" version = "0.2.0" source = "git+https://github.com/ChorusOne/lighthouse?rev=1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d#1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d" dependencies = [ - "itertools", + "itertools 0.10.5", ] [[package]] @@ -769,16 +860,12 @@ dependencies = [ ] [[package]] -name = "const-hex" -version = "1.12.0" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "proptest", - "serde", + "crossbeam-utils", ] [[package]] @@ -793,12 +880,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.4" @@ -811,9 +892,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -826,9 +907,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -944,7 +1025,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.0", + "rustc_version", "subtle", "zeroize", ] @@ -957,7 +1038,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1075,20 +1156,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", -] - -[[package]] -name = "derive_more" -version = "0.99.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1245,6 +1313,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1332,25 +1409,27 @@ dependencies = [ "eth2_keystore", "eth2_network_config", "eth2_wallet", - "ethereum-types", "ethereum_hashing 0.6.0", "ethereum_ssz", + "ethereum_ssz_derive", "getrandom", "hex", + "httpmock", "lazy_static", "log", "predicates", "pretty_assertions", "regex", + "reqwest", "serde", "serde_derive", "serde_json", - "ssz_rs", - "ssz_rs_derive", "test-log", "tiny-bip39", "tree_hash", + "tree_hash_derive", "types", + "url", "uuid 1.10.0", ] @@ -1519,7 +1598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" dependencies = [ "ethereum-types", - "itertools", + "itertools 0.10.5", "smallvec", ] @@ -1535,6 +1614,44 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1549,20 +1666,18 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.1.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] [[package]] -name = "fastrlp" -version = "0.3.1" +name = "fastrand" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" @@ -1587,7 +1702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -1603,6 +1718,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.31" @@ -1707,6 +1828,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1715,7 +1864,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1798,6 +1947,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.13.0" @@ -1866,19 +2027,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "hex" -version = "0.4.3" +name = "hermit-abi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] -name = "hex-literal" -version = "0.4.1" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" @@ -1972,6 +2130,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "humantime" version = "2.1.0" @@ -2108,9 +2294,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -2139,7 +2325,18 @@ name = "int_to_bytes" version = "0.2.0" source = "git+https://github.com/ChorusOne/lighthouse?rev=1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d#1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d" dependencies = [ - "bytes", + "bytes", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", ] [[package]] @@ -2150,11 +2347,11 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -2174,6 +2371,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2191,9 +2397,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -2222,13 +2428,12 @@ dependencies = [ ] [[package]] -name = "keccak-asm" -version = "0.1.3" +name = "kv-log-macro" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "digest 0.10.7", - "sha3-asm", + "log", ] [[package]] @@ -2248,6 +2453,37 @@ dependencies = [ "tree_hash", ] +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.4", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.7", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2263,11 +2499,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libflate" @@ -2498,6 +2740,12 @@ dependencies = [ "prometheus", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2519,6 +2767,9 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] [[package]] name = "logging" @@ -2610,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a991d4536c933306e52f0e8ab303757185ec13a09d1f3e1cbde5a0d8410bf" dependencies = [ "darling", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "smallvec", @@ -2640,11 +2891,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -2726,6 +2977,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nom" version = "7.1.3" @@ -2813,7 +3070,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2822,15 +3078,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" -version = "0.36.2" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -2870,7 +3126,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2933,6 +3189,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -3032,16 +3294,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pest" -version = "2.7.11" +name = "petgraph" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", + "fixedbitset", + "indexmap", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -3059,7 +3335,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3074,6 +3350,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -3090,6 +3377,37 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "polyval" version = "0.5.3" @@ -3117,6 +3435,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "3.1.2" @@ -3173,7 +3497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3222,38 +3546,12 @@ dependencies = [ "thiserror", ] -[[package]] -name = "proptest" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.6.0", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax 0.8.4", - "rusty-fork", - "tempfile", - "unarray", -] - [[package]] name = "protobuf" version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-protobuf" version = "0.8.1" @@ -3512,36 +3810,6 @@ dependencies = [ "rustc-hex", ] -[[package]] -name = "ruint" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" -dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "bytes", - "fastrlp", - "num-bigint", - "num-traits", - "parity-scale-codec", - "primitive-types", - "proptest", - "rand", - "rlp", - "ruint-macro", - "serde", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - [[package]] name = "rusqlite" version = "0.28.0" @@ -3576,20 +3844,25 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.11.0", + "semver", ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustix" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ - "semver 1.0.23", + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", ] [[package]] @@ -3601,7 +3874,7 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] @@ -3642,18 +3915,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -3684,6 +3945,15 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -3758,30 +4028,12 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "sensitive_url" version = "0.1.0" @@ -3793,29 +4045,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -3823,6 +4075,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -3831,7 +4093,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3904,16 +4166,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "sha3-asm" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" -dependencies = [ - "cc", - "cfg-if", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -3948,6 +4200,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -4107,29 +4371,6 @@ dependencies = [ "der", ] -[[package]] -name = "ssz_rs" -version = "0.9.0" -source = "git+https://github.com/ChorusOne/ssz-rs.git?rev=1f94d5dfc70c86dab672e91ac46af04a5f96c342#1f94d5dfc70c86dab672e91ac46af04a5f96c342" -dependencies = [ - "alloy-primitives", - "bitvec", - "hex", - "serde", - "sha2 0.9.9", - "ssz_rs_derive", -] - -[[package]] -name = "ssz_rs_derive" -version = "0.9.0" -source = "git+https://github.com/ChorusOne/ssz-rs.git?rev=1f94d5dfc70c86dab672e91ac46af04a5f96c342#1f94d5dfc70c86dab672e91ac46af04a5f96c342" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "ssz_types" version = "0.5.4" @@ -4140,7 +4381,7 @@ dependencies = [ "derivative", "ethereum_serde_utils", "ethereum_ssz", - "itertools", + "itertools 0.10.5", "serde", "serde_derive", "smallvec", @@ -4154,6 +4395,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.3", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.10.0" @@ -4201,7 +4455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b9e5728aa1a87141cefd4e7509903fc01fa0dcb108022b1e841a67c5159fc5" dependencies = [ "darling", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "smallvec", @@ -4230,9 +4484,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -4285,9 +4539,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.1.0", "once_cell", - "rustix", + "rustix 0.38.34", "windows-sys 0.59.0", ] @@ -4327,7 +4581,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4356,7 +4610,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4454,9 +4708,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", @@ -4477,7 +4731,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4533,9 +4787,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -4569,7 +4823,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4683,7 +4937,7 @@ dependencies = [ "ethereum_ssz_derive", "hex", "int_to_bytes", - "itertools", + "itertools 0.10.5", "kzg", "lazy_static", "log", @@ -4712,12 +4966,6 @@ dependencies = [ "tree_hash_derive", ] -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "uint" version = "0.9.5" @@ -4731,12 +4979,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4758,6 +5000,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + [[package]] name = "universal-hash" version = "0.4.1" @@ -4840,6 +5088,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4867,6 +5121,22 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4884,34 +5154,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -4921,9 +5192,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4931,22 +5202,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" @@ -4963,9 +5234,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -4986,7 +5257,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.34", ] [[package]] @@ -5011,6 +5282,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5238,7 +5518,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -5258,7 +5538,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 25978f4..5edce20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,24 +18,26 @@ ethereum_hashing = "0.6.0" eth2_key_derivation = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d"} eth2_keystore = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d"} eth2_network_config = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d" } -ethereum_ssz = "0.5.3" +ethereum_ssz = "0.5.4" +ethereum_ssz_derive = "0.5.4" eth2_wallet = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d"} -ethereum-types = { version = "0.14.1", optional = true } env_logger = "^0.11" hex = "0.4" lazy_static = "1.5" log = "^0.4" +# eth2_network_config uses native-tls, so do we +reqwest = { version = "0.11", default-features = false, features = ["native-tls"] } getrandom = "0.2" regex = "1.10.6" serde = "1.0.204" serde_derive = "1.0" serde_json = "1.0" -ssz_rs = { git = "https://github.com/ChorusOne/ssz-rs.git", rev = "1f94d5dfc70c86dab672e91ac46af04a5f96c342" } -ssz_rs_derive = { git = "https://github.com/ChorusOne/ssz-rs.git", rev = "1f94d5dfc70c86dab672e91ac46af04a5f96c342" } tiny-bip39 = "1.0.0" # This must be pinned to a version that lighthouse uses tree_hash = "0.5.2" +tree_hash_derive = "0.5.2" types = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d"} +url = "2.5" uuid = { version = "1.10", features = ["v4"] } [dev-dependencies] @@ -43,6 +45,7 @@ test-log = "^0.2" pretty_assertions = "^1.4" assert_cmd = "2.0" predicates = "3.0" +httpmock = "0.7" [[test]] name = "e2e-tests" diff --git a/README.md b/README.md index f3a13e5..ff41f7f 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,49 @@ You can use `eth-staking-smith` as follows to convert your address: ### Command to generate SignedBLSToExecutionChange ``` -./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" +./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" \ --execution_address "0x71C7656EC7ab88b098defB751B7401B5f6d8976F" ``` Note that --validator-index and --validator-start-index are two distinct parameter, the former being index of validator on Beacon chain, and the latter is the index of validator private key derived from the seed + +### Command to send SignedBLSToExecutionChange request to Beacon node + +``` +./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" \ +--execution_address "0x71C7656EC7ab88b098defB751B7401B5f6d8976F" \ +--beacon-node-uri http://beacon-node.local:5052 +``` + +Notice `--beacon-node-uri` parameter which makes payload to be sent to beacon node + +## Generating pre-signed exit message + +It is possible to create pre-signed voluntary exit message for every validator that +is generated from some known mnemonic, given the minimum epoch for exit to trigger. + +Use `eth-staking-smith` via command line like: + +### Command to generate presigned exit message + +``` +./target/debug/eth-staking-smith presigned-exit-message --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_seed_index 0 --validator_beacon_index 100 --epoch 300000 +``` + +Note that --validator-beacon-index and --validator-seed-index are two distinct parameter, the former being index of validator on Beacon chain, and the latter is the index of validator private key derived from the seed + + +### Command to send VoluntaryExitMessage request to Beacon node + +``` +./target/debug/eth-staking-smith presigned-exit-message --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_seed_index 0 --validator_beacon_index 100 --epoch 300000 \ +--beacon-node-uri http://beacon-node.local:5052 +``` + +Notice `--beacon-node-uri` parameter which makes payload to be sent to beacon node + + ## Exporting CLI standard output into common keystores folder format Most validator clients recognize the keystore folder format, @@ -117,18 +154,6 @@ lighthouse account validator import \ --directory validator_keys/ --password-file ./password.txt ``` -### Command to send SignedBLSToExecutionChange request to Beacon node - -``` -curl -H "Content-Type: application/json" -d '{ - "message": { - "validator_index": 100, - "from_bls_pubkey": "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d", - "to_execution_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F" - }, - "signature": "0x9220e5badefdfe8abc36cae01af29b981edeb940ff88c438f72c8af876fbd6416138c85f5348c5ace92a081fa15291aa0ffb856141b871dc807f3ec2fe9c8415cac3d76579c61455ab3938bc162e139d060c8aa13fcd670febe46bf0bb579c5a" -}' http://localhost:3500/eth/v1/beacon/pool/bls_to_execution_change -``` # Implementation Details To avoid heavy lifting, we're interfacing [Lighthouse account manager](https://github.com/sigp/lighthouse/blob/stable/account_manager), but optimizing it in a way so all operations are done in memory and key material is never written to filesystem during the generation to cater for our use case. @@ -163,3 +188,17 @@ To test our code e2e, we've generated files using the [staking deposit cli v2.3. | seed | secret value used to derive HD wallet addresses from a mnemonic phrase (BIP39 standard) | | withdrawal credentials | Withdrawal Credentials is a 32-byte field in the deposit data, for verifying the destination of valid withdrawals. Currently, there are two types of withdrawals: BLS withdrawal (with a 00 prefix) and Ethereum withdrawals (with a 01 prefix). By default the former will be generated, however Ethereum is planning to fully move to 01 credentials once withdrawals become available | | withdrawal address | Address for which withdrawal credentials should be generated. Eth staking smith allows execution addresses with the format `^(0x[a-fA-F0-9]{40})$` | + + +## Backwards compatibility +This project aims to present state-of-art Ethereum staking experience, +and does not follow semver approach for new releases. +Instead, backwards compatibility is provided on best-effort basis for both +library and command line interfaces, and every release that adds new +functionality can be treated as major release. + +Interfaces may change as result of implementing new features, and/or +backwards incompatible changes in Ethereum protocol. + +It is recommended to pin release version for users of command line interface, +and pin specific git commit of `eth-staking-smith` for library interface users. diff --git a/src/beacon_node.rs b/src/beacon_node.rs new file mode 100644 index 0000000..fc1020f --- /dev/null +++ b/src/beacon_node.rs @@ -0,0 +1,44 @@ +#[derive(Debug)] +pub enum BeaconNodeError { + InvalidBeaconNodeURI, + ClientConfigurationError, + NodeCommunicationError, + Non200Response, +} + +/// A trait for types that can be sent to beacon node as-is +/// without transformations +pub trait BeaconNodeExportable { + /// Export an entity as JSON + fn export(&self) -> serde_json::Value; + + /// The path at beacon node where to send data + fn beacon_node_path(&self) -> String; + + /// Send the JSON payload to beacon node + fn send_beacon_payload(&self, beacon_node_uri: url::Url) -> Result<(), BeaconNodeError> { + let reqwc = reqwest::blocking::Client::builder() + .build() + .map_err(|_| BeaconNodeError::ClientConfigurationError)?; + let joined_url = beacon_node_uri + .join(&self.beacon_node_path()) + .map_err(|_| BeaconNodeError::InvalidBeaconNodeURI)?; + let resp = reqwc + .post(joined_url) + .header("Content-Type", "application/json") + .body(self.export().to_string()) + .send(); + + match resp { + Ok(response) => { + let code = response.status().as_u16(); + if code != 200 { + Err(BeaconNodeError::Non200Response) + } else { + Ok(()) + } + } + Err(_) => Err(BeaconNodeError::NodeCommunicationError), + } + } +} diff --git a/src/bls_to_execution_change.rs b/src/bls_to_execution_change.rs deleted file mode 100644 index 2cbdb58..0000000 --- a/src/bls_to_execution_change.rs +++ /dev/null @@ -1,388 +0,0 @@ -use crate::{ - key_material, networks::SupportedNetworks, seed::get_eth2_seed, - utils::get_withdrawal_credentials, -}; -use lazy_static::lazy_static; -use regex::Regex; -use ssz::Encode; -use ssz_rs::prelude::*; -use std::collections::HashMap; -use std::str::FromStr; -use types::{Hash256, Keypair, PublicKey, SecretKey, Signature}; - -const DOMAIN_LEN: usize = 32; -const DOMAIN_TYPE_LEN: usize = 4; -const BLS_PUBKEY_LEN: usize = 96; -const EXECUTION_ADDR_LEN: usize = 40; -type DomainType = Vector; -type Domain = Vector; -type Version = Vector; -type BLSPubkey = Vector; -type ExecutionAddress = Vector; - -lazy_static! { - static ref DOMAIN_BLS_TO_EXECUTION_CHANGE: DomainType = - Vector::::deserialize(&[0x0A, 0, 0, 0]) - .expect("failed to deserialize"); - static ref CAPELLA_FORK_VERSION: Version = - Vector::::deserialize(&[0x03, 0, 0, 0]) - .expect("failed to deserialize"); - static ref GENESIS_VALIDATORS_ROOT_MAINNET: [u8; 32] = - "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95".as_bytes()[0..32] - .try_into() - .expect("could not wrap genesis validators root"); - static ref GENESIS_VALIDATORS_ROOT_HOLESKY: [u8; 32] = - "9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1".as_bytes()[0..32] - .try_into() - .expect("could not wrap genesis validators root"); - // FIXME: use the real testnet genesis_validators_root - static ref GENESIS_VALIDATORS_ROOT_STUB: [u8; 32] = - "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95".as_bytes()[0..32] - .try_into() - .expect("could not wrap genesis validators root"); - static ref GENESIS_VALIDATOR_ROOT: HashMap = HashMap::from([ - ( - SupportedNetworks::Mainnet, - Node::deserialize(GENESIS_VALIDATORS_ROOT_MAINNET.as_ssz_bytes().as_ref()).unwrap() - ), - ( - SupportedNetworks::Prater, - Node::deserialize(GENESIS_VALIDATORS_ROOT_STUB.as_ssz_bytes().as_ref()).unwrap() - ), - ( - SupportedNetworks::Goerli, - Node::deserialize(GENESIS_VALIDATORS_ROOT_STUB.as_ssz_bytes().as_ref()).unwrap() - ), - ( - SupportedNetworks::Holesky, - Node::deserialize(GENESIS_VALIDATORS_ROOT_HOLESKY.as_ssz_bytes().as_ref()).unwrap() - ), - ]); -} - -#[derive(SimpleSerialize, Default)] -pub struct ForkData { - current_version: Version, - genesis_validators_root: Node, -} - -#[derive(SimpleSerialize, Default)] -pub struct SigningData { - object_root: Node, - domain: Domain, -} - -#[derive(Clone)] -pub struct BLSToExecutionRequest { - validator_index: u32, - bls_keys: Keypair, - to_execution_address: ExecutionAddress, -} - -#[derive(SimpleSerialize, Default, Clone, Debug)] -pub struct BLSToExecutionChange { - validator_index: u32, - from_bls_pubkey: BLSPubkey, - to_execution_address: ExecutionAddress, -} - -#[derive(Debug, Clone)] -pub struct SignedBLSToExecutionChange { - message: BLSToExecutionChange, - signature: Signature, -} - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct BLSToExecutionChangeExport { - pub validator_index: u32, - pub from_bls_pubkey: String, - pub to_execution_address: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SignedBLSToExecutionChangeExport { - pub message: BLSToExecutionChangeExport, - pub signature: String, -} - -impl SignedBLSToExecutionChange { - pub fn validate( - self, - from_bls_withdrawal_credentials: &str, - to_execution_address: &str, - network: SupportedNetworks, - ) { - // execution address is same as input - let execution_address = std::str::from_utf8(&self.message.to_execution_address).unwrap(); - assert_eq!(to_execution_address, format!("0x{}", execution_address)); - - // withdrawal credentials are the same as input - let withdrawal_pubkey = std::str::from_utf8(&self.message.from_bls_pubkey).unwrap(); - let withdrawal_pubkey = PublicKey::from_str(&format!("0x{}", withdrawal_pubkey)).unwrap(); - - let withdrawal_credentials = hex::encode(get_withdrawal_credentials(&withdrawal_pubkey, 0)); - let withdrawal_credentials = - std::str::from_utf8(withdrawal_credentials.as_bytes()).unwrap(); - - assert_eq!( - from_bls_withdrawal_credentials, - format!("0x{}", withdrawal_credentials) - ); - - // verify signature - - let genesis_validator_root = if [ - SupportedNetworks::Goerli, - SupportedNetworks::Mainnet, - SupportedNetworks::Holesky, - SupportedNetworks::Prater, - ] - .contains(&network) - { - let genesis_validator_root = GENESIS_VALIDATOR_ROOT.get(&network).unwrap(); - genesis_validator_root - } else { - panic!("Unknown network name passed"); - }; - - let domain = compute_domain( - &DOMAIN_BLS_TO_EXECUTION_CHANGE, - CAPELLA_FORK_VERSION.to_owned(), - genesis_validator_root, - ) - .expect("could not compute domain"); - - let signing_root = compute_signing_root(self.message.clone(), domain) - .expect("could not compute signing root"); - - let mut buffer = vec![]; - signing_root - .serialize(&mut buffer) - .expect("Invalid domain computed"); - self.signature - .verify(&withdrawal_pubkey, Hash256::from_slice(buffer.as_slice())); - } - - pub fn export(&self) -> SignedBLSToExecutionChangeExport { - let withdrawal_pubkey = std::str::from_utf8(&self.message.from_bls_pubkey).unwrap(); - let withdrawal_pubkey = PublicKey::from_str(&format!("0x{}", withdrawal_pubkey)).unwrap(); - - let withdrawal_credentials = hex::encode(get_withdrawal_credentials(&withdrawal_pubkey, 0)); - let withdrawal_credentials = - std::str::from_utf8(withdrawal_credentials.as_bytes()).unwrap(); - - let to_execution_address = std::str::from_utf8(&self.message.to_execution_address).unwrap(); - - SignedBLSToExecutionChangeExport { - message: BLSToExecutionChangeExport { - validator_index: self.message.validator_index, - from_bls_pubkey: format!("0x{}", withdrawal_credentials), - to_execution_address: format!("0x{}", to_execution_address), - }, - signature: self.signature.to_string(), - } - } -} - -impl BLSToExecutionRequest { - pub fn new( - mnemonic_phrase: &[u8], - validator_start_index: u32, - validator_index: u32, - execution_address: &str, - ) -> Self { - let (seed, _) = get_eth2_seed(Some(mnemonic_phrase)); - - let execution_addr_regex: Regex = Regex::new(r"^(0x[a-fA-F0-9]{40})$").unwrap(); - - if !execution_addr_regex.is_match(execution_address) { - panic!( - "Invalid execution address: Please pass in a valid execution address with the correct format" - ); - } - - let execution_address = Vector::::deserialize( - execution_address.strip_prefix("0x").unwrap().as_bytes(), - ) - .expect("failed to deserialize"); - - let key_materials = - key_material::seed_to_key_material(&seed, 1, validator_start_index, None, true, None); - - let key_material = key_materials - .first() - .expect("Error deriving key material from mnemonic"); - - BLSToExecutionRequest { - validator_index, - bls_keys: key_material - .withdrawal_keypair - .clone() - .expect("Error deriving key material from mnemonic"), - to_execution_address: execution_address, - } - } - - pub fn sign(self, network: SupportedNetworks) -> SignedBLSToExecutionChange { - let withdrawal_pubkey = Vector::::deserialize( - self.bls_keys - .pk - .to_string() - .strip_prefix("0x") - .unwrap() - .as_bytes(), - ) - .expect("failed to deserialize"); - - let message = BLSToExecutionChange { - validator_index: self.validator_index, - from_bls_pubkey: withdrawal_pubkey, - to_execution_address: self.to_execution_address, - }; - - let secret_key = self.bls_keys.sk.serialize(); - - let withdrawal_privkey = secret_key.as_bytes(); - - generate_signed_bls_to_execution_change(message, withdrawal_privkey, network) - .expect("error generating signing bls to execution change") - } -} - -fn generate_signed_bls_to_execution_change( - message: BLSToExecutionChange, - secret_key: &[u8], - network: SupportedNetworks, -) -> Result> { - let genesis_validator_root = if [ - SupportedNetworks::Goerli, - SupportedNetworks::Mainnet, - SupportedNetworks::Holesky, - SupportedNetworks::Prater, - ] - .contains(&network) - { - let genesis_validator_root = GENESIS_VALIDATOR_ROOT.get(&network).unwrap(); - genesis_validator_root - } else { - panic!("Unknown network name passed"); - }; - - let domain = compute_domain( - &DOMAIN_BLS_TO_EXECUTION_CHANGE, - CAPELLA_FORK_VERSION.to_owned(), - genesis_validator_root, - )?; - - let signing_root = compute_signing_root(message.clone(), domain)?; - - let signature = sign(secret_key, signing_root)?; - - Ok(SignedBLSToExecutionChange { message, signature }) -} - -/// based on https://github.com/ethereum/consensus-specs/blob/02b32100ed26c3c7a4a44f41b932437859487fd2/specs/phase0/beacon-chain.md#compute_domain -fn compute_domain( - domain_type: &DomainType, - fork_version: Version, - genesis_validators_root: &Node, -) -> Result { - let fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root)?; - let mut bytes = Vec::new(); - domain_type.serialize(&mut bytes)?; - fork_data_root.serialize(&mut bytes)?; - Ok(Vector::deserialize(&bytes[0..DOMAIN_LEN]).expect("invalid domain data")) -} - -/// based on https://github.com/ethereum/consensus-specs/blob/02b32100ed26c3c7a4a44f41b932437859487fd2/specs/phase0/beacon-chain.md#compute_fork_data_root -fn compute_fork_data_root( - current_version: Version, - genesis_validators_root: &Node, -) -> Result { - ForkData { - current_version, - genesis_validators_root: genesis_validators_root.to_owned(), - } - .hash_tree_root() -} - -/// based on https://github.com/ethereum/consensus-specs/blob/02b32100ed26c3c7a4a44f41b932437859487fd2/specs/phase0/beacon-chain.md#compute_signing_root -pub fn compute_signing_root( - mut ssz_object: T, - domain: Domain, -) -> Result { - SigningData { - object_root: ssz_object.hash_tree_root()?, - domain, - } - .hash_tree_root() -} - -/// based on https://github.com/ethereum/consensus-specs/blob/02b32100ed26c3c7a4a44f41b932437859487fd2/specs/phase0/beacon-chain.md#bls-signatures -fn sign(secret_key: &[u8], msg: Node) -> Result> { - let secret_key = SecretKey::deserialize(secret_key).expect("couldn't load the key"); - let mut buffer = vec![]; - msg.serialize(&mut buffer) - .expect("Invalid signature computed"); - let message = Hash256::from_slice(buffer.as_slice()); - Ok(secret_key.sign(message)) -} - -#[cfg(test)] -mod test { - use std::str::FromStr; - - use types::{Hash256, PublicKey}; - - use crate::{networks::SupportedNetworks, utils}; - - use super::BLSToExecutionRequest; - - const EXECUTION_WITHDRAWAL_ADDRESS: &str = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; - const PHRASE: &str = "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup"; - - #[test] - fn it_generates_signed_bls_to_execution_change() { - // Keys asserted here are generated with the staking-deposit cli - // ./deposit existing-mnemonic --keystore_password testtest - - // Please enter your mnemonic separated by spaces (" "): entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup - // Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here. [0]: 0 - // Please choose how many new validators you wish to run: 1 - // Please choose the (mainnet or testnet) network/chain name ['mainnet', 'prater', 'kintsugi', 'kiln', 'minimal']: [mainnet]: minimal - - fn withdrawal_creds_from_pk(withdrawal_pk: &PublicKey) -> String { - let withdrawal_creds = utils::get_withdrawal_credentials(&withdrawal_pk, 0); - let credentials_hash = Hash256::from_slice(&withdrawal_creds); - hex::encode(&credentials_hash.as_bytes()) - } - - let bls_to_execution_change = - BLSToExecutionRequest::new(PHRASE.as_bytes(), 0, 100, EXECUTION_WITHDRAWAL_ADDRESS); - let signed_bls_to_execution_change = bls_to_execution_change - .clone() - .sign(SupportedNetworks::Mainnet); - - // format generated fields for assertion - let to_execution_address = - std::str::from_utf8(&signed_bls_to_execution_change.message.to_execution_address) - .unwrap(); - - let withdrawal_pub_key_str = - std::str::from_utf8(&signed_bls_to_execution_change.message.from_bls_pubkey).unwrap(); - let withdrawal_pub_key = - PublicKey::from_str(&format!("0x{}", withdrawal_pub_key_str)).unwrap(); - - let validator_index = bls_to_execution_change.clone().validator_index; - - assert_eq!(100, validator_index); - assert_eq!( - EXECUTION_WITHDRAWAL_ADDRESS, - format!("0x{}", to_execution_address) - ); - assert_eq!( - "00e078f11bc1454244bdf9f63a3b997815f081dd6630204186d4c9627a2942f7", - withdrawal_creds_from_pk(&withdrawal_pub_key) - ); - } -} diff --git a/src/bls_to_execution_change/mod.rs b/src/bls_to_execution_change/mod.rs new file mode 100644 index 0000000..81bf18f --- /dev/null +++ b/src/bls_to_execution_change/mod.rs @@ -0,0 +1,56 @@ +pub(crate) mod operations; + +use types::{Address, BlsToExecutionChange}; + +use crate::key_material::VotingKeyMaterial; + +pub fn bls_execution_change_from_mnemonic( + mnemonic_phrase: &[u8], + validator_start_index: u64, + validator_beacon_index: u64, + execution_address: &str, +) -> (BlsToExecutionChange, VotingKeyMaterial) { + let (seed, _) = crate::seed::get_eth2_seed(Some(mnemonic_phrase)); + + if !crate::utils::EXECUTION_ADDR_REGEX.is_match(execution_address) { + panic!( + "Invalid execution address: Please pass in a valid execution address with the correct format" + ); + } + + let execution_address = Address::from_slice( + hex::decode( + execution_address + .strip_prefix("0x") + .expect("0x prefix is required for execution_address"), + ) + .expect("Invalid hex passed as execution_address") + .as_slice(), + ); + + let key_materials = crate::key_material::seed_to_key_material( + &seed, + 1, + validator_start_index as u32, + None, + true, + None, + ); + + let key_material = key_materials + .first() + .expect("Error deriving key material from mnemonic"); + + let key_pair = key_material.withdrawal_keypair.clone().unwrap(); + + let bls_to_execution_change = BlsToExecutionChange { + validator_index: validator_beacon_index, + from_bls_pubkey: key_pair.pk.into(), + to_execution_address: execution_address, + }; + + (bls_to_execution_change, key_material.clone()) +} + +#[cfg(test)] +mod test; diff --git a/src/bls_to_execution_change/operations.rs b/src/bls_to_execution_change/operations.rs new file mode 100644 index 0000000..98234b6 --- /dev/null +++ b/src/bls_to_execution_change/operations.rs @@ -0,0 +1,85 @@ +use std::str::FromStr; + +use types::{ + BlsToExecutionChange, ChainSpec, Domain, Hash256, PublicKey, SignedBlsToExecutionChange, + SignedRoot, +}; + +use crate::{beacon_node::BeaconNodeExportable, utils::get_withdrawal_credentials}; + +pub(crate) trait SignedBlsToExecutionChangeValidator { + fn validate( + self, + from_bls_withdrawal_credentials: &str, + to_execution_address: &str, + spec: &ChainSpec, + genesis_validators_root: &Hash256, + ); +} + +impl BeaconNodeExportable for SignedBlsToExecutionChange { + fn export(&self) -> serde_json::Value { + serde_json::json!([ + { + "message": { + "validator_index": self.message.validator_index.to_string(), + "from_bls_pubkey": self.message.from_bls_pubkey, + "to_execution_address": format!("0x{}", hex::encode(self.message.to_execution_address)), + }, + "signature": self.signature.to_string() + } + ]) + } + + fn beacon_node_path(&self) -> String { + "/eth/v1/beacon/pool/bls_to_execution_changes".to_string() + } +} + +impl SignedBlsToExecutionChangeValidator for SignedBlsToExecutionChange { + fn validate( + self, + from_bls_withdrawal_credentials: &str, + execution_address: &str, + spec: &ChainSpec, + genesis_validators_root: &Hash256, + ) { + // execution address is same as input + let msg_execution_address = &self.message.to_execution_address; + assert_eq!( + execution_address.to_lowercase(), + format!("0x{}", hex::encode(msg_execution_address)) + ); + + // withdrawal credentials are the same as input + let withdrawal_pubkey = &self.message.from_bls_pubkey; + let withdrawal_pubkey = PublicKey::from_str(&withdrawal_pubkey.to_string()).unwrap(); + + let withdrawal = get_withdrawal_credentials(&withdrawal_pubkey.clone().into(), 0); + let withdrawal_credentials = hex::encode(withdrawal); + let withdrawal_credentials = + std::str::from_utf8(withdrawal_credentials.as_bytes()).unwrap(); + + assert_eq!( + from_bls_withdrawal_credentials, + format!("0x{}", withdrawal_credentials) + ); + + // verify signature + let domain = spec.compute_domain( + Domain::BlsToExecutionChange, + spec.genesis_fork_version, + *genesis_validators_root, + ); + + let bls_to_execution_change: BlsToExecutionChange = BlsToExecutionChange { + validator_index: self.message.validator_index, + from_bls_pubkey: withdrawal_pubkey.clone().into(), + to_execution_address: self.message.to_execution_address, + }; + let signing_root = bls_to_execution_change.signing_root(domain); + if !self.signature.verify(&withdrawal_pubkey, signing_root) { + panic!("Invalid bls to execution change signature") + } + } +} diff --git a/src/bls_to_execution_change/test.rs b/src/bls_to_execution_change/test.rs new file mode 100644 index 0000000..66c0064 --- /dev/null +++ b/src/bls_to_execution_change/test.rs @@ -0,0 +1,56 @@ +use std::str::FromStr; + +use types::{Hash256, PublicKey}; + +use crate::{chain_spec, networks::validators_root_for, utils}; + +const EXECUTION_WITHDRAWAL_ADDRESS: &str = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; +const PHRASE: &str = "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup"; + +#[test] +fn it_generates_signed_bls_to_execution_change() { + // Keys asserted here are generated with the staking-deposit cli + // ./deposit existing-mnemonic --keystore_password testtest + + // Please enter your mnemonic separated by spaces (" "): entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup + // Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here. [0]: 0 + // Please choose how many new validators you wish to run: 1 + // Please choose the (mainnet or testnet) network/chain name ['mainnet', 'prater', 'kintsugi', 'kiln', 'minimal']: [mainnet]: minimal + + fn withdrawal_creds_from_pk(withdrawal_pk: &PublicKey) -> String { + let withdrawal_creds = utils::get_withdrawal_credentials(&withdrawal_pk.into(), 0); + let credentials_hash = Hash256::from_slice(&withdrawal_creds); + hex::encode(&credentials_hash.as_bytes()) + } + + let (bls_to_execution_change, keypair) = + crate::bls_to_execution_change::bls_execution_change_from_mnemonic( + PHRASE.as_bytes(), + 0, + 100, + EXECUTION_WITHDRAWAL_ADDRESS, + ); + let signed_bls_to_execution_change = bls_to_execution_change.clone().sign( + &keypair.withdrawal_keypair.unwrap().sk, + validators_root_for(&crate::networks::SupportedNetworks::Mainnet), + &chain_spec::chain_spec_for_network(&crate::networks::SupportedNetworks::Mainnet).unwrap(), + ); + + // format generated fields for assertion + let to_execution_address = &signed_bls_to_execution_change.message.to_execution_address; + + let withdrawal_pub_key_str = &signed_bls_to_execution_change.message.from_bls_pubkey; + let withdrawal_pub_key = PublicKey::from_str(&withdrawal_pub_key_str.to_string()).unwrap(); + + let validator_index = bls_to_execution_change.clone().validator_index; + + assert_eq!(100, validator_index); + assert_eq!( + EXECUTION_WITHDRAWAL_ADDRESS.to_lowercase(), + format!("0x{}", hex::encode(to_execution_address.as_bytes())), + ); + assert_eq!( + "00e078f11bc1454244bdf9f63a3b997815f081dd6630204186d4c9627a2942f7", + withdrawal_creds_from_pk(&withdrawal_pub_key) + ); +} diff --git a/src/chain_spec.rs b/src/chain_spec.rs index 7724877..1d60e57 100644 --- a/src/chain_spec.rs +++ b/src/chain_spec.rs @@ -1,11 +1,11 @@ use std::path::Path; use eth2_network_config::Eth2NetworkConfig; -use types::{ChainSpec, Config, MainnetEthSpec, MinimalEthSpec}; +use types::{ChainSpec, Config, Hash256, MainnetEthSpec, MinimalEthSpec}; use crate::{networks::SupportedNetworks, DepositError}; -pub fn chain_spec_for_network(network: SupportedNetworks) -> Result { +pub fn chain_spec_for_network(network: &SupportedNetworks) -> Result { let network_name = network.to_string(); if ["goerli", "prater", "mainnet", "holesky"].contains(&network_name.as_str()) { Ok(Eth2NetworkConfig::constant(&network_name) @@ -40,3 +40,30 @@ pub fn chain_spec_from_file(chain_spec_file: String) -> Result, + testnet_properties: Option<(String, String)>, +) -> (Hash256, ChainSpec) { + if chain.is_some() { + let well_known_chain = chain.unwrap(); + ( + crate::networks::validators_root_for(&well_known_chain), + chain_spec_for_network(&well_known_chain).expect("Invalid chain spec"), + ) + } else { + let (genesis_validators_root_str, testnet_config_path) = testnet_properties.expect( + "If custom testnet config is passed, genesis validators root value must be included", + ); + let genesis_validators_root_bytes = hex::decode( + genesis_validators_root_str + .strip_prefix("0x") + .unwrap_or(&genesis_validators_root_str), + ) + .expect("Invalid custom genesis validators root"); + ( + Hash256::from_slice(genesis_validators_root_bytes.as_slice()), + chain_spec_from_file(testnet_config_path).expect("Invalid chain spec in file"), + ) + } +} diff --git a/src/cli/bls_to_execution_change.rs b/src/cli/bls_to_execution_change.rs index 843a5b9..8c115e0 100644 --- a/src/cli/bls_to_execution_change.rs +++ b/src/cli/bls_to_execution_change.rs @@ -1,4 +1,6 @@ -use crate::bls_to_execution_change; +use crate::bls_to_execution_change::operations::SignedBlsToExecutionChangeValidator; +use crate::chain_spec::validators_root_and_spec; +use crate::{beacon_node::BeaconNodeExportable, bls_to_execution_change}; use clap::{arg, Parser}; #[derive(Clone, Parser)] @@ -18,19 +20,19 @@ pub struct BlsToExecutionChangeSubcommandOpts { /// Use "mainnet" if you are /// depositing ETH #[arg(value_enum, long)] - pub chain: crate::networks::SupportedNetworks, + pub chain: Option, /// The index of the first validator's keys you wish to generate the address for /// e.g. if you generated 3 keys before (index #0, index #1, index #2) /// and you want to generate for the 2nd validator, /// the validator_start_index would be 1. /// If no index specified, it will be set to 0. - #[arg(long, visible_alias = "validator_start_index")] - pub validator_start_index: u32, + #[arg(long, visible_alias = "validator_seed_index")] + pub validator_seed_index: u32, /// On-chain beacon index of the validator. - #[arg(long, visible_alias = "validator_index")] - pub validator_index: u32, + #[arg(long, visible_alias = "validator_beacon_index")] + pub validator_beacon_index: u32, /// BLS withdrawal credentials you used when depositing the validator. #[arg(long, visible_alias = "bls_withdrawal_credentials")] @@ -39,29 +41,81 @@ pub struct BlsToExecutionChangeSubcommandOpts { /// Execution (0x01) address to which funds withdrawn should be sent to. #[arg(long, visible_alias = "execution_address")] pub execution_address: String, + + /// Path to a custom Eth PoS chain config + #[arg(long, visible_alias = "testnet_config")] + pub testnet_config: Option, + + /// Custom genesis validators root for the custom testnet, passed as hex string. + /// See https://eth2book.info/capella/part3/containers/state/ for value + /// description + #[arg(long, visible_alias = "genesis_validators_root")] + pub genesis_validators_root: Option, + + /// Optional beacon node URL. If set, the bls-to-execution-change message + /// will not be printed on stdout, but instead sent to beacon node + #[arg(long, visible_alias = "beacon_node_uri")] + pub beacon_node_uri: Option, } impl BlsToExecutionChangeSubcommandOpts { pub fn run(&self) { - let bls_to_execution_change = bls_to_execution_change::BLSToExecutionRequest::new( - self.mnemonic.as_bytes(), - self.validator_start_index, - self.validator_index, - self.execution_address.as_str(), + let chain = if self.chain.is_some() && self.testnet_config.is_some() { + panic!("should only pass one of testnet_config or chain") + } else if self.testnet_config.is_some() { + // Signalizes custom testnet config will be used + None + } else { + self.chain.clone() + }; + + let (genesis_validators_root, spec) = validators_root_and_spec( + chain.clone(), + if chain.is_some() { + None + } else { + Some(( + self.genesis_validators_root + .clone() + .expect("Genesis validators root parameter must be set"), + self.testnet_config + .clone() + .expect("Testnet config must be set"), + )) + }, ); - let signed_bls_to_execution_change = bls_to_execution_change.sign(self.chain.clone()); + let (bls_to_execution_change, keypair) = + bls_to_execution_change::bls_execution_change_from_mnemonic( + self.mnemonic.as_bytes(), + self.validator_seed_index as u64, + self.validator_beacon_index as u64, + self.execution_address.as_str(), + ); + + let signed_bls_to_execution_change = bls_to_execution_change.sign( + &keypair.withdrawal_keypair.unwrap().sk, + genesis_validators_root, + &spec, + ); signed_bls_to_execution_change.clone().validate( self.bls_withdrawal_credentials.as_str(), self.execution_address.as_str(), - self.chain.clone(), + &spec, + &genesis_validators_root, ); - let export = signed_bls_to_execution_change.export(); + if self.beacon_node_uri.is_some() { + signed_bls_to_execution_change + .send_beacon_payload(self.beacon_node_uri.clone().unwrap()) + .unwrap_or_else(|e| panic!("Failed sending beacon node payload: {:?}", e)) + } else { + let export = signed_bls_to_execution_change.export(); - let signed_bls_to_execution_change_json = - serde_json::to_string_pretty(&export).expect("could not parse validator export"); - println!("{}", signed_bls_to_execution_change_json); + let signed_bls_to_execution_change_json = + serde_json::to_string_pretty(&export).expect("could not parse validator export"); + println!("{}", signed_bls_to_execution_change_json); + } } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index cec4916..215b4c5 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,3 +1,4 @@ pub mod bls_to_execution_change; pub mod existing_mnemonic; pub mod new_mnemonic; +pub mod presigned_exit_message; diff --git a/src/cli/presigned_exit_message.rs b/src/cli/presigned_exit_message.rs new file mode 100644 index 0000000..25ce787 --- /dev/null +++ b/src/cli/presigned_exit_message.rs @@ -0,0 +1,112 @@ +use clap::{arg, Parser}; + +use crate::beacon_node::BeaconNodeExportable; +use crate::voluntary_exit::operations::SignedVoluntaryExitValidator; +use crate::{chain_spec::validators_root_and_spec, voluntary_exit}; + +#[derive(Clone, Parser)] +pub struct PresignedExitMessageSubcommandOpts { + /// The mnemonic that you used to generate your + /// keys. + /// + /// It is recommended not to use this + /// argument, and wait for the CLI to ask you + /// for your mnemonic as otherwise it will + /// appear in your shell history. + #[arg(long)] + pub mnemonic: String, + + /// The name of Ethereum PoS chain you are targeting. + /// + /// Use "mainnet" if you are + /// depositing ETH + #[arg(value_enum, long)] + pub chain: Option, + + /// The index of the first validator's keys you wish to generate the address for + /// e.g. if you generated 3 keys before (index #0, index #1, index #2) + /// and you want to generate for the 2nd validator, + /// the validator_start_index would be 1. + /// If no index specified, it will be set to 0. + #[arg(long, visible_alias = "validator_seed_index")] + pub validator_seed_index: u32, + + /// On-chain beacon index of the validator. + #[arg(long, visible_alias = "validator_beacon_index")] + pub validator_beacon_index: u32, + + /// Epoch number which must be included in the presigned exit message. + #[arg(long, visible_alias = "execution_address")] + pub epoch: u64, + + /// Path to a custom Eth PoS chain config + #[arg(long, visible_alias = "testnet_config")] + pub testnet_config: Option, + + /// Custom genesis validators root for the custom testnet, passed as hex string. + /// See https://eth2book.info/capella/part3/containers/state/ for value + /// description + #[arg(long, visible_alias = "genesis_validators_root")] + pub genesis_validators_root: Option, + + /// Optional beacon node URL. If set, the presigned-exit-message value + /// will not be printed on stdout, but instead sent to beacon node + #[arg(long, visible_alias = "beacon_node_uri")] + pub beacon_node_uri: Option, +} + +impl PresignedExitMessageSubcommandOpts { + pub fn run(&self) { + let chain = if self.chain.is_some() && self.testnet_config.is_some() { + panic!("should only pass one of testnet_config or chain") + } else if self.testnet_config.is_some() { + // Signalizes custom testnet config will be used + None + } else { + self.chain.clone() + }; + + let (genesis_validators_root, spec) = validators_root_and_spec( + chain.clone(), + if chain.is_some() { + None + } else { + Some(( + self.genesis_validators_root + .clone() + .expect("Genesis validators root parameter must be set"), + self.testnet_config + .clone() + .expect("Testnet config must be set"), + )) + }, + ); + + let (voluntary_exit, key_material) = voluntary_exit::voluntary_exit_message_from_mnemonic( + self.mnemonic.as_bytes(), + self.validator_seed_index as u64, + self.validator_beacon_index as u64, + self.epoch, + ); + + let signed_voluntary_exit = + voluntary_exit.sign(&key_material.keypair.sk, genesis_validators_root, &spec); + + signed_voluntary_exit.clone().validate( + &key_material.keypair.pk, + &spec, + &genesis_validators_root, + ); + + if self.beacon_node_uri.is_some() { + signed_voluntary_exit + .send_beacon_payload(self.beacon_node_uri.clone().unwrap()) + .unwrap_or_else(|e| panic!("Failed sending beacon node payload: {:?}", e)) + } else { + let export = signed_voluntary_exit.export(); + let presigned_exit_message_json = + serde_json::to_string_pretty(&export).expect("could not parse validator export"); + println!("{}", presigned_exit_message_json); + } + } +} diff --git a/src/deposit.rs b/src/deposit.rs index 4434acd..55f63f0 100644 --- a/src/deposit.rs +++ b/src/deposit.rs @@ -44,7 +44,7 @@ pub(crate) fn keystore_to_deposit( }; let spec = match network { - Some(chain) => chain_spec_for_network(chain)?, + Some(chain) => chain_spec_for_network(&chain)?, None => chain_spec_from_file(chain_spec_file.unwrap())?, }; @@ -181,7 +181,7 @@ mod test { voting_secret: PlainText::from(keypair.sk.serialize().as_bytes().to_vec()), withdrawal_keypair: Some(keypair.clone()), }; - let withdrawal_creds = get_withdrawal_credentials(&pk, 0); + let withdrawal_creds = get_withdrawal_credentials(&pk.into(), 0); let (deposit_data, _) = keystore_to_deposit( &key_material, &withdrawal_creds, diff --git a/src/key_material.rs b/src/key_material.rs index 42592e0..0ddba35 100644 --- a/src/key_material.rs +++ b/src/key_material.rs @@ -111,7 +111,7 @@ mod test { Seed::new(&mnemonic, "") } fn withdrawal_creds_from_pk(withdrawal_pk: &PublicKey) -> String { - let withdrawal_creds = utils::get_withdrawal_credentials(&withdrawal_pk, 0); + let withdrawal_creds = utils::get_withdrawal_credentials(&withdrawal_pk.into(), 0); let credentials_hash = Hash256::from_slice(&withdrawal_creds); hex::encode(&credentials_hash.as_bytes()) } diff --git a/src/lib.rs b/src/lib.rs index b3596fb..e61b504 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +pub(crate) mod beacon_node; pub mod bls_to_execution_change; pub mod chain_spec; pub mod cli; @@ -6,11 +7,9 @@ pub(crate) mod deposit; pub(crate) mod key_material; pub mod networks; pub(crate) mod seed; -pub(crate) mod utils; +pub mod utils; pub mod validators; +pub mod voluntary_exit; pub use deposit::DepositError; pub use validators::*; - -#[macro_use] -extern crate serde_derive; diff --git a/src/main.rs b/src/main.rs index 00d2202..185b7f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![forbid(unsafe_code)] use clap::{Parser, Subcommand}; -use eth_staking_smith::cli::{bls_to_execution_change, existing_mnemonic, new_mnemonic}; +use eth_staking_smith::cli::{ + bls_to_execution_change, existing_mnemonic, new_mnemonic, presigned_exit_message, +}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -14,18 +16,28 @@ enum SubCommands { /// Generates a SignedBLSToExecutionChange object which can be sent /// to the Beacon Node to change the withdrawal address from BLS to an execution address BlsToExecutionChange(bls_to_execution_change::BlsToExecutionChangeSubcommandOpts), + /// Generate (or recover) keys from an existing mnemonic. ExistingMnemonic(existing_mnemonic::ExistingMnemonicSubcommandOpts), + /// Generate new keys with new mnemonic. NewMnemonic(new_mnemonic::NewMnemonicSubcommandOpts), + /// Generate presigned exit message which can be sent + /// to the Beacon Node to start voluntary exit process for the validator + PresignedExitMessage(presigned_exit_message::PresignedExitMessageSubcommandOpts), +} + +impl SubCommands { + pub fn run(&self) { + match self { + Self::BlsToExecutionChange(sub) => sub.run(), + Self::ExistingMnemonic(sub) => sub.run(), + Self::NewMnemonic(sub) => sub.run(), + Self::PresignedExitMessage(sub) => sub.run(), + } + } } fn main() { env_logger::init(); - let opts = Opts::parse(); - match opts.subcommand { - // Server - SubCommands::BlsToExecutionChange(sub) => sub.run(), - SubCommands::ExistingMnemonic(sub) => sub.run(), - SubCommands::NewMnemonic(sub) => sub.run(), - } + opts.subcommand.run() } diff --git a/src/networks.rs b/src/networks.rs index 89bed64..7925d37 100644 --- a/src/networks.rs +++ b/src/networks.rs @@ -1,3 +1,8 @@ +use std::collections::HashMap; + +use lazy_static::lazy_static; +use types::Hash256; + #[derive(clap::ValueEnum, Clone, Hash, Eq, PartialEq)] pub enum SupportedNetworks { Mainnet, @@ -18,3 +23,44 @@ impl std::fmt::Display for SupportedNetworks { write!(f, "{}", s) } } + +fn decode_genesis_validators_root(hex_value: &str) -> Hash256 { + Hash256::from_slice(hex::decode(hex_value).unwrap().as_slice()) +} + +// Genesis validators root values are not present in chain spec, +// but instead acquired from genesis. The values below are well-known +// and taken from repositories in https://github.com/eth-clients organization. +lazy_static! { + pub static ref GENESIS_VALIDATORS_ROOT_MAINNET: Hash256 = decode_genesis_validators_root( + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" + ); + pub static ref GENESIS_VALIDATORS_ROOT_HOLESKY: Hash256 = decode_genesis_validators_root( + "9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1" + ); + pub static ref GENESIS_VALIDATORS_ROOT_GOERLI: Hash256 = decode_genesis_validators_root( + "043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb" + ); + pub static ref GENESIS_VALIDATOR_ROOT: HashMap = HashMap::from([ + ( + SupportedNetworks::Mainnet, + GENESIS_VALIDATORS_ROOT_MAINNET.to_owned() + ), + ( + SupportedNetworks::Prater, + GENESIS_VALIDATORS_ROOT_GOERLI.to_owned() + ), + ( + SupportedNetworks::Goerli, + GENESIS_VALIDATORS_ROOT_GOERLI.to_owned() + ), + ( + SupportedNetworks::Holesky, + GENESIS_VALIDATORS_ROOT_HOLESKY.to_owned() + ), + ]); +} + +pub(crate) fn validators_root_for(network: &SupportedNetworks) -> Hash256 { + *GENESIS_VALIDATOR_ROOT.get(network).unwrap() +} diff --git a/src/utils.rs b/src/utils.rs index 546c2bd..c62a629 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,8 @@ use eth2_keystore::json_keystore::{HexBytes, Kdf, Pbkdf2, Prf, Scrypt}; use eth2_keystore::{DKLEN, SALT_SIZE}; +use regex::Regex; use ssz::Encode; -use types::PublicKey; +use types::{Hash256, PublicKeyBytes}; pub(crate) fn pbkdf2() -> Kdf { let mut salt = vec![0u8; SALT_SIZE]; @@ -26,13 +27,31 @@ pub(crate) fn scrypt() -> Kdf { }) } -/// Returns the withdrawal credentials for a given public key. +/// Returns the withdrawal credentials for a given BLS public key. /// /// Used for submitting deposits to the Eth1 deposit contract. -pub(crate) fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { +pub(crate) fn get_withdrawal_credentials(pubkey: &PublicKeyBytes, prefix_byte: u8) -> Vec { let hashed = ethereum_hashing::hash(&pubkey.as_ssz_bytes()); let mut prefixed = vec![prefix_byte]; prefixed.extend_from_slice(&hashed[1..]); prefixed } + +/// Given BLS public key, creates 0x0 withdrawal credentials from it +/// +/// Used for deriving withdrawal from the validator BLS key pair +pub fn withdrawal_creds_from_pk(withdrawal_pk: &PublicKeyBytes) -> String { + let withdrawal_creds = get_withdrawal_credentials(withdrawal_pk, 0); + let credentials_hash = Hash256::from_slice(&withdrawal_creds); + hex::encode(credentials_hash.as_bytes()) +} + +// Various regexes used for input validation +lazy_static::lazy_static! { + /// see format of execution address: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#eth1_address_withdrawal_prefix + pub static ref EXECUTION_ADDR_REGEX: Regex = Regex::new(r"^(0x[a-fA-F0-9]{40})$").unwrap(); + pub static ref EXECUTION_CREDS_REGEX: Regex = + Regex::new(r"^(0x01[0]{22}[a-fA-F0-9]{40})$").unwrap(); + pub static ref BLS_CREDS_REGEX: Regex = Regex::new(r"^(0x00[a-fA-F0-9]{62})$").unwrap(); +} diff --git a/src/validators.rs b/src/validators.rs index 9a9c793..32a7822 100644 --- a/src/validators.rs +++ b/src/validators.rs @@ -8,7 +8,6 @@ use crate::utils::get_withdrawal_credentials; use bip39::{Mnemonic, Seed as Bip39Seed}; use eth2_keystore::Keystore; use eth2_wallet::json_wallet::Kdf; -use regex::Regex; use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use types::{ @@ -298,18 +297,14 @@ fn set_withdrawal_credentials( ) -> Result, DepositError> { let withdrawal_credentials = match existing_withdrawal_credentials { Some(creds) => { - let execution_addr_regex: Regex = Regex::new(r"^(0x[a-fA-F0-9]{40})$").unwrap(); - let execution_creds_regex: Regex = - Regex::new(r"^(0x01[0]{22}[a-fA-F0-9]{40})$").unwrap(); - let bls_creds_regex: Regex = Regex::new(r"^(0x00[a-fA-F0-9]{62})$").unwrap(); - - let withdrawal_credentials = if execution_addr_regex.is_match(creds.as_str()) { - // see format of execution address: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#eth1_address_withdrawal_prefix + let withdrawal_credentials = if crate::utils::EXECUTION_ADDR_REGEX + .is_match(creds.as_str()) + { let mut formatted_creds = ETH1_CREDENTIALS_PREFIX.to_vec(); formatted_creds.extend_from_slice(&creds.as_bytes()[2..]); formatted_creds - } else if execution_creds_regex.is_match(creds.as_str()) - || bls_creds_regex.is_match(creds.as_str()) + } else if crate::utils::EXECUTION_CREDS_REGEX.is_match(creds.as_str()) + || crate::utils::BLS_CREDS_REGEX.is_match(creds.as_str()) { // see format of execution & bls credentials https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#bls_withdrawal_prefix let formatted_creds = creds.as_bytes()[2..].to_vec(); @@ -332,7 +327,7 @@ fn set_withdrawal_credentials( } }; - get_withdrawal_credentials(&withdrawal_pk, 0) + get_withdrawal_credentials(&withdrawal_pk.into(), 0) } }; diff --git a/src/voluntary_exit/mod.rs b/src/voluntary_exit/mod.rs new file mode 100644 index 0000000..88a8b27 --- /dev/null +++ b/src/voluntary_exit/mod.rs @@ -0,0 +1,37 @@ +pub(crate) mod operations; + +use types::{Epoch, VoluntaryExit}; + +use crate::key_material::VotingKeyMaterial; + +pub fn voluntary_exit_message_from_mnemonic( + mnemonic_phrase: &[u8], + validator_seed_index: u64, + validator_beacon_index: u64, + epoch: u64, +) -> (VoluntaryExit, VotingKeyMaterial) { + let (seed, _) = crate::seed::get_eth2_seed(Some(mnemonic_phrase)); + + let key_materials = crate::key_material::seed_to_key_material( + &seed, + 1, + validator_seed_index as u32, + None, + false, + None, + ); + + let key_material = key_materials + .first() + .expect("Error deriving key material from mnemonic"); + + let voluntary_exit = VoluntaryExit { + epoch: Epoch::from(epoch), + validator_index: validator_beacon_index, + }; + + (voluntary_exit, key_material.clone()) +} + +#[cfg(test)] +mod test; diff --git a/src/voluntary_exit/operations.rs b/src/voluntary_exit/operations.rs new file mode 100644 index 0000000..bc1f9ce --- /dev/null +++ b/src/voluntary_exit/operations.rs @@ -0,0 +1,53 @@ +use tree_hash::Hash256; +use types::{ + ChainSpec, Domain, ForkName, PublicKey, SignedRoot, SignedVoluntaryExit, VoluntaryExit, +}; + +use crate::beacon_node::BeaconNodeExportable; + +pub(crate) trait SignedVoluntaryExitValidator { + fn validate(self, pubkey: &PublicKey, spec: &ChainSpec, genesis_validators_root: &Hash256); +} + +impl BeaconNodeExportable for SignedVoluntaryExit { + fn export(&self) -> serde_json::Value { + serde_json::json!({ + "message": { + "epoch": self.message.epoch.as_u64(), + "validator_index": self.message.validator_index, + }, + "signature": self.signature.to_string() + }) + } + + fn beacon_node_path(&self) -> String { + "/eth/v1/beacon/pool/voluntary_exits".to_string() + } +} + +impl SignedVoluntaryExitValidator for SignedVoluntaryExit { + fn validate(self, pubkey: &PublicKey, spec: &ChainSpec, genesis_validators_root: &Hash256) { + let fork_name = spec.fork_name_at_epoch(self.message.epoch); + let fork_version = match fork_name { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { + spec.fork_version_for_name(fork_name) + } + // EIP-7044 + ForkName::Deneb => spec.fork_version_for_name(ForkName::Capella), + }; + let domain = spec.compute_domain( + Domain::VoluntaryExit, + fork_version, + *genesis_validators_root, + ); + + let voluntary_exit: VoluntaryExit = VoluntaryExit { + validator_index: self.message.validator_index, + epoch: self.message.epoch, + }; + let signing_root = voluntary_exit.signing_root(domain); + if !self.signature.verify(pubkey, signing_root) { + panic!("Invalid voluntary exit signature") + } + } +} diff --git a/src/voluntary_exit/test.rs b/src/voluntary_exit/test.rs new file mode 100644 index 0000000..c0bcb00 --- /dev/null +++ b/src/voluntary_exit/test.rs @@ -0,0 +1,24 @@ +use crate::{chain_spec::validators_root_and_spec, networks::SupportedNetworks}; + +const PHRASE: &str = "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup"; + +#[test] +fn it_generates_presigned_exit_message() { + let (genesis_validators_root, spec) = + validators_root_and_spec(Some(SupportedNetworks::Holesky), None); + + let (voluntary_exit, key_material) = + crate::voluntary_exit::voluntary_exit_message_from_mnemonic( + PHRASE.as_bytes(), + 0, + 100, + 73682, + ); + + let signed_voluntary_exit = + voluntary_exit.sign(&key_material.keypair.sk, genesis_validators_root, &spec); + + assert_eq!(100, signed_voluntary_exit.message.validator_index); + assert_eq!(73682, signed_voluntary_exit.message.epoch.as_u64()); + assert_eq!("0xa418543c8bdc266e00a45d7409386fffe02ad9ce3c1e707d562b38f7160966567602c518f6615edc8ecd964b978837f30a742c535dafc33137833b3aa6c30f4fecf37449de405ed50d8e436f21c6f58c0a48407037b1985507c3221ce53ba213", signed_voluntary_exit.signature.to_string()); +} diff --git a/tests/e2e/bls_to_execution_change.rs b/tests/e2e/bls_to_execution_change.rs index 7c2392d..8f321b3 100644 --- a/tests/e2e/bls_to_execution_change.rs +++ b/tests/e2e/bls_to_execution_change.rs @@ -1,13 +1,30 @@ use assert_cmd::prelude::*; -use eth_staking_smith::bls_to_execution_change::SignedBLSToExecutionChangeExport; +use eth_staking_smith::utils::withdrawal_creds_from_pk; +use ssz::Encode; use std::process::Command; +use types::SignedBlsToExecutionChange; /* - create SignedBLSToExecutionChange message for existing mnemonic + python3 staking_deposit/deposit.py generate-bls-to-execution-change + + ***Using the tool on an offline and secure device is highly recommended to keep your mnemonic safe.*** + + Please choose your language ['1. العربية', '2. ελληνικά', '3. English', '4. Français', '5. Bahasa melayu', '6. Italiano', '7. 日本語', '8. 한국어', '9. Português do Brasil', '10. român', '11. Türkçe', '12. 简体中文']: [English]: + Please choose the (mainnet or testnet) network/chain name ['mainnet', 'goerli', 'sepolia', 'zhejiang', 'holesky']: [mainnet]: holesky + Please enter your mnemonic separated by spaces (" "). Note: you only need to enter the first 4 letters of each word if you'd prefer.: ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say + Please enter the index position for the keys to start generating withdrawal credentials in ERC-2334 format. [0]: 0 + Please enter a list of the validator index number(s) of your validator(s) as identified on the beacon chain. Split multiple items with whitespaces or commas.: 100 + Please enter a list of the old BLS withdrawal credentials of your validator(s). Split multiple items with whitespaces or commas. The withdrawal credentials are in hexadecimal encoded form.: 0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d + Please enter the 20-byte execution address for the new withdrawal credentials. Note that you CANNOT change it once you have set it on chain.: 0x71C7656EC7ab88b098defB751B7401B5f6d8976F + + **[Warning] you are setting an Eth1 address as your withdrawal address. Please ensure that you have control over this address.** + + Repeat your execution address for confirmation.: 0x71C7656EC7ab88b098defB751B7401B5f6d8976F + */ #[test] fn test_bls_to_execution_change() -> Result<(), Box> { - let chain = "goerli"; + let chain = "holesky"; let expected_mnemonic = "ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say"; let validator_start_index = "0"; let validator_index = "100"; @@ -20,9 +37,9 @@ fn test_bls_to_execution_change() -> Result<(), Box> { cmd.arg("bls-to-execution-change"); cmd.arg("--chain"); cmd.arg(chain); - cmd.arg("--validator_start_index"); + cmd.arg("--validator_seed_index"); cmd.arg(validator_start_index); - cmd.arg("--validator_index"); + cmd.arg("--validator_beacon_index"); cmd.arg(validator_index); cmd.arg("--mnemonic"); cmd.arg(expected_mnemonic); @@ -38,20 +55,90 @@ fn test_bls_to_execution_change() -> Result<(), Box> { let output = &cmd.output()?.stdout; let command_output = std::str::from_utf8(output)?; - let signed_bls_to_execution_change: SignedBLSToExecutionChangeExport = + let signed_bls_to_execution_changes: Vec = serde_json::from_str(command_output)?; + assert_eq!(1, signed_bls_to_execution_changes.len()); + let signed_bls_to_execution_change = signed_bls_to_execution_changes.first().unwrap(); assert_eq!(100, signed_bls_to_execution_change.message.validator_index); assert_eq!( - "0x71C7656EC7ab88b098defB751B7401B5f6d8976F", - signed_bls_to_execution_change.message.to_execution_address + "0x71C7656EC7ab88b098defB751B7401B5f6d8976F".to_lowercase(), + format!( + "0x{}", + hex::encode(signed_bls_to_execution_change.message.to_execution_address) + ) ); + assert_eq!( "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d", - signed_bls_to_execution_change.message.from_bls_pubkey + format!( + "0x{}", + withdrawal_creds_from_pk(&signed_bls_to_execution_change.message.from_bls_pubkey) + ) + ); + + assert_eq!( + "0xb9e6fcdf66962fbaeec762908e7c986c154ba2274fdfe307603d71c465acda49af98a75aa62743fc59a71e678fccd433164247130c1cede0832a17cc61fc21204ec83c7f8fd76848d6520805939547b4c677fca85f98d1f749c428814fd6a6c5", + format!( + "0x{}", + hex::encode(signed_bls_to_execution_change.signature.as_ssz_bytes()), + ) ); Ok(()) } -// negative test inputs +#[test] +fn test_bls_to_execution_change_send_beacon_node() -> Result<(), Box> { + let server = httpmock::MockServer::start(); + + let beacon_node_mock = server.mock(|when, then| { + when.method(httpmock::Method::POST) + .path("/eth/v1/beacon/pool/bls_to_execution_changes") + .json_body(serde_json::json!([ + { + "message": { + "from_bls_pubkey": "0x958823db41e63bdb54b8445e454f24a592a44faef7bf1161c482c254d36cd2ffb027af3cc87817064c6a09f54acec5a0", + "to_execution_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F".to_lowercase(), + "validator_index": "100", + }, + "signature": "0xb9e6fcdf66962fbaeec762908e7c986c154ba2274fdfe307603d71c465acda49af98a75aa62743fc59a71e678fccd433164247130c1cede0832a17cc61fc21204ec83c7f8fd76848d6520805939547b4c677fca85f98d1f749c428814fd6a6c5" + } + ])); + then.status(200); + }); + + let chain = "holesky"; + let expected_mnemonic = "ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say"; + let validator_start_index = "0"; + let validator_index = "100"; + let execution_address = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; + let bls_withdrawal_credentials = + "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d"; + // run eth-staking-smith + let mut cmd = Command::cargo_bin("eth-staking-smith")?; + + cmd.arg("bls-to-execution-change"); + cmd.arg("--chain"); + cmd.arg(chain); + cmd.arg("--validator_seed_index"); + cmd.arg(validator_start_index); + cmd.arg("--validator_beacon_index"); + cmd.arg(validator_index); + cmd.arg("--mnemonic"); + cmd.arg(expected_mnemonic); + cmd.arg("--bls_withdrawal_credentials"); + cmd.arg(bls_withdrawal_credentials); + cmd.arg("--execution_address"); + cmd.arg(execution_address); + cmd.arg("--beacon-node-uri"); + cmd.arg(server.base_url()); + + cmd.assert().success(); + + // verify request path and body + + beacon_node_mock.assert(); + + Ok(()) +} diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index c29808b..49c56c2 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -1,6 +1,7 @@ mod bls_to_execution_change; mod existing_mnemonic; mod new_mnemonic; +mod presigned_exit_message; use serde::{Deserialize, Serialize}; diff --git a/tests/e2e/new_mnemonic.rs b/tests/e2e/new_mnemonic.rs index 7523c09..f020f21 100644 --- a/tests/e2e/new_mnemonic.rs +++ b/tests/e2e/new_mnemonic.rs @@ -50,7 +50,7 @@ fn test_deposit_data_keystore() -> Result<(), Box> { let keystore = generated_validator_json.keystores.get(0).unwrap(); generated_deposit_data - .validate(eth_staking_smith::chain_spec::chain_spec_for_network(chain).unwrap()); + .validate(eth_staking_smith::chain_spec::chain_spec_for_network(&chain).unwrap()); // decrypt keystore with expected password to derive private key let encoded_private_key = decrypt_generated_keystore( @@ -104,7 +104,7 @@ fn test_multliple_validators() -> Result<(), Box> { let generated_deposit_datas = generated_validator_json.deposit_data; let spec = - eth_staking_smith::chain_spec::chain_spec_for_network(SupportedNetworks::Goerli).unwrap(); + eth_staking_smith::chain_spec::chain_spec_for_network(&SupportedNetworks::Goerli).unwrap(); for deposit_data in generated_deposit_datas { deposit_data.validate(spec.clone()); @@ -176,7 +176,7 @@ fn test_withdrawal_address_execution() -> Result<(), Box> .to_owned(); generated_deposit_data - .validate(eth_staking_smith::chain_spec::chain_spec_for_network(chain).unwrap()); + .validate(eth_staking_smith::chain_spec::chain_spec_for_network(&chain).unwrap()); // decrypt keystore with expected password to derive private key let encoded_private_key = decrypt_generated_keystore( @@ -234,7 +234,7 @@ fn test_withdrawal_credentials_bls() -> Result<(), Box> { .to_owned(); generated_deposit_data - .validate(eth_staking_smith::chain_spec::chain_spec_for_network(chain).unwrap()); + .validate(eth_staking_smith::chain_spec::chain_spec_for_network(&chain).unwrap()); // decrypt keystore with expected password to derive private key let encoded_private_key = decrypt_generated_keystore( @@ -355,7 +355,7 @@ fn test_withdrawal_credentials_execution() -> Result<(), Box Result<(), Box> { let keystore = generated_validator_json.keystores.get(0).unwrap(); generated_deposit_data - .validate(eth_staking_smith::chain_spec::chain_spec_for_network(chain).unwrap()); + .validate(eth_staking_smith::chain_spec::chain_spec_for_network(&chain).unwrap()); // decrypt keystore with expected password to derive private key let encoded_private_key = decrypt_generated_keystore( @@ -476,7 +476,7 @@ fn test_keystore_kdf_scrypt() -> Result<(), Box> { let keystore = generated_validator_json.keystores.get(0).unwrap(); generated_deposit_data - .validate(eth_staking_smith::chain_spec::chain_spec_for_network(chain).unwrap()); + .validate(eth_staking_smith::chain_spec::chain_spec_for_network(&chain).unwrap()); // decrypt keystore with expected password to derive private key let encoded_private_key = decrypt_generated_keystore( diff --git a/tests/e2e/presigned_exit_message.rs b/tests/e2e/presigned_exit_message.rs new file mode 100644 index 0000000..e8eb68b --- /dev/null +++ b/tests/e2e/presigned_exit_message.rs @@ -0,0 +1,151 @@ +use assert_cmd::prelude::*; +use std::process::Command; +use types::SignedVoluntaryExit; + +/** + +Command sequence to verify signature: + +./target/debug/eth-staking-smith existing-mnemonic \ + --chain mainnet \ + --num_validators 1 \ + --mnemonic 'ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say' +{ + "deposit_data": [ + { + "amount": 32000000000, + "deposit_cli_version": "2.7.0", + "deposit_data_root": "7ac103cb959b55dff155f7406393c3e6f1ba0011baee2b61bca00fdc3b2cb2c2", + "deposit_message_root": "bfd9d2c616eb570ad3fd4d4caf169b88f80490d8923537474bf1f6c5cec5e56d", + "fork_version": "00000000", + "network_name": "mainnet", + "pubkey": "8844cebb34d10e0e57f3c29ada375dafe14762ab85b2e408c3d6d55ce6d03317660bca9f2c2d17d8fbe14a2529ada1ea", + "signature": "96ebebf92967a2b187e031062f5cb5128a2bfc42559bd9dfdd1e481a056b3ef2cfddf1a0381530286013e3893e097b02129113e62a94bedd250253eb766f010824d0be7616f51b9f7609972695231bcda1cabf7a6a2d60a07e14237f2b6096ab", + "withdrawal_credentials": "0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" + } + ], + "keystores": [], + "mnemonic": { + "seed": "ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say" + }, + "private_keys": [ + "6d446ca271eb229044b9039354ecdfa6244d1a11615ec1a46fc82a800367de5d" + ] +} + +./ethdo validator exit --epoch 305658 --private-key=0x6d446ca271eb229044b9039354ecdfa6244d1a11615ec1a46fc82a800367de5d --offline --json | jq +{ + "message": { + "epoch": "305658", + "validator_index": "100" + }, + "signature": "0xa74f22d26da9934c2a9c783799fb9e7bef49b3d7c3759a0683b52ee5d71516c0ecdbcc47703f11959c5e701a6c47194410bed800217bd4dd0dab1e0587b14551771accd04ff1c78302f9605f44c3894976c5b3537b70cb7ac9dcb5398dc22079" +} + +cat offline-preparation.json +{ + "version": "3", + "genesis_validators_root": "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95", + "epoch": "305658", + "genesis_fork_version": "0x00000000", + "exit_fork_version": "0x03000000", + "current_fork_version": "0x04000000", + "bls_to_execution_change_domain_type": "0x0a000000", + "voluntary_exit_domain_type": "0x04000000", + "validators": [ + { + "index": "100", + "pubkey": "8844cebb34d10e0e57f3c29ada375dafe14762ab85b2e408c3d6d55ce6d03317660bca9f2c2d17d8fbe14a2529ada1ea", + "state": "active_ongoing", + "withdrawal_credentials": "0x0100000000000000000000000d369bb49efa5100fd3b86a9f828c55da04d2d50" + } + ] +} + +*/ + +#[test] +fn test_presigned_exit_message() -> Result<(), Box> { + let chain = "mainnet"; + let expected_mnemonic = "ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say"; + let validator_start_index = "0"; + let validator_index = "100"; + let epoch = "305658"; + + // run eth-staking-smith + let mut cmd = Command::cargo_bin("eth-staking-smith")?; + + cmd.arg("presigned-exit-message"); + cmd.arg("--chain"); + cmd.arg(chain); + cmd.arg("--validator_seed_index"); + cmd.arg(validator_start_index); + cmd.arg("--validator_beacon_index"); + cmd.arg(validator_index); + cmd.arg("--mnemonic"); + cmd.arg(expected_mnemonic); + cmd.arg("--epoch"); + cmd.arg(epoch); + + cmd.assert().success(); + + let output = &cmd.output()?.stdout; + let command_output = std::str::from_utf8(output)?; + + let signed_voluntary_exit: SignedVoluntaryExit = serde_json::from_str(command_output)?; + assert_eq!( + signed_voluntary_exit.signature.to_string(), + "0xa74f22d26da9934c2a9c783799fb9e7bef49b3d7c3759a0683b52ee5d71516c0ecdbcc47703f11959c5e701a6c47194410bed800217bd4dd0dab1e0587b14551771accd04ff1c78302f9605f44c3894976c5b3537b70cb7ac9dcb5398dc22079" + ); + + Ok(()) +} + +#[test] +fn test_presigned_exit_message_send_beacon_node() -> Result<(), Box> { + let chain = "mainnet"; + let expected_mnemonic = "ski interest capable knee usual ugly duty exercise tattoo subway delay upper bid forget say"; + let validator_start_index = "0"; + let validator_index = "100"; + let epoch = "305658"; + + let server = httpmock::MockServer::start(); + + let beacon_node_mock = server.mock(|when, then| { + when.method(httpmock::Method::POST) + .path("/eth/v1/beacon/pool/voluntary_exits") + .json_body(serde_json::json!({ + "message": { + "epoch": epoch.parse::().unwrap(), + "validator_index": validator_index.parse::().unwrap(), + }, + "signature": "0xa74f22d26da9934c2a9c783799fb9e7bef49b3d7c3759a0683b52ee5d71516c0ecdbcc47703f11959c5e701a6c47194410bed800217bd4dd0dab1e0587b14551771accd04ff1c78302f9605f44c3894976c5b3537b70cb7ac9dcb5398dc22079" + })); + then.status(200); + }); + + // run eth-staking-smith + let mut cmd = Command::cargo_bin("eth-staking-smith")?; + + cmd.arg("presigned-exit-message"); + cmd.arg("--chain"); + cmd.arg(chain); + cmd.arg("--validator_seed_index"); + cmd.arg(validator_start_index); + cmd.arg("--validator_beacon_index"); + cmd.arg(validator_index); + cmd.arg("--mnemonic"); + cmd.arg(expected_mnemonic); + cmd.arg("--epoch"); + cmd.arg(epoch); + cmd.arg("--beacon-node-uri"); + cmd.arg(server.base_url()); + + cmd.assert().success(); + + // verify request path and body + + beacon_node_mock.assert(); + + Ok(()) +}