From ea783360d3d211e26e011f7cd344588b26fd8fbc Mon Sep 17 00:00:00 2001 From: realbigsean Date: Thu, 31 Mar 2022 07:52:23 +0000 Subject: [PATCH] Kiln mev boost (#3062) ## Issue Addressed MEV boost compatibility ## Proposed Changes See #2987 ## Additional Info This is blocked on the stabilization of a couple specs, [here](https://github.com/ethereum/beacon-APIs/pull/194) and [here](https://github.com/flashbots/mev-boost/pull/20). Additional TODO's and outstanding questions - [ ] MEV boost JWT Auth - [ ] Will `builder_proposeBlindedBlock` return the revealed payload for the BN to propogate - [ ] Should we remove `private-tx-proposals` flag and communicate BN <> VC with blinded blocks by default once these endpoints enter the beacon-API's repo? This simplifies merge transition logic. Co-authored-by: realbigsean Co-authored-by: realbigsean --- Cargo.lock | 222 +++++++-------- Cargo.toml | 2 + beacon_node/beacon_chain/Cargo.toml | 2 +- beacon_node/beacon_chain/src/beacon_chain.rs | 24 +- .../beacon_chain/src/block_verification.rs | 5 +- .../beacon_chain/src/execution_payload.rs | 42 +-- .../beacon_chain/tests/block_verification.rs | 3 + beacon_node/beacon_chain/tests/merge.rs | 25 +- .../tests/payload_invalidation.rs | 2 +- beacon_node/execution_layer/src/engine_api.rs | 78 +++--- .../execution_layer/src/engine_api/http.rs | 114 ++++++-- .../src/engine_api/json_structures.rs | 196 ++++++++----- beacon_node/execution_layer/src/engines.rs | 125 ++++++++- beacon_node/execution_layer/src/lib.rs | 257 ++++++++++++------ .../src/test_utils/mock_execution_layer.rs | 7 +- beacon_node/http_api/Cargo.toml | 1 + beacon_node/http_api/src/lib.rs | 183 ++++++++++++- beacon_node/http_api/tests/tests.rs | 19 +- beacon_node/lighthouse_network/Cargo.toml | 2 +- .../lighthouse_network/tests/rpc_tests.rs | 8 +- beacon_node/src/cli.rs | 7 + beacon_node/src/config.rs | 8 + common/eth2/src/lib.rs | 85 +++++- consensus/fork_choice/src/fork_choice.rs | 8 +- .../src/per_block_processing.rs | 89 +++--- .../block_signature_verifier.rs | 47 ++-- .../process_operations.rs | 8 +- .../per_block_processing/signature_sets.rs | 21 +- consensus/types/Cargo.toml | 2 +- consensus/types/src/beacon_block.rs | 79 ++---- consensus/types/src/beacon_block_body.rs | 20 +- consensus/types/src/execution_payload.rs | 17 +- .../types/src/execution_payload_header.rs | 26 +- consensus/types/src/lib.rs | 4 +- consensus/types/src/payload.rs | 236 ++++++++++++++++ consensus/types/src/signed_beacon_block.rs | 24 +- consensus/types/src/test_utils/test_random.rs | 7 + testing/ef_tests/src/cases/operations.rs | 13 +- testing/ef_tests/src/type_name.rs | 1 + testing/ef_tests/tests/tests.rs | 4 +- .../src/test_rig.rs | 13 +- validator_client/src/block_service.rs | 168 ++++++++++-- validator_client/src/cli.rs | 8 + validator_client/src/config.rs | 6 + validator_client/src/lib.rs | 1 + validator_client/src/signing_method.rs | 10 +- .../src/signing_method/web3signer.rs | 12 +- validator_client/src/validator_store.rs | 31 ++- 48 files changed, 1628 insertions(+), 644 deletions(-) create mode 100644 consensus/types/src/payload.rs diff --git a/Cargo.lock b/Cargo.lock index c13752e94..38725b126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", "cipher", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "ctr", "opaque-debug", ] @@ -293,7 +293,7 @@ dependencies = [ "eth2", "eth2_hashing 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "eth2_ssz_types", "execution_layer", "fork_choice", @@ -610,7 +610,7 @@ version = "0.1.0" dependencies = [ "eth2_hashing 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "eth2_ssz_types", "ethereum-types 0.12.1", "quickcheck 0.9.2", @@ -640,7 +640,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.0", + "nom 7.1.1", ] [[package]] @@ -657,7 +657,7 @@ checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if", "cipher", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "zeroize", ] @@ -769,7 +769,7 @@ dependencies = [ "slot_clock", "store", "task_executor", - "time 0.3.7", + "time 0.3.9", "timer", "tokio", "toml", @@ -858,9 +858,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -912,9 +912,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if", "crossbeam-utils", @@ -933,10 +933,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg 1.1.0", "cfg-if", "crossbeam-utils", "lazy_static", @@ -946,9 +947,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if", "lazy_static", @@ -1228,9 +1229,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1301,9 +1302,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" +checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" dependencies = [ "signature", ] @@ -1333,7 +1334,7 @@ dependencies = [ "compare_fields_derive", "derivative", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "ethereum-types 0.12.1", "fork_choice", "fs2", @@ -1348,7 +1349,7 @@ dependencies = [ "store", "swap_or_not_shuffle", "tree_hash", - "tree_hash_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tree_hash_derive", "types", ] @@ -1476,7 +1477,7 @@ dependencies = [ "eth1_test_rig", "eth2", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "fallback", "futures", "hex", @@ -1520,7 +1521,7 @@ dependencies = [ "eth2_keystore", "eth2_serde_utils", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "futures", "futures-util", "libsecp256k1 0.6.0", @@ -1647,7 +1648,7 @@ dependencies = [ name = "eth2_ssz" version = "0.4.1" dependencies = [ - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "ethereum-types 0.12.1", "smallvec", ] @@ -1662,18 +1663,6 @@ dependencies = [ "syn", ] -[[package]] -name = "eth2_ssz_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635b86d2c941bb71e7419a571e1763d65c93e51a1bafc400352e3bef6ff59fc9" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "eth2_ssz_types" version = "0.2.2" @@ -1687,7 +1676,7 @@ dependencies = [ "serde_json", "smallvec", "tree_hash", - "tree_hash_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tree_hash_derive", "typenum", ] @@ -1860,7 +1849,7 @@ dependencies = [ "tempfile", "tokio", "tree_hash", - "tree_hash_derive 0.4.0", + "tree_hash_derive", "types", "warp 0.3.0", "zeroize", @@ -2008,7 +1997,7 @@ version = "0.1.0" dependencies = [ "beacon_chain", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "proto_array", "store", "types", @@ -2102,9 +2091,9 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d383f0425d991a05e564c2f3ec150bd6dde863179c131dd60d8aa73a05434461" +checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" dependencies = [ "futures-io", "rustls 0.20.4", @@ -2454,6 +2443,7 @@ dependencies = [ "eth1", "eth2", "eth2_ssz", + "execution_layer", "futures", "hex", "lazy_static", @@ -2518,9 +2508,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ "bytes", "futures-channel", @@ -2884,15 +2874,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.119" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libflate" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d57e534717ac3e0b8dc459fe338bdfb4e29d7eea8fd0926ba649ddd3f4765f" +checksum = "05605ab2bce11bcfc0e9c635ff29ef8b2ea83f29be257ee7d730cac3ee373093" dependencies = [ "adler32", "crc32fast", @@ -2926,9 +2916,9 @@ checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" [[package]] name = "libmdbx" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8a3723c12c5caa3f2a456b645063d1d8ffb1562895fa43746a999d205b0c6" +checksum = "3265f0f9e378bfbbd98596a3288b5909f26f3169e4f6d4a05fda8c734ce2cdd8" dependencies = [ "bitflags", "byteorder", @@ -2936,7 +2926,7 @@ dependencies = [ "indexmap", "libc", "mdbx-sys", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "thiserror", ] @@ -3193,9 +3183,9 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8153a6472e84ec888ef2bf21deafe8d4214e811f0f162abbf07156c27f8fa8" +checksum = "daf2fe8c80b43561355f4d51875273b5b6dfbac37952e8f64b1270769305c9d7" dependencies = [ "quote", "syn", @@ -3421,7 +3411,7 @@ dependencies = [ "discv5", "error-chain", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "eth2_ssz_types", "exit-future", "fnv", @@ -3493,9 +3483,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", ] @@ -3584,9 +3574,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mdbx-sys" -version = "0.11.4-git.20210105" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b21b3e0def3a5c880f6388ed2e33b695097c6b0eca039dae6010527b059f8be1" +checksum = "cb471ee10f93c8c276083d59cae56365cca92d5bb2e27959da89ced5a8adf13e" dependencies = [ "bindgen", "cc", @@ -3667,14 +3657,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -3981,13 +3972,12 @@ checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -4071,9 +4061,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" dependencies = [ "libc", ] @@ -4155,7 +4145,7 @@ dependencies = [ "beacon_chain", "derivative", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "itertools", "lazy_static", "lighthouse_metrics", @@ -4441,7 +4431,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "opaque-debug", "universal-hash", ] @@ -4453,7 +4443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "opaque-debug", "universal-hash", ] @@ -4660,7 +4650,7 @@ name = "proto_array" version = "0.2.0" dependencies = [ "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "serde", "serde_derive", "serde_yaml", @@ -4742,9 +4732,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" dependencies = [ "proc-macro2", ] @@ -4905,21 +4895,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55" dependencies = [ "getrandom 0.2.5", "redox_syscall", + "thiserror", ] [[package]] @@ -4959,9 +4950,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64 0.13.0", "bytes", @@ -4991,7 +4982,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.7.0", + "winreg 0.10.1", ] [[package]] @@ -5435,7 +5426,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug", ] @@ -5447,7 +5438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -5459,7 +5450,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug", ] @@ -5471,7 +5462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -5530,7 +5521,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -5563,7 +5554,7 @@ dependencies = [ "bincode", "byteorder", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "filesystem", "flate2", "lazy_static", @@ -5582,7 +5573,7 @@ dependencies = [ "sloggers", "tempfile", "tree_hash", - "tree_hash_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tree_hash_derive", "types", ] @@ -5642,14 +5633,14 @@ dependencies = [ [[package]] name = "slog-json" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f825ce7346f40aa318111df5d3a94945a7fdca9081584cb9b05692fb3dfcb4" +checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" dependencies = [ "serde", "serde_json", "slog", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -5675,9 +5666,9 @@ dependencies = [ [[package]] name = "slog-stdlog" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8228ab7302adbf4fcb37e66f3cda78003feb521e7fd9e3847ec117a7784d0f5a" +checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" dependencies = [ "log", "slog", @@ -5694,7 +5685,7 @@ dependencies = [ "slog", "term", "thread_local", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -5882,7 +5873,7 @@ dependencies = [ "db-key", "directory", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "itertools", "lazy_static", "leveldb", @@ -5961,9 +5952,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" dependencies = [ "proc-macro2", "quote", @@ -6128,9 +6119,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "itoa 1.0.1", "libc", @@ -6141,9 +6132,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "timer" @@ -6448,11 +6439,11 @@ dependencies = [ "beacon_chain", "eth2_hashing 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "ethereum-types 0.12.1", "rand 0.7.3", "smallvec", - "tree_hash_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tree_hash_derive", "types", ] @@ -6465,17 +6456,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tree_hash_derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd22d128157837a4434bb51119aef11103f17bfe8c402ce688cf25aa1e608ad" -dependencies = [ - "darling", - "quote", - "syn", -] - [[package]] name = "trust-dns-proto" version = "0.20.4" @@ -6596,7 +6576,7 @@ dependencies = [ "eth2_interop_keypairs", "eth2_serde_utils", "eth2_ssz", - "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "eth2_ssz_derive", "eth2_ssz_types", "ethereum-types 0.12.1", "hex", @@ -6623,7 +6603,7 @@ dependencies = [ "tempfile", "test_random_derive", "tree_hash", - "tree_hash_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tree_hash_derive", ] [[package]] @@ -6983,6 +6963,12 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.79" @@ -7199,9 +7185,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -7311,9 +7297,9 @@ dependencies = [ [[package]] name = "winreg" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] @@ -7361,14 +7347,14 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d4c1dd079043fe673e79fe3c3a260ae2d2fb413f1062cae9e062748df0df03" +checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index aee6755da..4b1132ba6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,8 @@ members = [ fixed-hash = { git = "https://github.com/paritytech/parity-common", rev="df638ab0885293d21d656dc300d39236b69ce57d" } warp = { git = "https://github.com/macladson/warp", rev ="7e75acc" } eth2_ssz = { path = "consensus/ssz" } +eth2_ssz_derive = { path = "consensus/ssz_derive" } eth2_ssz_types = { path = "consensus/ssz_types" } tree_hash = { path = "consensus/tree_hash" } +tree_hash_derive = { path = "consensus/tree_hash_derive" } eth2_serde_utils = { path = "consensus/serde_utils" } diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 2628d1436..3347d5dec 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -59,7 +59,7 @@ strum = { version = "0.21.0", features = ["derive"] } logging = { path = "../../common/logging" } execution_layer = { path = "../execution_layer" } sensitive_url = { path = "../../common/sensitive_url" } -superstruct = "0.4.0" +superstruct = "0.4.1" [[test]] name = "beacon_chain_tests" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 7bc47815d..50daac83b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -80,6 +80,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::collections::HashSet; use std::io::prelude::*; +use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; use store::iter::{BlockRootsIterator, ParentRootBlockIterator, StateRootsIterator}; @@ -370,7 +371,7 @@ pub struct BeaconChain { pub validator_monitor: RwLock>, } -type BeaconBlockAndState = (BeaconBlock, BeaconState); +type BeaconBlockAndState = (BeaconBlock, BeaconState); impl BeaconChain { /// Persists the head tracker and fork choice. @@ -1151,7 +1152,7 @@ impl BeaconChain { .body() .execution_payload() .ok() - .map(|ep| ep.block_hash), + .map(|ep| ep.block_hash()), random, }) }) @@ -2892,12 +2893,12 @@ impl BeaconChain { /// /// The produced block will not be inherently valid, it must be signed by a block producer. /// Block signing is out of the scope of this function and should be done by a separate program. - pub fn produce_block( + pub fn produce_block>( &self, randao_reveal: Signature, slot: Slot, validator_graffiti: Option, - ) -> Result, BlockProductionError> { + ) -> Result, BlockProductionError> { self.produce_block_with_verification( randao_reveal, slot, @@ -2907,13 +2908,13 @@ impl BeaconChain { } /// Same as `produce_block` but allowing for configuration of RANDAO-verification. - pub fn produce_block_with_verification( + pub fn produce_block_with_verification>( &self, randao_reveal: Signature, slot: Slot, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + ) -> Result, BlockProductionError> { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); @@ -2964,7 +2965,7 @@ impl BeaconChain { }; drop(state_load_timer); - self.produce_block_on_state( + self.produce_block_on_state::( state, state_root_opt, slot, @@ -2986,7 +2987,7 @@ impl BeaconChain { /// The provided `state_root_opt` should only ever be set to `Some` if the contained value is /// equal to the root of `state`. Providing this value will serve as an optimization to avoid /// performing a tree hash in some scenarios. - pub fn produce_block_on_state( + pub fn produce_block_on_state>( &self, mut state: BeaconState, state_root_opt: Option, @@ -2994,7 +2995,7 @@ impl BeaconChain { randao_reveal: Signature, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + ) -> Result, BlockProductionError> { let eth1_chain = self .eth1_chain .as_ref() @@ -3118,6 +3119,7 @@ impl BeaconChain { attestations, deposits, voluntary_exits: voluntary_exits.into(), + _phantom: PhantomData, }, }), BeaconState::Altair(_) => { @@ -3137,12 +3139,14 @@ impl BeaconChain { deposits, voluntary_exits: voluntary_exits.into(), sync_aggregate, + _phantom: PhantomData, }, }) } BeaconState::Merge(_) => { let sync_aggregate = get_sync_aggregate()?; - let execution_payload = get_execution_payload(self, &state, proposer_index)?; + let execution_payload = + get_execution_payload::(self, &state, proposer_index)?; BeaconBlock::Merge(BeaconBlockMerge { slot, proposer_index, diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 7dbad7677..968723450 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -75,6 +75,7 @@ use std::io::Write; use std::time::Duration; use store::{Error as DBError, HotColdDB, HotStateSummary, KeyValueStore, StoreOp}; use tree_hash::TreeHash; +use types::ExecPayload; use types::{ BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, ExecutionBlockHash, Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, @@ -1295,9 +1296,9 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> { if valid_merge_transition_block { info!(chain.log, "{}", POS_PANDA_BANNER); info!(chain.log, "Proof of Stake Activated"; "slot" => block.slot()); - info!(chain.log, ""; "Terminal POW Block Hash" => ?block.message().execution_payload()?.parent_hash.into_root()); + info!(chain.log, ""; "Terminal POW Block Hash" => ?block.message().execution_payload()?.parent_hash().into_root()); info!(chain.log, ""; "Merge Transition Block Root" => ?block.message().tree_hash_root()); - info!(chain.log, ""; "Merge Transition Execution Hash" => ?block.message().execution_payload()?.block_hash.into_root()); + info!(chain.log, ""; "Merge Transition Execution Hash" => ?block.message().execution_payload()?.block_hash().into_root()); } Ok(Self { diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 0ee9e4b87..d95a7a671 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -53,8 +53,9 @@ pub fn notify_new_payload( .execution_layer .as_ref() .ok_or(ExecutionPayloadError::NoExecutionConnection)?; - let new_payload_response = execution_layer - .block_on(|execution_layer| execution_layer.notify_new_payload(execution_payload)); + let new_payload_response = execution_layer.block_on(|execution_layer| { + execution_layer.notify_new_payload(&execution_payload.execution_payload) + }); match new_payload_response { Ok(status) => match status { @@ -118,10 +119,10 @@ pub fn validate_merge_block( .into()); } - if execution_payload.parent_hash != spec.terminal_block_hash { + if execution_payload.parent_hash() != spec.terminal_block_hash { return Err(ExecutionPayloadError::InvalidTerminalBlockHash { terminal_block_hash: spec.terminal_block_hash, - payload_parent_hash: execution_payload.parent_hash, + payload_parent_hash: execution_payload.parent_hash(), } .into()); } @@ -136,14 +137,14 @@ pub fn validate_merge_block( let is_valid_terminal_pow_block = execution_layer .block_on(|execution_layer| { - execution_layer.is_valid_terminal_pow_block_hash(execution_payload.parent_hash, spec) + execution_layer.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec) }) .map_err(ExecutionPayloadError::from)?; match is_valid_terminal_pow_block { Some(true) => Ok(()), Some(false) => Err(ExecutionPayloadError::InvalidTerminalPoWBlock { - parent_hash: execution_payload.parent_hash, + parent_hash: execution_payload.parent_hash(), } .into()), None => { @@ -167,7 +168,7 @@ pub fn validate_merge_block( debug!( chain.log, "Optimistically accepting terminal block"; - "block_hash" => ?execution_payload.parent_hash, + "block_hash" => ?execution_payload.parent_hash(), "msg" => "the terminal block/parent was unavailable" ); Ok(()) @@ -215,11 +216,11 @@ pub fn validate_execution_payload_for_gossip( ))?; // The block's execution payload timestamp is correct with respect to the slot - if execution_payload.timestamp != expected_timestamp { + if execution_payload.timestamp() != expected_timestamp { return Err(BlockError::ExecutionPayloadError( ExecutionPayloadError::InvalidPayloadTimestamp { expected: expected_timestamp, - found: execution_payload.timestamp, + found: execution_payload.timestamp(), }, )); } @@ -241,20 +242,23 @@ pub fn validate_execution_payload_for_gossip( /// Equivalent to the `get_execution_payload` function in the Validator Guide: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal -pub fn get_execution_payload( +pub fn get_execution_payload>( chain: &BeaconChain, state: &BeaconState, proposer_index: u64, -) -> Result, BlockProductionError> { - Ok(prepare_execution_payload_blocking(chain, state, proposer_index)?.unwrap_or_default()) +) -> Result { + Ok( + prepare_execution_payload_blocking::(chain, state, proposer_index)? + .unwrap_or_default(), + ) } /// Wraps the async `prepare_execution_payload` function as a blocking task. -pub fn prepare_execution_payload_blocking( +pub fn prepare_execution_payload_blocking>( chain: &BeaconChain, state: &BeaconState, proposer_index: u64, -) -> Result>, BlockProductionError> { +) -> Result, BlockProductionError> { let execution_layer = chain .execution_layer .as_ref() @@ -262,7 +266,7 @@ pub fn prepare_execution_payload_blocking( execution_layer .block_on_generic(|_| async { - prepare_execution_payload(chain, state, proposer_index).await + prepare_execution_payload::(chain, state, proposer_index).await }) .map_err(BlockProductionError::BlockingFailed)? } @@ -281,11 +285,11 @@ pub fn prepare_execution_payload_blocking( /// Equivalent to the `prepare_execution_payload` function in the Validator Guide: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal -pub async fn prepare_execution_payload( +pub async fn prepare_execution_payload>( chain: &BeaconChain, state: &BeaconState, proposer_index: u64, -) -> Result>, BlockProductionError> { +) -> Result, BlockProductionError> { let spec = &chain.spec; let execution_layer = chain .execution_layer @@ -335,12 +339,12 @@ pub async fn prepare_execution_payload( .body() .execution_payload() .ok() - .map(|ep| ep.block_hash) + .map(|ep| ep.block_hash()) }; // Note: the suggested_fee_recipient is stored in the `execution_layer`, it will add this parameter. let execution_payload = execution_layer - .get_payload( + .get_payload::( parent_hash, timestamp, random, diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 993a13d6c..9acfba17b 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -12,6 +12,7 @@ use state_processing::{ per_block_processing::{per_block_processing, BlockSignatureStrategy}, per_slot_processing, BlockProcessingError, VerifyBlockRoot, }; +use std::marker::PhantomData; use std::sync::Arc; use tempfile::tempdir; use types::{test_utils::generate_deterministic_keypair, *}; @@ -962,6 +963,7 @@ fn add_base_block_to_altair_chain() { attestations: altair_body.attestations.clone(), deposits: altair_body.deposits.clone(), voluntary_exits: altair_body.voluntary_exits.clone(), + _phantom: PhantomData, }, }, signature: Signature::empty(), @@ -1082,6 +1084,7 @@ fn add_altair_block_to_base_chain() { deposits: base_body.deposits.clone(), voluntary_exits: base_body.voluntary_exits.clone(), sync_aggregate: SyncAggregate::empty(), + _phantom: PhantomData, }, }, signature: Signature::empty(), diff --git a/beacon_node/beacon_chain/tests/merge.rs b/beacon_node/beacon_chain/tests/merge.rs index d3ef3ea5e..d67ed35f9 100644 --- a/beacon_node/beacon_chain/tests/merge.rs +++ b/beacon_node/beacon_chain/tests/merge.rs @@ -8,17 +8,20 @@ const VALIDATOR_COUNT: usize = 32; type E = MainnetEthSpec; -fn verify_execution_payload_chain(chain: &[ExecutionPayload]) { - let mut prev_ep: Option> = None; +fn verify_execution_payload_chain(chain: &[FullPayload]) { + let mut prev_ep: Option> = None; for ep in chain { - assert!(*ep != ExecutionPayload::default()); - assert!(ep.block_hash != ExecutionBlockHash::zero()); + assert!(*ep != FullPayload::default()); + assert!(ep.block_hash() != ExecutionBlockHash::zero()); // Check against previous `ExecutionPayload`. if let Some(prev_ep) = prev_ep { - assert_eq!(prev_ep.block_hash, ep.parent_hash); - assert_eq!(prev_ep.block_number + 1, ep.block_number); + assert_eq!(prev_ep.block_hash(), ep.execution_payload.parent_hash); + assert_eq!( + prev_ep.execution_payload.block_number + 1, + ep.execution_payload.block_number + ); } prev_ep = Some(ep.clone()); } @@ -83,12 +86,12 @@ fn merge_with_terminal_block_hash_override() { let execution_payload = block.message().body().execution_payload().unwrap().clone(); if i == 0 { - assert_eq!(execution_payload.block_hash, genesis_pow_block_hash); + assert_eq!(execution_payload.block_hash(), genesis_pow_block_hash); } execution_payloads.push(execution_payload); } - verify_execution_payload_chain(&execution_payloads); + verify_execution_payload_chain(execution_payloads.as_slice()); } #[test] @@ -138,7 +141,7 @@ fn base_altair_merge_with_terminal_block_after_fork() { assert_eq!(merge_head.slot(), merge_fork_slot); assert_eq!( *merge_head.message().body().execution_payload().unwrap(), - ExecutionPayload::default() + FullPayload::default() ); /* @@ -154,7 +157,7 @@ fn base_altair_merge_with_terminal_block_after_fork() { .body() .execution_payload() .unwrap(), - ExecutionPayload::default() + FullPayload::default() ); assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 1); @@ -178,5 +181,5 @@ fn base_altair_merge_with_terminal_block_after_fork() { execution_payloads.push(block.message().body().execution_payload().unwrap().clone()); } - verify_execution_payload_chain(&execution_payloads); + verify_execution_payload_chain(execution_payloads.as_slice()); } diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 4d2dfccac..70482dfd4 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -72,7 +72,7 @@ impl InvalidPayloadRig { .body() .execution_payload() .unwrap() - .block_hash + .block_hash() } fn execution_status(&self, block_root: Hash256) -> ExecutionStatus { diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 51c689ac6..ad14ceb51 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -1,18 +1,21 @@ +use crate::engines::ForkChoiceState; use async_trait::async_trait; use eth1::http::RpcError; +pub use json_structures::TransitionConfigurationV1; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; - -pub const LATEST_TAG: &str = "latest"; - -use crate::engines::ForkChoiceState; -pub use json_structures::TransitionConfigurationV1; -pub use types::{Address, EthSpec, ExecutionBlockHash, ExecutionPayload, Hash256, Uint256}; +use slog::Logger; +pub use types::{ + Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, Hash256, + Uint256, +}; pub mod auth; pub mod http; pub mod json_structures; +pub const LATEST_TAG: &str = "latest"; + pub type PayloadId = [u8; 8]; #[derive(Debug)] @@ -24,7 +27,10 @@ pub enum Error { InvalidExecutePayloadResponse(&'static str), JsonRpc(RpcError), Json(serde_json::Error), - ServerMessage { code: i64, message: String }, + ServerMessage { + code: i64, + message: String, + }, Eip155Failure, IsSyncing, ExecutionBlockNotFound(ExecutionBlockHash), @@ -32,6 +38,14 @@ pub enum Error { ParentHashEqualsBlockHash(ExecutionBlockHash), PayloadIdUnavailable, TransitionConfigurationMismatch, + PayloadConversionLogicFlaw, + InvalidBuilderQuery, + MissingPayloadId { + parent_hash: ExecutionBlockHash, + timestamp: u64, + prev_randao: Hash256, + suggested_fee_recipient: Address, + }, } impl From for Error { @@ -59,41 +73,17 @@ impl From for Error { } } -/// A generic interface for an execution engine API. +pub struct EngineApi; +pub struct BuilderApi; + #[async_trait] -pub trait EngineApi { - async fn upcheck(&self) -> Result<(), Error>; - - async fn get_block_by_number<'a>( - &self, - block_by_number: BlockByNumberQuery<'a>, - ) -> Result, Error>; - - async fn get_block_by_hash<'a>( - &self, - block_hash: ExecutionBlockHash, - ) -> Result, Error>; - - async fn new_payload_v1( - &self, - execution_payload: ExecutionPayload, - ) -> Result; - - async fn get_payload_v1( - &self, - payload_id: PayloadId, - ) -> Result, Error>; - - async fn forkchoice_updated_v1( +pub trait Builder { + async fn notify_forkchoice_updated( &self, forkchoice_state: ForkChoiceState, payload_attributes: Option, + log: &Logger, ) -> Result; - - async fn exchange_transition_configuration_v1( - &self, - transition_configuration: TransitionConfigurationV1, - ) -> Result; } #[derive(Clone, Copy, Debug, PartialEq)] @@ -142,3 +132,17 @@ pub struct ForkchoiceUpdatedResponse { pub payload_status: PayloadStatusV1, pub payload_id: Option, } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ProposeBlindedBlockResponseStatus { + Valid, + Invalid, + Syncing, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProposeBlindedBlockResponse { + pub status: ProposeBlindedBlockResponseStatus, + pub latest_valid_hash: Option, + pub validation_error: Option, +} diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index fc0288bbc..718b08534 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -3,14 +3,14 @@ use super::*; use crate::auth::Auth; use crate::json_structures::*; -use async_trait::async_trait; use eth1::http::EIP155_ERROR_STR; use reqwest::header::CONTENT_TYPE; use sensitive_url::SensitiveUrl; use serde::de::DeserializeOwned; use serde_json::json; +use std::marker::PhantomData; use std::time::Duration; -use types::EthSpec; +use types::{BlindedPayload, EthSpec, ExecutionPayloadHeader, SignedBeaconBlock}; pub use reqwest::Client; @@ -42,18 +42,26 @@ pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1: &str = pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT: Duration = Duration::from_millis(500); -pub struct HttpJsonRpc { +pub const BUILDER_GET_PAYLOAD_HEADER_V1: &str = "builder_getPayloadHeaderV1"; +pub const BUILDER_GET_PAYLOAD_HEADER_TIMEOUT: Duration = Duration::from_secs(2); + +pub const BUILDER_PROPOSE_BLINDED_BLOCK_V1: &str = "builder_proposeBlindedBlockV1"; +pub const BUILDER_PROPOSE_BLINDED_BLOCK_TIMEOUT: Duration = Duration::from_secs(2); + +pub struct HttpJsonRpc { pub client: Client, pub url: SensitiveUrl, auth: Option, + _phantom: PhantomData, } -impl HttpJsonRpc { +impl HttpJsonRpc { pub fn new(url: SensitiveUrl) -> Result { Ok(Self { client: Client::builder().build()?, url, auth: None, + _phantom: PhantomData, }) } @@ -62,15 +70,16 @@ impl HttpJsonRpc { client: Client::builder().build()?, url, auth: Some(auth), + _phantom: PhantomData, }) } - pub async fn rpc_request( + pub async fn rpc_request( &self, method: &str, params: serde_json::Value, timeout: Duration, - ) -> Result { + ) -> Result { let body = JsonRequestBody { jsonrpc: JSONRPC_VERSION, method, @@ -108,9 +117,8 @@ impl HttpJsonRpc { } } -#[async_trait] -impl EngineApi for HttpJsonRpc { - async fn upcheck(&self) -> Result<(), Error> { +impl HttpJsonRpc { + pub async fn upcheck(&self) -> Result<(), Error> { let result: serde_json::Value = self .rpc_request(ETH_SYNCING, json!([]), ETH_SYNCING_TIMEOUT) .await?; @@ -127,7 +135,7 @@ impl EngineApi for HttpJsonRpc { } } - async fn get_block_by_number<'a>( + pub async fn get_block_by_number<'a>( &self, query: BlockByNumberQuery<'a>, ) -> Result, Error> { @@ -141,7 +149,7 @@ impl EngineApi for HttpJsonRpc { .await } - async fn get_block_by_hash<'a>( + pub async fn get_block_by_hash<'a>( &self, block_hash: ExecutionBlockHash, ) -> Result, Error> { @@ -151,7 +159,7 @@ impl EngineApi for HttpJsonRpc { .await } - async fn new_payload_v1( + pub async fn new_payload_v1( &self, execution_payload: ExecutionPayload, ) -> Result { @@ -164,7 +172,7 @@ impl EngineApi for HttpJsonRpc { Ok(response.into()) } - async fn get_payload_v1( + pub async fn get_payload_v1( &self, payload_id: PayloadId, ) -> Result, Error> { @@ -177,7 +185,7 @@ impl EngineApi for HttpJsonRpc { Ok(response.into()) } - async fn forkchoice_updated_v1( + pub async fn forkchoice_updated_v1( &self, forkchoice_state: ForkChoiceState, payload_attributes: Option, @@ -198,7 +206,7 @@ impl EngineApi for HttpJsonRpc { Ok(response.into()) } - async fn exchange_transition_configuration_v1( + pub async fn exchange_transition_configuration_v1( &self, transition_configuration: TransitionConfigurationV1, ) -> Result { @@ -216,6 +224,62 @@ impl EngineApi for HttpJsonRpc { } } +impl HttpJsonRpc { + pub async fn get_payload_header_v1( + &self, + payload_id: PayloadId, + ) -> Result, Error> { + let params = json!([JsonPayloadIdRequest::from(payload_id)]); + + let response: JsonExecutionPayloadHeaderV1 = self + .rpc_request( + BUILDER_GET_PAYLOAD_HEADER_V1, + params, + BUILDER_GET_PAYLOAD_HEADER_TIMEOUT, + ) + .await?; + + Ok(response.into()) + } + + pub async fn forkchoice_updated_v1( + &self, + forkchoice_state: ForkChoiceState, + payload_attributes: Option, + ) -> Result { + let params = json!([ + JsonForkChoiceStateV1::from(forkchoice_state), + payload_attributes.map(JsonPayloadAttributesV1::from) + ]); + + let response: JsonForkchoiceUpdatedV1Response = self + .rpc_request( + ENGINE_FORKCHOICE_UPDATED_V1, + params, + ENGINE_FORKCHOICE_UPDATED_TIMEOUT, + ) + .await?; + + Ok(response.into()) + } + + pub async fn propose_blinded_block_v1( + &self, + block: SignedBeaconBlock>, + ) -> Result, Error> { + let params = json!([block]); + + let response: JsonExecutionPayloadV1 = self + .rpc_request( + BUILDER_PROPOSE_BLINDED_BLOCK_V1, + params, + BUILDER_PROPOSE_BLINDED_BLOCK_TIMEOUT, + ) + .await?; + + Ok(response.into()) + } +} #[cfg(test)] mod test { use super::auth::JwtKey; @@ -224,7 +288,7 @@ mod test { use std::future::Future; use std::str::FromStr; use std::sync::Arc; - use types::{MainnetEthSpec, Transaction, Unsigned, VariableList}; + use types::{MainnetEthSpec, Transactions, Unsigned, VariableList}; struct Tester { server: MockServer, @@ -326,10 +390,7 @@ mod test { const LOGS_BLOOM_01: &str = "0x01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; fn encode_transactions( - transactions: VariableList< - Transaction, - E::MaxTransactionsPerPayload, - >, + transactions: Transactions, ) -> Result { let ep: JsonExecutionPayloadV1 = JsonExecutionPayloadV1 { transactions, @@ -341,10 +402,7 @@ mod test { fn decode_transactions( transactions: serde_json::Value, - ) -> Result< - VariableList, E::MaxTransactionsPerPayload>, - serde_json::Error, - > { + ) -> Result, serde_json::Error> { let mut json = json!({ "parentHash": HASH_00, "feeRecipient": ADDRESS_01, @@ -370,7 +428,7 @@ mod test { fn assert_transactions_serde( name: &str, - as_obj: VariableList, E::MaxTransactionsPerPayload>, + as_obj: Transactions, as_json: serde_json::Value, ) { assert_eq!( @@ -388,9 +446,7 @@ mod test { } /// Example: if `spec == &[1, 1]`, then two one-byte transactions will be created. - fn generate_transactions( - spec: &[usize], - ) -> VariableList, E::MaxTransactionsPerPayload> { + fn generate_transactions(spec: &[usize]) -> Transactions { let mut txs = VariableList::default(); for &num_bytes in spec { @@ -860,7 +916,7 @@ mod test { extra_data: vec![].into(), base_fee_per_gas: Uint256::from(7), block_hash: ExecutionBlockHash::from_str("0x6359b8381a370e2f54072a5784ddd78b6ed024991558c511d4452eb4f6ac898c").unwrap(), - transactions: vec![].into(), + transactions: vec![].into(), }; assert_eq!(payload, expected); diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index c95113092..5414c5262 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1,6 +1,9 @@ use super::*; use serde::{Deserialize, Serialize}; -use types::{EthSpec, ExecutionBlockHash, FixedVector, Transaction, Unsigned, VariableList}; +use types::{ + EthSpec, ExecutionBlockHash, ExecutionPayloadHeader, FixedVector, Transaction, Unsigned, + VariableList, +}; #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -55,6 +58,70 @@ pub struct JsonPayloadIdResponse { pub payload_id: PayloadId, } +#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(bound = "T: EthSpec", rename_all = "camelCase")] +pub struct JsonExecutionPayloadHeaderV1 { + pub parent_hash: ExecutionBlockHash, + pub fee_recipient: Address, + pub state_root: Hash256, + pub receipts_root: Hash256, + #[serde(with = "serde_logs_bloom")] + pub logs_bloom: FixedVector, + pub prev_randao: Hash256, + #[serde(with = "eth2_serde_utils::u64_hex_be")] + pub block_number: u64, + #[serde(with = "eth2_serde_utils::u64_hex_be")] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::u64_hex_be")] + pub gas_used: u64, + #[serde(with = "eth2_serde_utils::u64_hex_be")] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + pub base_fee_per_gas: Uint256, + pub block_hash: ExecutionBlockHash, + pub transactions_root: Hash256, +} + +impl From> for ExecutionPayloadHeader { + fn from(e: JsonExecutionPayloadHeaderV1) -> Self { + // Use this verbose deconstruction pattern to ensure no field is left unused. + let JsonExecutionPayloadHeaderV1 { + parent_hash, + fee_recipient, + state_root, + receipts_root, + logs_bloom, + prev_randao, + block_number, + gas_limit, + gas_used, + timestamp, + extra_data, + base_fee_per_gas, + block_hash, + transactions_root, + } = e; + + Self { + parent_hash, + fee_recipient, + state_root, + receipts_root, + logs_bloom, + prev_randao, + block_number, + gas_limit, + gas_used, + timestamp, + extra_data, + base_fee_per_gas, + block_hash, + transactions_root, + } + } +} + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(bound = "T: EthSpec", rename_all = "camelCase")] pub struct JsonExecutionPayloadV1 { @@ -77,7 +144,7 @@ pub struct JsonExecutionPayloadV1 { pub extra_data: VariableList, pub base_fee_per_gas: Uint256, pub block_hash: ExecutionBlockHash, - #[serde(with = "serde_transactions")] + #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: VariableList, T::MaxTransactionsPerPayload>, } @@ -363,6 +430,59 @@ impl From for JsonForkchoiceUpdatedV1Response { } } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum JsonProposeBlindedBlockResponseStatus { + Valid, + Invalid, + Syncing, +} +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(bound = "E: EthSpec")] +pub struct JsonProposeBlindedBlockResponse { + pub result: ExecutionPayload, + pub error: Option, +} + +impl From> for ExecutionPayload { + fn from(j: JsonProposeBlindedBlockResponse) -> Self { + let JsonProposeBlindedBlockResponse { result, error: _ } = j; + result + } +} + +impl From for ProposeBlindedBlockResponseStatus { + fn from(j: JsonProposeBlindedBlockResponseStatus) -> Self { + match j { + JsonProposeBlindedBlockResponseStatus::Valid => { + ProposeBlindedBlockResponseStatus::Valid + } + JsonProposeBlindedBlockResponseStatus::Invalid => { + ProposeBlindedBlockResponseStatus::Invalid + } + JsonProposeBlindedBlockResponseStatus::Syncing => { + ProposeBlindedBlockResponseStatus::Syncing + } + } + } +} +impl From for JsonProposeBlindedBlockResponseStatus { + fn from(f: ProposeBlindedBlockResponseStatus) -> Self { + match f { + ProposeBlindedBlockResponseStatus::Valid => { + JsonProposeBlindedBlockResponseStatus::Valid + } + ProposeBlindedBlockResponseStatus::Invalid => { + JsonProposeBlindedBlockResponseStatus::Invalid + } + ProposeBlindedBlockResponseStatus::Syncing => { + JsonProposeBlindedBlockResponseStatus::Syncing + } + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransitionConfigurationV1 { @@ -400,75 +520,3 @@ pub mod serde_logs_bloom { .map_err(|e| serde::de::Error::custom(format!("invalid logs bloom: {:?}", e))) } } - -/// Serializes the `transactions` field of an `ExecutionPayload`. -pub mod serde_transactions { - use super::*; - use eth2_serde_utils::hex; - use serde::ser::SerializeSeq; - use serde::{de, Deserializer, Serializer}; - use std::marker::PhantomData; - - type Value = VariableList, N>; - - #[derive(Default)] - pub struct ListOfBytesListVisitor { - _phantom_m: PhantomData, - _phantom_n: PhantomData, - } - - impl<'a, M: Unsigned, N: Unsigned> serde::de::Visitor<'a> for ListOfBytesListVisitor { - type Value = Value; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "a list of 0x-prefixed byte lists") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'a>, - { - let mut outer = VariableList::default(); - - while let Some(val) = seq.next_element::()? { - let inner_vec = hex::decode(&val).map_err(de::Error::custom)?; - let transaction = VariableList::new(inner_vec).map_err(|e| { - serde::de::Error::custom(format!("transaction too large: {:?}", e)) - })?; - outer.push(transaction).map_err(|e| { - serde::de::Error::custom(format!("too many transactions: {:?}", e)) - })?; - } - - Ok(outer) - } - } - - pub fn serialize( - value: &Value, - serializer: S, - ) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(value.len()))?; - for transaction in value { - // It's important to match on the inner values of the transaction. Serializing the - // entire `Transaction` will result in appending the SSZ union prefix byte. The - // execution node does not want that. - let hex = hex::encode(&transaction[..]); - seq.serialize_element(&hex)?; - } - seq.end() - } - - pub fn deserialize<'de, D, M: Unsigned, N: Unsigned>( - deserializer: D, - ) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let visitor: ListOfBytesListVisitor = <_>::default(); - deserializer.deserialize_any(visitor) - } -} diff --git a/beacon_node/execution_layer/src/engines.rs b/beacon_node/execution_layer/src/engines.rs index 239672974..719db74c5 100644 --- a/beacon_node/execution_layer/src/engines.rs +++ b/beacon_node/execution_layer/src/engines.rs @@ -1,8 +1,11 @@ //! Provides generic behaviour for multiple execution engines, specifically fallback behaviour. use crate::engine_api::{ - EngineApi, Error as EngineApiError, ForkchoiceUpdatedResponse, PayloadAttributes, PayloadId, + Builder, EngineApi, Error as EngineApiError, ForkchoiceUpdatedResponse, PayloadAttributes, + PayloadId, }; +use crate::{BuilderApi, HttpJsonRpc}; +use async_trait::async_trait; use futures::future::join_all; use lru::LruCache; use slog::{crit, debug, info, warn, Logger}; @@ -58,14 +61,14 @@ struct PayloadIdCacheKey { /// An execution engine. pub struct Engine { pub id: String, - pub api: T, + pub api: HttpJsonRpc, payload_id_cache: Mutex>, state: RwLock, } impl Engine { /// Creates a new, offline engine. - pub fn new(id: String, api: T) -> Self { + pub fn new(id: String, api: HttpJsonRpc) -> Self { Self { id, api, @@ -94,8 +97,9 @@ impl Engine { } } -impl Engine { - pub async fn notify_forkchoice_updated( +#[async_trait] +impl Builder for Engine { + async fn notify_forkchoice_updated( &self, forkchoice_state: ForkChoiceState, payload_attributes: Option, @@ -124,14 +128,47 @@ impl Engine { } } +#[async_trait] +impl Builder for Engine { + async fn notify_forkchoice_updated( + &self, + forkchoice_state: ForkChoiceState, + pa: Option, + log: &Logger, + ) -> Result { + let payload_attributes = pa.ok_or(EngineApiError::InvalidBuilderQuery)?; + let response = self + .api + .forkchoice_updated_v1(forkchoice_state, Some(payload_attributes)) + .await?; + + if let Some(payload_id) = response.payload_id { + let key = PayloadIdCacheKey::new(&forkchoice_state, &payload_attributes); + self.payload_id_cache.lock().await.put(key, payload_id); + } else { + warn!( + log, + "Builder should have returned a payload_id for attributes {:?}", payload_attributes + ); + } + + Ok(response) + } +} + /// Holds multiple execution engines and provides functionality for managing them in a fallback /// manner. -pub struct Engines { - pub engines: Vec>, +pub struct Engines { + pub engines: Vec>, pub latest_forkchoice_state: RwLock>, pub log: Logger, } +pub struct Builders { + pub builders: Vec>, + pub log: Logger, +} + #[derive(Debug)] pub enum EngineError { Offline { id: String }, @@ -139,7 +176,7 @@ pub enum EngineError { Auth { id: String }, } -impl Engines { +impl Engines { async fn get_latest_forkchoice_state(&self) -> Option { *self.latest_forkchoice_state.read().await } @@ -148,7 +185,7 @@ impl Engines { *self.latest_forkchoice_state.write().await = Some(state); } - async fn send_latest_forkchoice_state(&self, engine: &Engine) { + async fn send_latest_forkchoice_state(&self, engine: &Engine) { let latest_forkchoice_state = self.get_latest_forkchoice_state().await; if let Some(forkchoice_state) = latest_forkchoice_state { @@ -286,7 +323,7 @@ impl Engines { /// it runs, it will try to upcheck all offline nodes and then run the function again. pub async fn first_success<'a, F, G, H>(&'a self, func: F) -> Result> where - F: Fn(&'a Engine) -> G + Copy, + F: Fn(&'a Engine) -> G + Copy, G: Future>, { match self.first_success_without_retry(func).await { @@ -308,12 +345,12 @@ impl Engines { /// Run `func` on all engines, in the order in which they are defined, returning the first /// successful result that is found. - async fn first_success_without_retry<'a, F, G, H>( + pub async fn first_success_without_retry<'a, F, G, H>( &'a self, func: F, ) -> Result> where - F: Fn(&'a Engine) -> G, + F: Fn(&'a Engine) -> G, G: Future>, { let mut errors = vec![]; @@ -364,7 +401,7 @@ impl Engines { /// it runs, it will try to upcheck all offline nodes and then run the function again. pub async fn broadcast<'a, F, G, H>(&'a self, func: F) -> Vec> where - F: Fn(&'a Engine) -> G + Copy, + F: Fn(&'a Engine) -> G + Copy, G: Future>, { let first_results = self.broadcast_without_retry(func).await; @@ -392,7 +429,7 @@ impl Engines { func: F, ) -> Vec> where - F: Fn(&'a Engine) -> G, + F: Fn(&'a Engine) -> G, G: Future>, { let func = &func; @@ -426,6 +463,66 @@ impl Engines { } } +impl Builders { + pub async fn first_success_without_retry<'a, F, G, H>( + &'a self, + func: F, + ) -> Result> + where + F: Fn(&'a Engine) -> G, + G: Future>, + { + let mut errors = vec![]; + + for builder in &self.builders { + match func(builder).await { + Ok(result) => return Ok(result), + Err(error) => { + debug!( + self.log, + "Builder call failed"; + "error" => ?error, + "id" => &builder.id + ); + errors.push(EngineError::Api { + id: builder.id.clone(), + error, + }) + } + } + } + + Err(errors) + } + + pub async fn broadcast_without_retry<'a, F, G, H>( + &'a self, + func: F, + ) -> Vec> + where + F: Fn(&'a Engine) -> G, + G: Future>, + { + let func = &func; + let futures = self.builders.iter().map(|engine| async move { + func(engine).await.map_err(|error| { + debug!( + self.log, + "Builder call failed"; + "error" => ?error, + "id" => &engine.id + ); + EngineError::Api { + id: engine.id.clone(), + error, + } + }) + }); + + join_all(futures).await + } +} + impl PayloadIdCacheKey { fn new(state: &ForkChoiceState, attributes: &PayloadAttributes) -> Self { Self { diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index ba4208d88..c962e58ae 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -14,6 +14,7 @@ use serde::{Deserialize, Serialize}; use slog::{crit, debug, error, info, trace, Logger}; use slot_clock::SlotClock; use std::collections::HashMap; +use std::convert::TryInto; use std::future::Future; use std::io::Write; use std::path::PathBuf; @@ -24,11 +25,15 @@ use tokio::{ sync::{Mutex, MutexGuard, RwLock}, time::{sleep, sleep_until, Instant}, }; -use types::{ChainSpec, Epoch, ExecutionBlockHash, ProposerPreparationData, Slot}; - -pub use engine_api::{ - http::HttpJsonRpc, json_structures, PayloadAttributes, PayloadStatusV1Status, +use types::{ + BlindedPayload, BlockType, ChainSpec, Epoch, ExecPayload, ExecutionBlockHash, + ProposerPreparationData, SignedBeaconBlock, Slot, }; + +use crate::engine_api::Builder; +use crate::engines::Builders; +pub use engine_api::*; +pub use engine_api::{http, http::HttpJsonRpc}; pub use payload_status::PayloadStatus; mod engine_api; @@ -59,6 +64,7 @@ const CONFIG_POLL_INTERVAL: Duration = Duration::from_secs(60); #[derive(Debug)] pub enum Error { NoEngines, + NoPayloadBuilder, ApiError(ApiError), EngineErrors(Vec), NotSynced, @@ -94,7 +100,8 @@ pub struct Proposer { } struct Inner { - engines: Engines, + engines: Engines, + builders: Builders, execution_engine_forkchoice_lock: Mutex<()>, suggested_fee_recipient: Option
, proposer_preparation_data: Mutex>, @@ -108,6 +115,8 @@ struct Inner { pub struct Config { /// Endpoint urls for EL nodes that are running the engine api. pub execution_endpoints: Vec, + /// Endpoint urls for services providing the builder api. + pub builder_endpoints: Vec, /// JWT secrets for the above endpoints running the engine api. pub secret_files: Vec, /// The default fee recipient to use on the beacon node if none if provided from @@ -148,6 +157,7 @@ impl ExecutionLayer { pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result { let Config { execution_endpoints: urls, + builder_endpoints: builder_urls, mut secret_files, suggested_fee_recipient, jwt_id, @@ -203,15 +213,24 @@ impl ExecutionLayer { .collect::>() .map_err(Error::InvalidJWTSecret)?; - let engines: Vec> = urls + let engines: Vec> = urls .into_iter() .zip(secrets.into_iter()) .map(|(url, (secret, path))| { let id = url.to_string(); let auth = Auth::new(secret, jwt_id.clone(), jwt_version.clone()); debug!(log, "Loaded execution endpoint"; "endpoint" => %id, "jwt_path" => ?path); - let api = HttpJsonRpc::new_with_auth(url, auth)?; - Ok(Engine::new(id, api)) + let api = HttpJsonRpc::::new_with_auth(url, auth)?; + Ok(Engine::::new(id, api)) + }) + .collect::>()?; + + let builders: Vec> = builder_urls + .into_iter() + .map(|url| { + let id = url.to_string(); + let api = HttpJsonRpc::::new(url)?; + Ok(Engine::::new(id, api)) }) .collect::>()?; @@ -221,6 +240,10 @@ impl ExecutionLayer { latest_forkchoice_state: <_>::default(), log: log.clone(), }, + builders: Builders { + builders, + log: log.clone(), + }, execution_engine_forkchoice_lock: <_>::default(), suggested_fee_recipient, proposer_preparation_data: Mutex::new(HashMap::new()), @@ -237,10 +260,14 @@ impl ExecutionLayer { } impl ExecutionLayer { - fn engines(&self) -> &Engines { + fn engines(&self) -> &Engines { &self.inner.engines } + fn builders(&self) -> &Builders { + &self.inner.builders + } + fn executor(&self) -> &TaskExecutor { &self.inner.executor } @@ -542,14 +569,14 @@ impl ExecutionLayer { /// /// The result will be returned from the first node that returns successfully. No more nodes /// will be contacted. - pub async fn get_payload( + pub async fn get_payload>( &self, parent_hash: ExecutionBlockHash, timestamp: u64, prev_randao: Hash256, finalized_block_hash: ExecutionBlockHash, proposer_index: u64, - ) -> Result, Error> { + ) -> Result { let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metrics::GET_PAYLOAD], @@ -557,72 +584,121 @@ impl ExecutionLayer { let suggested_fee_recipient = self.get_suggested_fee_recipient(proposer_index).await; - debug!( - self.log(), - "Issuing engine_getPayload"; - "suggested_fee_recipient" => ?suggested_fee_recipient, - "prev_randao" => ?prev_randao, - "timestamp" => timestamp, - "parent_hash" => ?parent_hash, - ); - self.engines() - .first_success(|engine| async move { - let payload_id = if let Some(id) = engine - .get_payload_id(parent_hash, timestamp, prev_randao, suggested_fee_recipient) + match Payload::block_type() { + BlockType::Blinded => { + debug!( + self.log(), + "Issuing builder_getPayloadHeader"; + "suggested_fee_recipient" => ?suggested_fee_recipient, + "prev_randao" => ?prev_randao, + "timestamp" => timestamp, + "parent_hash" => ?parent_hash, + ); + self.builders() + .first_success_without_retry(|engine| async move { + let payload_id = engine + .get_payload_id( + parent_hash, + timestamp, + prev_randao, + suggested_fee_recipient, + ) + .await + .ok_or(ApiError::MissingPayloadId { + parent_hash, + timestamp, + prev_randao, + suggested_fee_recipient, + })?; + engine + .api + .get_payload_header_v1::(payload_id) + .await? + .try_into() + .map_err(|_| ApiError::PayloadConversionLogicFlaw) + }) .await - { - // The payload id has been cached for this engine. - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_PRE_PREPARED_PAYLOAD_ID, - &[metrics::HIT], - ); - id - } else { - // The payload id has *not* been cached for this engine. Trigger an artificial - // fork choice update to retrieve a payload ID. - // - // TODO(merge): a better algorithm might try to favour a node that already had a - // cached payload id, since a payload that has had more time to produce is - // likely to be more profitable. - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_PRE_PREPARED_PAYLOAD_ID, - &[metrics::MISS], - ); - let fork_choice_state = ForkChoiceState { - head_block_hash: parent_hash, - safe_block_hash: parent_hash, - finalized_block_hash, - }; - let payload_attributes = PayloadAttributes { - timestamp, - prev_randao, - suggested_fee_recipient, - }; - - engine - .notify_forkchoice_updated( - fork_choice_state, - Some(payload_attributes), - self.log(), - ) - .await - .map(|response| response.payload_id)? - .ok_or_else(|| { - error!( - self.log(), - "Exec engine unable to produce payload"; - "msg" => "No payload ID, the engine is likely syncing. \ - This has the potential to cause a missed block proposal.", + .map_err(Error::EngineErrors) + } + BlockType::Full => { + debug!( + self.log(), + "Issuing engine_getPayload"; + "suggested_fee_recipient" => ?suggested_fee_recipient, + "prev_randao" => ?prev_randao, + "timestamp" => timestamp, + "parent_hash" => ?parent_hash, + ); + self.engines() + .first_success(|engine| async move { + let payload_id = if let Some(id) = engine + .get_payload_id( + parent_hash, + timestamp, + prev_randao, + suggested_fee_recipient, + ) + .await + { + // The payload id has been cached for this engine. + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_PRE_PREPARED_PAYLOAD_ID, + &[metrics::HIT], ); + id + } else { + // The payload id has *not* been cached for this engine. Trigger an artificial + // fork choice update to retrieve a payload ID. + // + // TODO(merge): a better algorithm might try to favour a node that already had a + // cached payload id, since a payload that has had more time to produce is + // likely to be more profitable. + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_PRE_PREPARED_PAYLOAD_ID, + &[metrics::MISS], + ); + let fork_choice_state = ForkChoiceState { + head_block_hash: parent_hash, + safe_block_hash: parent_hash, + finalized_block_hash, + }; + let payload_attributes = PayloadAttributes { + timestamp, + prev_randao, + suggested_fee_recipient, + }; - ApiError::PayloadIdUnavailable - })? - }; + engine + .notify_forkchoice_updated( + fork_choice_state, + Some(payload_attributes), + self.log(), + ) + .await + .map(|response| response.payload_id)? + .ok_or_else(|| { + error!( + self.log(), + "Exec engine unable to produce payload"; + "msg" => "No payload ID, the engine is likely syncing. \ + This has the potential to cause a missed block \ + proposal.", + ); - engine.api.get_payload_v1(payload_id).await - }) - .await - .map_err(Error::EngineErrors) + ApiError::PayloadIdUnavailable + })? + }; + + engine + .api + .get_payload_v1::(payload_id) + .await + .map(Into::into) + }) + .await + .map_err(Error::EngineErrors) + } + } } /// Maps to the `engine_newPayload` JSON-RPC call. @@ -801,10 +877,23 @@ impl ExecutionLayer { }) .await; + // Only query builders with payload attributes populated. + let builder_broadcast_results = if payload_attributes.is_some() { + self.builders() + .broadcast_without_retry(|engine| async move { + engine + .notify_forkchoice_updated(forkchoice_state, payload_attributes, self.log()) + .await + }) + .await + } else { + vec![] + }; process_multiple_payload_statuses( head_block_hash, broadcast_results .into_iter() + .chain(builder_broadcast_results.into_iter()) .map(|result| result.map(|response| response.payload_status)), self.log(), ) @@ -931,7 +1020,7 @@ impl ExecutionLayer { /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md async fn get_pow_block_hash_at_total_difficulty( &self, - engine: &Engine, + engine: &Engine, spec: &ChainSpec, ) -> Result, ApiError> { let mut block = engine @@ -1013,7 +1102,6 @@ impl ExecutionLayer { )); } } - Ok(None) }) .await; @@ -1076,7 +1164,7 @@ impl ExecutionLayer { /// https://github.com/ethereum/consensus-specs/issues/2636 async fn get_pow_block( &self, - engine: &Engine, + engine: &Engine, hash: ExecutionBlockHash, ) -> Result, ApiError> { if let Some(cached) = self.execution_blocks().await.get(&hash).copied() { @@ -1094,6 +1182,23 @@ impl ExecutionLayer { Ok(None) } } + + pub async fn propose_blinded_beacon_block( + &self, + block: &SignedBeaconBlock>, + ) -> Result, Error> { + debug!( + self.log(), + "Issuing builder_proposeBlindedBlock"; + "root" => ?block.canonical_root(), + ); + self.builders() + .first_success_without_retry(|engine| async move { + engine.api.propose_blinded_block_v1(block.clone()).await + }) + .await + .map_err(Error::EngineErrors) + } } #[cfg(test)] diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index cf8c8516f..f5a731339 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -7,7 +7,7 @@ use sensitive_url::SensitiveUrl; use std::sync::Arc; use task_executor::TaskExecutor; use tempfile::NamedTempFile; -use types::{Address, ChainSpec, Epoch, EthSpec, Hash256, Uint256}; +use types::{Address, ChainSpec, Epoch, EthSpec, FullPayload, Hash256, Uint256}; pub struct ExecutionLayerRuntime { pub runtime: Option>, @@ -154,7 +154,7 @@ impl MockExecutionLayer { let validator_index = 0; let payload = self .el - .get_payload::( + .get_payload::>( parent_hash, timestamp, prev_randao, @@ -162,7 +162,8 @@ impl MockExecutionLayer { validator_index, ) .await - .unwrap(); + .unwrap() + .execution_payload; let block_hash = payload.block_hash; assert_eq!(payload.parent_hash, parent_hash); assert_eq!(payload.block_number, block_number); diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index 7a23d128b..4ed9bdd7d 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -27,6 +27,7 @@ slot_clock = { path = "../../common/slot_clock" } eth2_ssz = "0.4.1" bs58 = "0.4.0" futures = "0.3.8" +execution_layer = {path = "../execution_layer"} parking_lot = "0.11.0" safe_arith = {path = "../../consensus/safe_arith"} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index ada4af3d2..1b8bb16c5 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -45,9 +45,10 @@ use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; use types::{ - Attestation, AttesterSlashing, BeaconStateError, CommitteeCache, ConfigAndPreset, Epoch, - EthSpec, ForkName, ProposerPreparationData, ProposerSlashing, RelativeEpoch, Signature, - SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, + Attestation, AttesterSlashing, BeaconBlockBodyMerge, BeaconBlockMerge, BeaconStateError, + BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, + ProposerPreparationData, ProposerSlashing, RelativeEpoch, Signature, SignedAggregateAndProof, + SignedBeaconBlock, SignedBeaconBlockMerge, SignedContributionAndProof, SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, }; use version::{ @@ -1022,6 +1023,116 @@ pub fn serve( }, ); + /* + * beacon/blocks + */ + + // POST beacon/blocks + let post_beacon_blinded_blocks = eth1_v1 + .and(warp::path("beacon")) + .and(warp::path("blinded_blocks")) + .and(warp::path::end()) + .and(warp::body::json()) + .and(chain_filter.clone()) + .and(network_tx_filter.clone()) + .and(log_filter.clone()) + .and_then( + |block: SignedBeaconBlock>, + chain: Arc>, + network_tx: UnboundedSender>, + _log: Logger| { + blocking_json_task(move || { + if let Some(el) = chain.execution_layer.as_ref() { + //FIXME(sean): we may not always receive the payload in this response because it + // should be the relay's job to propogate the block. However, since this block is + // already signed and sent this might be ok (so long as the relay validates + // the block before revealing the payload). + + //FIXME(sean) additionally, this endpoint should serve blocks prior to Bellatrix, and should + // be able to support the normal block proposal flow, because at some point full block endpoints + // will be deprecated from the beacon API. This will entail creating full blocks in + // `validator/blinded_blocks`, caching their payloads, and transforming them into blinded + // blocks. We will access the payload of those blocks here. This flow should happen if the + // execution layer has no payload builders or if we have not yet finalized post-merge transition. + let payload = el + .block_on(|el| el.propose_blinded_beacon_block(&block)) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "proposal failed: {:?}", + e + )) + })?; + let new_block = SignedBeaconBlock::Merge(SignedBeaconBlockMerge { + message: BeaconBlockMerge { + slot: block.message().slot(), + proposer_index: block.message().proposer_index(), + parent_root: block.message().parent_root(), + state_root: block.message().state_root(), + body: BeaconBlockBodyMerge { + randao_reveal: block.message().body().randao_reveal().clone(), + eth1_data: block.message().body().eth1_data().clone(), + graffiti: *block.message().body().graffiti(), + proposer_slashings: block + .message() + .body() + .proposer_slashings() + .clone(), + attester_slashings: block + .message() + .body() + .attester_slashings() + .clone(), + attestations: block.message().body().attestations().clone(), + deposits: block.message().body().deposits().clone(), + voluntary_exits: block + .message() + .body() + .voluntary_exits() + .clone(), + sync_aggregate: block + .message() + .body() + .sync_aggregate() + .unwrap() + .clone(), + execution_payload: payload.into(), + }, + }, + signature: block.signature().clone(), + }); + + // Send the block, regardless of whether or not it is valid. The API + // specification is very clear that this is the desired behaviour. + publish_pubsub_message( + &network_tx, + PubsubMessage::BeaconBlock(Box::new(new_block.clone())), + )?; + + match chain.process_block(new_block) { + Ok(_) => { + // Update the head since it's likely this block will become the new + // head. + chain + .fork_choice() + .map_err(warp_utils::reject::beacon_chain_error)?; + + Ok(()) + } + Err(e) => { + let msg = format!("{:?}", e); + + Err(warp_utils::reject::broadcast_without_import(msg)) + } + } + } else { + Err(warp_utils::reject::custom_server_error( + "no execution layer found".to_string(), + )) + } + }) + }, + ); + let block_id_or_err = warp::path::param::().or_else(|_| async { Err(warp_utils::reject::custom_bad_request( "Invalid block ID".to_string(), @@ -1899,7 +2010,69 @@ pub fn serve( }; let (block, _) = chain - .produce_block_with_verification( + .produce_block_with_verification::>( + randao_reveal, + slot, + query.graffiti.map(Into::into), + randao_verification, + ) + .map_err(warp_utils::reject::block_production_error)?; + let fork_name = block + .to_ref() + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + fork_versioned_response(endpoint_version, fork_name, block) + }) + }, + ); + + // GET validator/blinded_blocks/{slot} + let get_validator_blinded_blocks = any_version + .and(warp::path("validator")) + .and(warp::path("blinded_blocks")) + .and(warp::path::param::().or_else(|_| async { + Err(warp_utils::reject::custom_bad_request( + "Invalid slot".to_string(), + )) + })) + .and(warp::path::end()) + .and(not_while_syncing_filter.clone()) + .and(warp::query::()) + .and(chain_filter.clone()) + .and_then( + |endpoint_version: EndpointVersion, + slot: Slot, + query: api_types::ValidatorBlocksQuery, + chain: Arc>| { + blocking_json_task(move || { + let randao_reveal = query.randao_reveal.as_ref().map_or_else( + || { + if query.verify_randao { + Err(warp_utils::reject::custom_bad_request( + "randao_reveal is mandatory unless verify_randao=false".into(), + )) + } else { + Ok(Signature::empty()) + } + }, + |sig_bytes| { + sig_bytes.try_into().map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "randao reveal is not a valid BLS signature: {:?}", + e + )) + }) + }, + )?; + + let randao_verification = if query.verify_randao { + ProduceBlockVerification::VerifyRandao + } else { + ProduceBlockVerification::NoVerification + }; + + let (block, _) = chain + .produce_block_with_verification::>( randao_reveal, slot, query.graffiti.map(Into::into), @@ -2766,6 +2939,7 @@ pub fn serve( .or(get_node_peer_count.boxed()) .or(get_validator_duties_proposer.boxed()) .or(get_validator_blocks.boxed()) + .or(get_validator_blinded_blocks.boxed()) .or(get_validator_attestation_data.boxed()) .or(get_validator_aggregate_attestation.boxed()) .or(get_validator_sync_committee_contribution.boxed()) @@ -2791,6 +2965,7 @@ pub fn serve( .or(warp::post().and( post_beacon_blocks .boxed() + .or(post_beacon_blinded_blocks.boxed()) .or(post_beacon_pool_attestations.boxed()) .or(post_beacon_pool_attester_slashings.boxed()) .or(post_beacon_pool_proposer_slashings.boxed()) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index f3a1ccbf0..98dd3d5a5 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1902,7 +1902,7 @@ impl ApiTester { let block = self .client - .get_validator_blocks::(slot, &randao_reveal, None) + .get_validator_blocks::>(slot, &randao_reveal, None) .await .unwrap() .data; @@ -1925,7 +1925,12 @@ impl ApiTester { let block = self .client - .get_validator_blocks_with_verify_randao::(slot, None, None, Some(false)) + .get_validator_blocks_with_verify_randao::>( + slot, + None, + None, + Some(false), + ) .await .unwrap() .data; @@ -1976,13 +1981,13 @@ impl ApiTester { // Check failure with no `verify_randao` passed. self.client - .get_validator_blocks::(slot, &bad_randao_reveal, None) + .get_validator_blocks::>(slot, &bad_randao_reveal, None) .await .unwrap_err(); // Check failure with `verify_randao=true`. self.client - .get_validator_blocks_with_verify_randao::( + .get_validator_blocks_with_verify_randao::>( slot, Some(&bad_randao_reveal), None, @@ -1993,14 +1998,16 @@ impl ApiTester { // Check failure with no randao reveal provided. self.client - .get_validator_blocks_with_verify_randao::(slot, None, None, None) + .get_validator_blocks_with_verify_randao::>( + slot, None, None, None, + ) .await .unwrap_err(); // Check success with `verify_randao=false`. let block = self .client - .get_validator_blocks_with_verify_randao::( + .get_validator_blocks_with_verify_randao::>( slot, Some(&bad_randao_reveal), None, diff --git a/beacon_node/lighthouse_network/Cargo.toml b/beacon_node/lighthouse_network/Cargo.toml index 6f746705d..d1407f981 100644 --- a/beacon_node/lighthouse_network/Cargo.toml +++ b/beacon_node/lighthouse_network/Cargo.toml @@ -37,7 +37,7 @@ rand = "0.7.3" directory = { path = "../../common/directory" } regex = "1.5.5" strum = { version = "0.21.0", features = ["derive"] } -superstruct = "0.4.0" +superstruct = "0.4.1" prometheus-client = "0.15.0" unused_port = { path = "../../common/unused_port" } diff --git a/beacon_node/lighthouse_network/tests/rpc_tests.rs b/beacon_node/lighthouse_network/tests/rpc_tests.rs index 6f32e6526..a270e4044 100644 --- a/beacon_node/lighthouse_network/tests/rpc_tests.rs +++ b/beacon_node/lighthouse_network/tests/rpc_tests.rs @@ -21,11 +21,11 @@ type E = MinimalEthSpec; /// Merge block with length < max_rpc_size. fn merge_block_small(fork_context: &ForkContext) -> BeaconBlock { - let mut block = BeaconBlockMerge::empty(&E::default_spec()); + let mut block = BeaconBlockMerge::::empty(&E::default_spec()); let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(100).collect::>()); - block.body.execution_payload.transactions = txs; + block.body.execution_payload.execution_payload.transactions = txs; let block = BeaconBlock::Merge(block); assert!(block.ssz_bytes_len() <= max_rpc_size(fork_context)); @@ -36,11 +36,11 @@ fn merge_block_small(fork_context: &ForkContext) -> BeaconBlock { /// The max limit for a merge block is in the order of ~16GiB which wouldn't fit in memory. /// Hence, we generate a merge block just greater than `MAX_RPC_SIZE` to test rejection on the rpc layer. fn merge_block_large(fork_context: &ForkContext) -> BeaconBlock { - let mut block = BeaconBlockMerge::empty(&E::default_spec()); + let mut block = BeaconBlockMerge::::empty(&E::default_spec()); let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(100000).collect::>()); - block.body.execution_payload.transactions = txs; + block.body.execution_payload.execution_payload.transactions = txs; let block = BeaconBlock::Merge(block); assert!(block.ssz_bytes_len() > max_rpc_size(fork_context)); diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index e6374d820..693bebd11 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -454,6 +454,13 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .requires("merge") .takes_value(true) ) + .arg( + Arg::with_name("payload-builders") + .long("payload-builders") + .help("The URL of a service compatible with the MEV-boost API.") + .requires("merge") + .takes_value(true) + ) /* * Database purging and compaction. diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 293a33644..4ccf22e65 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -250,6 +250,14 @@ pub fn get_config( el_config.execution_endpoints = client_config.eth1.endpoints.clone(); } + if let Some(endpoints) = cli_args.value_of("payload-builders") { + el_config.builder_endpoints = endpoints + .split(',') + .map(SensitiveUrl::parse) + .collect::>() + .map_err(|e| format!("payload-builders contains an invalid URL {:?}", e))?; + } + if let Some(secrets) = cli_args.value_of("jwt-secrets") { let secret_files: Vec<_> = secrets.split(',').map(PathBuf::from).collect(); if !secret_files.is_empty() && secret_files.len() != el_config.execution_endpoints.len() diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 856097cfe..3e965a2bf 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -579,9 +579,9 @@ impl BeaconNodeHttpClient { /// `POST beacon/blocks` /// /// Returns `Ok(None)` on a 404 error. - pub async fn post_beacon_blocks( + pub async fn post_beacon_blocks>( &self, - block: &SignedBeaconBlock, + block: &SignedBeaconBlock, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; @@ -596,6 +596,26 @@ impl BeaconNodeHttpClient { Ok(()) } + /// `POST beacon/blinded_blocks` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn post_beacon_blinded_blocks>( + &self, + block: &SignedBeaconBlock, + ) -> Result<(), Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("blinded_blocks"); + + self.post_with_timeout(path, block, self.timeouts.proposal) + .await?; + + Ok(()) + } + /// Path for `v2/beacon/blocks` pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result { let mut path = self.eth_path(V2)?; @@ -1150,24 +1170,24 @@ impl BeaconNodeHttpClient { } /// `GET v2/validator/blocks/{slot}` - pub async fn get_validator_blocks( + pub async fn get_validator_blocks>( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, - ) -> Result>, Error> { + ) -> Result>, Error> { self.get_validator_blocks_with_verify_randao(slot, Some(randao_reveal), graffiti, None) .await } /// `GET v2/validator/blocks/{slot}` - pub async fn get_validator_blocks_with_verify_randao( + pub async fn get_validator_blocks_with_verify_randao>( &self, slot: Slot, randao_reveal: Option<&SignatureBytes>, graffiti: Option<&Graffiti>, verify_randao: Option, - ) -> Result>, Error> { + ) -> Result>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() @@ -1194,6 +1214,59 @@ impl BeaconNodeHttpClient { self.get(path).await } + /// `GET v2/validator/blinded_blocks/{slot}` + pub async fn get_validator_blinded_blocks>( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result>, Error> { + self.get_validator_blinded_blocks_with_verify_randao( + slot, + Some(randao_reveal), + graffiti, + None, + ) + .await + } + + /// `GET v2/validator/blocks/{slot}` + pub async fn get_validator_blinded_blocks_with_verify_randao< + T: EthSpec, + Payload: ExecPayload, + >( + &self, + slot: Slot, + randao_reveal: Option<&SignatureBytes>, + graffiti: Option<&Graffiti>, + verify_randao: Option, + ) -> Result>, Error> { + let mut path = self.eth_path(V2)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("validator") + .push("blinded_blocks") + .push(&slot.to_string()); + + if let Some(randao_reveal) = randao_reveal { + path.query_pairs_mut() + .append_pair("randao_reveal", &randao_reveal.to_string()); + } + + if let Some(graffiti) = graffiti { + path.query_pairs_mut() + .append_pair("graffiti", &graffiti.to_string()); + } + + if let Some(verify_randao) = verify_randao { + path.query_pairs_mut() + .append_pair("verify_randao", &verify_randao.to_string()); + } + + self.get(path).await + } + /// `GET validator/attestation_data?slot,committee_index` pub async fn get_validator_attestation_data( &self, diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index dfa922e5d..dbcb0e336 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -6,8 +6,8 @@ use std::marker::PhantomData; use std::time::Duration; use types::{ consts::merge::INTERVALS_PER_SLOT, AttestationShufflingId, BeaconBlock, BeaconState, - BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256, - IndexedAttestation, RelativeEpoch, SignedBeaconBlock, Slot, + BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, + Hash256, IndexedAttestation, RelativeEpoch, SignedBeaconBlock, Slot, }; #[derive(Debug)] @@ -323,7 +323,7 @@ where } else { // Assume that this payload is valid, since the anchor should be a trusted block and // state. - ExecutionStatus::Valid(message.body.execution_payload.block_hash) + ExecutionStatus::Valid(message.body.execution_payload.block_hash()) } }, ); @@ -648,7 +648,7 @@ where .map_err(Error::AfterBlockFailed)?; let execution_status = if let Ok(execution_payload) = block.body().execution_payload() { - let block_hash = execution_payload.block_hash; + let block_hash = execution_payload.block_hash(); if block_hash == ExecutionBlockHash::zero() { // The block is post-merge-fork, but pre-terminal-PoW block. We don't need to verify diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index f87756c12..89cb76e0a 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -87,9 +87,9 @@ pub enum VerifyBlockRoot { /// re-calculating the root when it is already known. Note `block_root` should be equal to the /// tree hash root of the block, NOT the signing root of the block. This function takes /// care of mixing in the domain. -pub fn per_block_processing( +pub fn per_block_processing>( state: &mut BeaconState, - signed_block: &SignedBeaconBlock, + signed_block: &SignedBeaconBlock, block_root: Option, block_signature_strategy: BlockSignatureStrategy, verify_block_root: VerifyBlockRoot, @@ -129,7 +129,12 @@ pub fn per_block_processing( BlockSignatureStrategy::VerifyRandao => VerifySignatures::False, }; - let proposer_index = process_block_header(state, block, verify_block_root, spec)?; + let proposer_index = process_block_header( + state, + block.temporary_block_header(), + verify_block_root, + spec, + )?; if verify_signatures.is_true() { verify_block_signature(state, signed_block, block_root, spec)?; @@ -172,28 +177,28 @@ pub fn per_block_processing( /// Processes the block header, returning the proposer index. pub fn process_block_header( state: &mut BeaconState, - block: BeaconBlockRef<'_, T>, + block_header: BeaconBlockHeader, verify_block_root: VerifyBlockRoot, spec: &ChainSpec, ) -> Result> { // Verify that the slots match verify!( - block.slot() == state.slot(), + block_header.slot == state.slot(), HeaderInvalid::StateSlotMismatch ); // Verify that the block is newer than the latest block header verify!( - block.slot() > state.latest_block_header().slot, + block_header.slot > state.latest_block_header().slot, HeaderInvalid::OlderThanLatestBlockHeader { - block_slot: block.slot(), + block_slot: block_header.slot, latest_block_header_slot: state.latest_block_header().slot, } ); // Verify that proposer index is the correct index - let proposer_index = block.proposer_index() as usize; - let state_proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?; + let proposer_index = block_header.proposer_index as usize; + let state_proposer_index = state.get_beacon_proposer_index(block_header.slot, spec)?; verify!( proposer_index == state_proposer_index, HeaderInvalid::ProposerIndexMismatch { @@ -205,15 +210,15 @@ pub fn process_block_header( if verify_block_root == VerifyBlockRoot::True { let expected_previous_block_root = state.latest_block_header().tree_hash_root(); verify!( - block.parent_root() == expected_previous_block_root, + block_header.parent_root == expected_previous_block_root, HeaderInvalid::ParentBlockRootMismatch { state: expected_previous_block_root, - block: block.parent_root(), + block: block_header.parent_root, } ); } - *state.latest_block_header_mut() = block.temporary_block_header(); + *state.latest_block_header_mut() = block_header; // Verify proposer is not slashed verify!( @@ -221,15 +226,15 @@ pub fn process_block_header( HeaderInvalid::ProposerSlashed(proposer_index) ); - Ok(block.proposer_index()) + Ok(proposer_index as u64) } /// Verifies the signature of a block. /// /// Spec v0.12.1 -pub fn verify_block_signature( +pub fn verify_block_signature>( state: &BeaconState, - block: &SignedBeaconBlock, + block: &SignedBeaconBlock, block_root: Option, spec: &ChainSpec, ) -> Result<(), BlockOperationError> { @@ -250,9 +255,9 @@ pub fn verify_block_signature( /// Verifies the `randao_reveal` against the block's proposer pubkey and updates /// `state.latest_randao_mixes`. -pub fn process_randao( +pub fn process_randao>( state: &mut BeaconState, - block: BeaconBlockRef<'_, T>, + block: BeaconBlockRef<'_, T, Payload>, verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { @@ -314,34 +319,34 @@ pub fn get_new_eth1_data( /// Contains a partial set of checks from the `process_execution_payload` function: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/beacon-chain.md#process_execution_payload -pub fn partially_verify_execution_payload( +pub fn partially_verify_execution_payload>( state: &BeaconState, - payload: &ExecutionPayload, + payload: &Payload, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { if is_merge_transition_complete(state) { block_verify!( - payload.parent_hash == state.latest_execution_payload_header()?.block_hash, + payload.parent_hash() == state.latest_execution_payload_header()?.block_hash, BlockProcessingError::ExecutionHashChainIncontiguous { expected: state.latest_execution_payload_header()?.block_hash, - found: payload.parent_hash, + found: payload.parent_hash(), } ); } block_verify!( - payload.prev_randao == *state.get_randao_mix(state.current_epoch())?, + payload.prev_randao() == *state.get_randao_mix(state.current_epoch())?, BlockProcessingError::ExecutionRandaoMismatch { expected: *state.get_randao_mix(state.current_epoch())?, - found: payload.prev_randao, + found: payload.prev_randao(), } ); let timestamp = compute_timestamp_at_slot(state, spec)?; block_verify!( - payload.timestamp == timestamp, + payload.timestamp() == timestamp, BlockProcessingError::ExecutionInvalidTimestamp { expected: timestamp, - found: payload.timestamp, + found: payload.timestamp(), } ); @@ -355,29 +360,14 @@ pub fn partially_verify_execution_payload( /// Partially equivalent to the `process_execution_payload` function: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/beacon-chain.md#process_execution_payload -pub fn process_execution_payload( +pub fn process_execution_payload>( state: &mut BeaconState, - payload: &ExecutionPayload, + payload: &Payload, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { partially_verify_execution_payload(state, payload, spec)?; - *state.latest_execution_payload_header_mut()? = ExecutionPayloadHeader { - parent_hash: payload.parent_hash, - fee_recipient: payload.fee_recipient, - state_root: payload.state_root, - receipts_root: payload.receipts_root, - logs_bloom: payload.logs_bloom.clone(), - prev_randao: payload.prev_randao, - block_number: payload.block_number, - gas_limit: payload.gas_limit, - gas_used: payload.gas_used, - timestamp: payload.timestamp, - extra_data: payload.extra_data.clone(), - base_fee_per_gas: payload.base_fee_per_gas, - block_hash: payload.block_hash, - transactions_root: payload.transactions.tree_hash_root(), - }; + *state.latest_execution_payload_header_mut()? = payload.to_execution_payload_header(); Ok(()) } @@ -394,24 +384,21 @@ pub fn is_merge_transition_complete(state: &BeaconState) -> bool .unwrap_or(false) } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/beacon-chain.md#is_merge_transition_block -pub fn is_merge_transition_block( +pub fn is_merge_transition_block>( state: &BeaconState, - body: BeaconBlockBodyRef, + body: BeaconBlockBodyRef, ) -> bool { body.execution_payload() - .map(|payload| { - !is_merge_transition_complete(state) && *payload != >::default() - }) + .map(|payload| !is_merge_transition_complete(state) && *payload != Payload::default()) .unwrap_or(false) } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/beacon-chain.md#is_execution_enabled -pub fn is_execution_enabled( +pub fn is_execution_enabled>( state: &BeaconState, - body: BeaconBlockBodyRef, + body: BeaconBlockBodyRef, ) -> bool { is_merge_transition_block(state, body) || is_merge_transition_complete(state) } - /// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/beacon-chain.md#compute_timestamp_at_slot pub fn compute_timestamp_at_slot( state: &BeaconState, diff --git a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs index 28044a462..78205ca92 100644 --- a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs +++ b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs @@ -7,7 +7,7 @@ use bls::{verify_signature_sets, PublicKey, PublicKeyBytes, SignatureSet}; use rayon::prelude::*; use std::borrow::Cow; use types::{ - BeaconState, BeaconStateError, ChainSpec, EthSpec, Hash256, IndexedAttestation, + BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, Hash256, IndexedAttestation, SignedBeaconBlock, }; @@ -117,11 +117,11 @@ where /// contains invalid signatures on deposits._ /// /// See `Self::verify` for more detail. - pub fn verify_entire_block( + pub fn verify_entire_block>( state: &'a BeaconState, get_pubkey: F, decompressor: D, - block: &'a SignedBeaconBlock, + block: &'a SignedBeaconBlock, block_root: Option, spec: &'a ChainSpec, ) -> Result<()> { @@ -131,9 +131,9 @@ where } /// Includes all signatures on the block (except the deposit signatures) for verification. - pub fn include_all_signatures( + pub fn include_all_signatures>( &mut self, - block: &'a SignedBeaconBlock, + block: &'a SignedBeaconBlock, block_root: Option, ) -> Result<()> { self.include_block_proposal(block, block_root)?; @@ -144,9 +144,9 @@ where /// Includes all signatures on the block (except the deposit signatures and the proposal /// signature) for verification. - pub fn include_all_signatures_except_proposal( + pub fn include_all_signatures_except_proposal>( &mut self, - block: &'a SignedBeaconBlock, + block: &'a SignedBeaconBlock, ) -> Result<()> { self.include_randao_reveal(block)?; self.include_proposer_slashings(block)?; @@ -160,9 +160,9 @@ where } /// Includes the block signature for `self.block` for verification. - pub fn include_block_proposal( + pub fn include_block_proposal>( &mut self, - block: &'a SignedBeaconBlock, + block: &'a SignedBeaconBlock, block_root: Option, ) -> Result<()> { let set = block_proposal_signature_set( @@ -177,7 +177,10 @@ where } /// Includes the randao signature for `self.block` for verification. - pub fn include_randao_reveal(&mut self, block: &'a SignedBeaconBlock) -> Result<()> { + pub fn include_randao_reveal>( + &mut self, + block: &'a SignedBeaconBlock, + ) -> Result<()> { let set = randao_signature_set( self.state, self.get_pubkey.clone(), @@ -189,7 +192,10 @@ where } /// Includes all signatures in `self.block.body.proposer_slashings` for verification. - pub fn include_proposer_slashings(&mut self, block: &'a SignedBeaconBlock) -> Result<()> { + pub fn include_proposer_slashings>( + &mut self, + block: &'a SignedBeaconBlock, + ) -> Result<()> { self.sets .sets .reserve(block.message().body().proposer_slashings().len() * 2); @@ -215,7 +221,10 @@ where } /// Includes all signatures in `self.block.body.attester_slashings` for verification. - pub fn include_attester_slashings(&mut self, block: &'a SignedBeaconBlock) -> Result<()> { + pub fn include_attester_slashings>( + &mut self, + block: &'a SignedBeaconBlock, + ) -> Result<()> { self.sets .sets .reserve(block.message().body().attester_slashings().len() * 2); @@ -241,9 +250,9 @@ where } /// Includes all signatures in `self.block.body.attestations` for verification. - pub fn include_attestations( + pub fn include_attestations>( &mut self, - block: &'a SignedBeaconBlock, + block: &'a SignedBeaconBlock, ) -> Result>> { self.sets .sets @@ -280,7 +289,10 @@ where } /// Includes all signatures in `self.block.body.voluntary_exits` for verification. - pub fn include_exits(&mut self, block: &'a SignedBeaconBlock) -> Result<()> { + pub fn include_exits>( + &mut self, + block: &'a SignedBeaconBlock, + ) -> Result<()> { self.sets .sets .reserve(block.message().body().voluntary_exits().len()); @@ -301,7 +313,10 @@ where } /// Include the signature of the block's sync aggregate (if it exists) for verification. - pub fn include_sync_aggregate(&mut self, block: &'a SignedBeaconBlock) -> Result<()> { + pub fn include_sync_aggregate>( + &mut self, + block: &'a SignedBeaconBlock, + ) -> Result<()> { if let Ok(sync_aggregate) = block.message().body().sync_aggregate() { if let Some(signature_set) = sync_aggregate_signature_set( &self.decompressor, diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index 0cdf54a6c..3bf22d004 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -8,9 +8,9 @@ use crate::VerifySignatures; use safe_arith::SafeArith; use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}; -pub fn process_operations<'a, T: EthSpec>( +pub fn process_operations<'a, T: EthSpec, Payload: ExecPayload>( state: &mut BeaconState, - block_body: BeaconBlockBodyRef<'a, T>, + block_body: BeaconBlockBodyRef<'a, T, Payload>, proposer_index: u64, verify_signatures: VerifySignatures, spec: &ChainSpec, @@ -217,9 +217,9 @@ pub fn process_attester_slashings( } /// Wrapper function to handle calling the correct version of `process_attestations` based on /// the fork. -pub fn process_attestations<'a, T: EthSpec>( +pub fn process_attestations<'a, T: EthSpec, Payload: ExecPayload>( state: &mut BeaconState, - block_body: BeaconBlockBodyRef<'a, T>, + block_body: BeaconBlockBodyRef<'a, T, Payload>, proposer_index: u64, verify_signatures: VerifySignatures, spec: &ChainSpec, diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index 5a89bd686..5ce1bfddd 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -8,10 +8,11 @@ use std::borrow::Cow; use tree_hash::TreeHash; use types::{ AggregateSignature, AttesterSlashing, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, - DepositData, Domain, Epoch, EthSpec, Fork, Hash256, InconsistentFork, IndexedAttestation, - ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SignedBeaconBlockHeader, SignedContributionAndProof, SignedRoot, - SignedVoluntaryExit, SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, + DepositData, Domain, Epoch, EthSpec, ExecPayload, Fork, Hash256, InconsistentFork, + IndexedAttestation, ProposerSlashing, PublicKey, PublicKeyBytes, Signature, + SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, + SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate, + SyncAggregatorSelectionData, Unsigned, }; pub type Result = std::result::Result; @@ -70,10 +71,10 @@ where } /// A signature set that is valid if a block was signed by the expected block producer. -pub fn block_proposal_signature_set<'a, T, F>( +pub fn block_proposal_signature_set<'a, T, F, Payload: ExecPayload>( state: &'a BeaconState, get_pubkey: F, - signed_block: &'a SignedBeaconBlock, + signed_block: &'a SignedBeaconBlock, block_root: Option, spec: &'a ChainSpec, ) -> Result> @@ -107,8 +108,8 @@ where /// Unlike `block_proposal_signature_set` this does **not** check that the proposer index is /// correct according to the shuffling. It should only be used if no suitable `BeaconState` is /// available. -pub fn block_proposal_signature_set_from_parts<'a, T, F>( - signed_block: &'a SignedBeaconBlock, +pub fn block_proposal_signature_set_from_parts<'a, T, F, Payload: ExecPayload>( + signed_block: &'a SignedBeaconBlock, block_root: Option, proposer_index: u64, fork: &Fork, @@ -151,10 +152,10 @@ where } /// A signature set that is valid if the block proposers randao reveal signature is correct. -pub fn randao_signature_set<'a, T, F>( +pub fn randao_signature_set<'a, T, F, Payload: ExecPayload>( state: &'a BeaconState, get_pubkey: F, - block: BeaconBlockRef<'a, T>, + block: BeaconBlockRef<'a, T, Payload>, spec: &'a ChainSpec, ) -> Result> where diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index 31d08fde1..c23f0700b 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -43,7 +43,7 @@ regex = "1.5.5" lazy_static = "1.4.0" parking_lot = "0.11.1" itertools = "0.10.0" -superstruct = "0.4.0" +superstruct = "0.4.1" serde_json = "1.0.74" smallvec = "1.8.0" diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 0026db0ee..7f83d46dd 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -9,6 +9,7 @@ use derivative::Derivative; use serde_derive::{Deserialize, Serialize}; use ssz::{Decode, DecodeError}; use ssz_derive::{Decode, Encode}; +use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -29,8 +30,8 @@ use tree_hash_derive::TreeHash; TestRandom, Derivative, ), - derivative(PartialEq, Hash(bound = "T: EthSpec")), - serde(bound = "T: EthSpec", deny_unknown_fields), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: ExecPayload")), + serde(bound = "T: EthSpec, Payload: ExecPayload", deny_unknown_fields), cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)), ), ref_attributes( @@ -41,11 +42,11 @@ use tree_hash_derive::TreeHash; #[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)] #[derivative(PartialEq, Hash(bound = "T: EthSpec"))] #[serde(untagged)] -#[serde(bound = "T: EthSpec")] +#[serde(bound = "T: EthSpec, Payload: ExecPayload")] #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] #[tree_hash(enum_behaviour = "transparent")] #[ssz(enum_behaviour = "transparent")] -pub struct BeaconBlock { +pub struct BeaconBlock = FullPayload> { #[superstruct(getter(copy))] pub slot: Slot, #[superstruct(getter(copy))] @@ -56,17 +57,17 @@ pub struct BeaconBlock { #[superstruct(getter(copy))] pub state_root: Hash256, #[superstruct(only(Base), partial_getter(rename = "body_base"))] - pub body: BeaconBlockBodyBase, + pub body: BeaconBlockBodyBase, #[superstruct(only(Altair), partial_getter(rename = "body_altair"))] - pub body: BeaconBlockBodyAltair, + pub body: BeaconBlockBodyAltair, #[superstruct(only(Merge), partial_getter(rename = "body_merge"))] - pub body: BeaconBlockBodyMerge, + pub body: BeaconBlockBodyMerge, } -impl SignedRoot for BeaconBlock {} -impl<'a, T: EthSpec> SignedRoot for BeaconBlockRef<'a, T> {} +impl> SignedRoot for BeaconBlock {} +impl<'a, T: EthSpec, Payload: ExecPayload> SignedRoot for BeaconBlockRef<'a, T, Payload> {} -impl BeaconBlock { +impl> BeaconBlock { /// Returns an empty block to be used during genesis. pub fn empty(spec: &ChainSpec) -> Self { if spec.bellatrix_fork_epoch == Some(T::genesis_epoch()) { @@ -114,12 +115,12 @@ impl BeaconBlock { } /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. - pub fn body(&self) -> BeaconBlockBodyRef<'_, T> { + pub fn body(&self) -> BeaconBlockBodyRef<'_, T, Payload> { self.to_ref().body() } /// Convenience accessor for the `body` as a `BeaconBlockBodyRefMut`. - pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T> { + pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T, Payload> { self.to_mut().body_mut() } @@ -160,7 +161,7 @@ impl BeaconBlock { fork: &Fork, genesis_validators_root: Hash256, spec: &ChainSpec, - ) -> SignedBeaconBlock { + ) -> SignedBeaconBlock { let domain = spec.get_domain( self.epoch(), Domain::BeaconProposer, @@ -173,7 +174,7 @@ impl BeaconBlock { } } -impl<'a, T: EthSpec> BeaconBlockRef<'a, T> { +impl<'a, T: EthSpec, Payload: ExecPayload> BeaconBlockRef<'a, T, Payload> { /// Returns the name of the fork pertaining to `self`. /// /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork @@ -197,7 +198,7 @@ impl<'a, T: EthSpec> BeaconBlockRef<'a, T> { } /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. - pub fn body(&self) -> BeaconBlockBodyRef<'a, T> { + pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> { match self { BeaconBlockRef::Base(block) => BeaconBlockBodyRef::Base(&block.body), BeaconBlockRef::Altair(block) => BeaconBlockBodyRef::Altair(&block.body), @@ -240,14 +241,14 @@ impl<'a, T: EthSpec> BeaconBlockRef<'a, T> { /// Extracts a reference to an execution payload from a block, returning an error if the block /// is pre-merge. - pub fn execution_payload(&self) -> Result<&ExecutionPayload, Error> { + pub fn execution_payload(&self) -> Result<&Payload, Error> { self.body().execution_payload() } } -impl<'a, T: EthSpec> BeaconBlockRefMut<'a, T> { +impl<'a, T: EthSpec, Payload: ExecPayload> BeaconBlockRefMut<'a, T, Payload> { /// Convert a mutable reference to a beacon block to a mutable ref to its body. - pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T> { + pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T, Payload> { match self { BeaconBlockRefMut::Base(block) => BeaconBlockBodyRefMut::Base(&mut block.body), BeaconBlockRefMut::Altair(block) => BeaconBlockBodyRefMut::Altair(&mut block.body), @@ -256,7 +257,7 @@ impl<'a, T: EthSpec> BeaconBlockRefMut<'a, T> { } } -impl BeaconBlockBase { +impl> BeaconBlockBase { /// Returns an empty block to be used during genesis. pub fn empty(spec: &ChainSpec) -> Self { BeaconBlockBase { @@ -277,6 +278,7 @@ impl BeaconBlockBase { attestations: VariableList::empty(), deposits: VariableList::empty(), voluntary_exits: VariableList::empty(), + _phantom: PhantomData, }, } } @@ -343,7 +345,7 @@ impl BeaconBlockBase { signature: Signature::empty(), }; - let mut block = BeaconBlockBase::::empty(spec); + let mut block = BeaconBlockBase::::empty(spec); for _ in 0..T::MaxProposerSlashings::to_usize() { block .body @@ -376,7 +378,7 @@ impl BeaconBlockBase { } } -impl BeaconBlockAltair { +impl> BeaconBlockAltair { /// Returns an empty Altair block to be used during genesis. pub fn empty(spec: &ChainSpec) -> Self { BeaconBlockAltair { @@ -398,13 +400,14 @@ impl BeaconBlockAltair { deposits: VariableList::empty(), voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), + _phantom: PhantomData, }, } } /// Return an Altair block where the block has maximum size. pub fn full(spec: &ChainSpec) -> Self { - let base_block = BeaconBlockBase::full(spec); + let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); let sync_aggregate = SyncAggregate { sync_committee_signature: AggregateSignature::empty(), sync_committee_bits: BitVector::default(), @@ -428,12 +431,13 @@ impl BeaconBlockAltair { deposit_count: 0, }, graffiti: Graffiti::default(), + _phantom: PhantomData, }, } } } -impl BeaconBlockMerge { +impl> BeaconBlockMerge { /// Returns an empty Merge block to be used during genesis. pub fn empty(spec: &ChainSpec) -> Self { BeaconBlockMerge { @@ -455,34 +459,7 @@ impl BeaconBlockMerge { deposits: VariableList::empty(), voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), - execution_payload: ExecutionPayload::empty(), - }, - } - } - - /// Return an Merge block where the block has maximum size. - pub fn full(spec: &ChainSpec) -> Self { - let altair_block = BeaconBlockAltair::full(spec); - BeaconBlockMerge { - slot: spec.genesis_slot, - proposer_index: 0, - parent_root: Hash256::zero(), - state_root: Hash256::zero(), - body: BeaconBlockBodyMerge { - proposer_slashings: altair_block.body.proposer_slashings, - attester_slashings: altair_block.body.attester_slashings, - attestations: altair_block.body.attestations, - deposits: altair_block.body.deposits, - voluntary_exits: altair_block.body.voluntary_exits, - sync_aggregate: altair_block.body.sync_aggregate, - randao_reveal: Signature::empty(), - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - deposit_count: 0, - }, - graffiti: Graffiti::default(), - execution_payload: ExecutionPayload::default(), + execution_payload: Payload::default(), }, } } diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index c4df4f277..c1db64ae0 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -4,6 +4,7 @@ use derivative::Derivative; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; +use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; @@ -25,8 +26,8 @@ use tree_hash_derive::TreeHash; TestRandom, Derivative, ), - derivative(PartialEq, Hash(bound = "T: EthSpec")), - serde(bound = "T: EthSpec", deny_unknown_fields), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: ExecPayload")), + serde(bound = "T: EthSpec, Payload: ExecPayload", deny_unknown_fields), cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)) ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), @@ -35,9 +36,9 @@ use tree_hash_derive::TreeHash; #[derive(Debug, Clone, Serialize, Deserialize, Derivative)] #[derivative(PartialEq, Hash(bound = "T: EthSpec"))] #[serde(untagged)] -#[serde(bound = "T: EthSpec")] +#[serde(bound = "T: EthSpec, Payload: ExecPayload")] #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] -pub struct BeaconBlockBody { +pub struct BeaconBlockBody = FullPayload> { pub randao_reveal: Signature, pub eth1_data: Eth1Data, pub graffiti: Graffiti, @@ -48,8 +49,17 @@ pub struct BeaconBlockBody { pub voluntary_exits: VariableList, #[superstruct(only(Altair, Merge))] pub sync_aggregate: SyncAggregate, + // We flatten the execution payload so that serde can use the name of the inner type, + // either `execution_payload` for full payloads, or `execution_payload_header` for blinded + // payloads. #[superstruct(only(Merge))] - pub execution_payload: ExecutionPayload, + #[serde(flatten)] + pub execution_payload: Payload, + #[superstruct(only(Base, Altair))] + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + pub _phantom: PhantomData, } impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> { diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index ab5e6b5ae..412e5a8df 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -6,7 +6,11 @@ use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -pub type Transaction = VariableList; +pub type Transaction = VariableList; +pub type Transactions = VariableList< + Transaction<::MaxBytesPerTransaction>, + ::MaxTransactionsPerPayload, +>; #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] #[derive( @@ -36,8 +40,7 @@ pub struct ExecutionPayload { pub base_fee_per_gas: Uint256, pub block_hash: ExecutionBlockHash, #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] - pub transactions: - VariableList, T::MaxTransactionsPerPayload>, + pub transactions: Transactions, } impl ExecutionPayload { @@ -50,9 +53,9 @@ impl ExecutionPayload { pub fn max_execution_payload_size() -> usize { // Fixed part Self::empty().as_ssz_bytes().len() - // Max size of variable length `extra_data` field - + (T::max_extra_data_bytes() * ::ssz_fixed_len()) - // Max size of variable length `transactions` field - + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) + // Max size of variable length `extra_data` field + + (T::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) } } diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 24390bcf4..01780fa1c 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -1,13 +1,16 @@ use crate::{test_utils::TestRandom, *}; +use derivative::Derivative; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; +use tree_hash::TreeHash; use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] #[derive( - Default, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, + Default, Debug, Clone, Serialize, Deserialize, Derivative, Encode, Decode, TreeHash, TestRandom, )] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] pub struct ExecutionPayloadHeader { pub parent_hash: ExecutionBlockHash, pub fee_recipient: Address, @@ -37,3 +40,24 @@ impl ExecutionPayloadHeader { Self::default() } } + +impl<'a, T: EthSpec> From<&'a ExecutionPayload> for ExecutionPayloadHeader { + fn from(payload: &'a ExecutionPayload) -> Self { + ExecutionPayloadHeader { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom.clone(), + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + } + } +} diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 6aeb6f320..908419cdd 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -70,6 +70,7 @@ pub mod config_and_preset; pub mod fork_context; pub mod participation_flags; pub mod participation_list; +pub mod payload; pub mod preset; pub mod slot_epoch; pub mod subnet_id; @@ -115,7 +116,7 @@ pub use crate::enr_fork_id::EnrForkId; pub use crate::eth1_data::Eth1Data; pub use crate::eth_spec::EthSpecId; pub use crate::execution_block_hash::ExecutionBlockHash; -pub use crate::execution_payload::{ExecutionPayload, Transaction}; +pub use crate::execution_payload::{ExecutionPayload, Transaction, Transactions}; pub use crate::execution_payload_header::ExecutionPayloadHeader; pub use crate::fork::Fork; pub use crate::fork_context::ForkContext; @@ -127,6 +128,7 @@ pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; +pub use crate::payload::{BlindedPayload, BlockType, ExecPayload, FullPayload}; pub use crate::pending_attestation::PendingAttestation; pub use crate::preset::{AltairPreset, BasePreset, BellatrixPreset}; pub use crate::proposer_preparation_data::ProposerPreparationData; diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs new file mode 100644 index 000000000..5be603c19 --- /dev/null +++ b/consensus/types/src/payload.rs @@ -0,0 +1,236 @@ +use crate::{test_utils::TestRandom, *}; +use derivative::Derivative; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; +use std::convert::TryFrom; +use std::fmt::Debug; +use std::hash::Hash; +use test_random_derive::TestRandom; +use tree_hash::TreeHash; + +pub enum BlockType { + Blinded, + Full, +} + +pub trait ExecPayload: + Encode + + Decode + + TestRandom + + TreeHash + + Default + + PartialEq + + Serialize + + DeserializeOwned + + Hash + + TryFrom> + + From> +{ + fn block_type() -> BlockType; + + /// Convert the payload into a payload header. + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader; + + // We provide a subset of field accessors, for the fields used in `consensus`. + // + // More fields can be added here if you wish. + fn parent_hash(&self) -> ExecutionBlockHash; + fn prev_randao(&self) -> Hash256; + fn timestamp(&self) -> u64; + fn block_hash(&self) -> ExecutionBlockHash; +} + +impl ExecPayload for FullPayload { + fn block_type() -> BlockType { + BlockType::Full + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + ExecutionPayloadHeader::from(&self.execution_payload) + } + + fn parent_hash(&self) -> ExecutionBlockHash { + self.execution_payload.parent_hash + } + + fn prev_randao(&self) -> Hash256 { + self.execution_payload.prev_randao + } + + fn timestamp(&self) -> u64 { + self.execution_payload.timestamp + } + + fn block_hash(&self) -> ExecutionBlockHash { + self.execution_payload.block_hash + } +} + +impl ExecPayload for BlindedPayload { + fn block_type() -> BlockType { + BlockType::Blinded + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + self.execution_payload_header.clone() + } + + fn parent_hash(&self) -> ExecutionBlockHash { + self.execution_payload_header.parent_hash + } + + fn prev_randao(&self) -> Hash256 { + self.execution_payload_header.prev_randao + } + + fn timestamp(&self) -> u64 { + self.execution_payload_header.timestamp + } + + fn block_hash(&self) -> ExecutionBlockHash { + self.execution_payload_header.block_hash + } +} + +#[derive(Default, Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +pub struct BlindedPayload { + pub execution_payload_header: ExecutionPayloadHeader, +} + +impl From> for BlindedPayload { + fn from(execution_payload_header: ExecutionPayloadHeader) -> Self { + Self { + execution_payload_header, + } + } +} + +impl From> for ExecutionPayloadHeader { + fn from(blinded: BlindedPayload) -> Self { + blinded.execution_payload_header + } +} + +impl From> for BlindedPayload { + fn from(execution_payload: ExecutionPayload) -> Self { + Self { + execution_payload_header: ExecutionPayloadHeader::from(&execution_payload), + } + } +} + +impl TreeHash for BlindedPayload { + fn tree_hash_type() -> tree_hash::TreeHashType { + >::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> Vec { + self.execution_payload_header.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + >::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.execution_payload_header.tree_hash_root() + } +} + +impl Decode for BlindedPayload { + fn is_ssz_fixed_len() -> bool { + as Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + as Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Ok(Self { + execution_payload_header: ExecutionPayloadHeader::from_ssz_bytes(bytes)?, + }) + } +} + +impl Encode for BlindedPayload { + fn is_ssz_fixed_len() -> bool { + as Encode>::is_ssz_fixed_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.execution_payload_header.ssz_append(buf) + } + + fn ssz_bytes_len(&self) -> usize { + self.execution_payload_header.ssz_bytes_len() + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, TestRandom, Derivative)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +pub struct FullPayload { + pub execution_payload: ExecutionPayload, +} + +impl From> for FullPayload { + fn from(execution_payload: ExecutionPayload) -> Self { + Self { execution_payload } + } +} + +impl TryFrom> for FullPayload { + type Error = (); + + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(()) + } +} + +impl TreeHash for FullPayload { + fn tree_hash_type() -> tree_hash::TreeHashType { + >::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> Vec { + self.execution_payload.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + >::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.execution_payload.tree_hash_root() + } +} + +impl Decode for FullPayload { + fn is_ssz_fixed_len() -> bool { + as Decode>::is_ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Ok(FullPayload { + execution_payload: Decode::from_ssz_bytes(bytes)?, + }) + } +} + +impl Encode for FullPayload { + fn is_ssz_fixed_len() -> bool { + as Encode>::is_ssz_fixed_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.execution_payload.ssz_append(buf) + } + + fn ssz_bytes_len(&self) -> usize { + self.execution_payload.ssz_bytes_len() + } +} diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 8d7df0cb0..da191dbff 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -52,27 +52,27 @@ impl From for Hash256 { ), derivative(PartialEq, Hash(bound = "E: EthSpec")), cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)), - serde(bound = "E: EthSpec") + serde(bound = "E: EthSpec, Payload: ExecPayload"), ) )] #[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)] #[derivative(PartialEq, Hash(bound = "E: EthSpec"))] #[serde(untagged)] -#[serde(bound = "E: EthSpec")] +#[serde(bound = "E: EthSpec, Payload: ExecPayload")] #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] #[tree_hash(enum_behaviour = "transparent")] #[ssz(enum_behaviour = "transparent")] -pub struct SignedBeaconBlock { +pub struct SignedBeaconBlock = FullPayload> { #[superstruct(only(Base), partial_getter(rename = "message_base"))] - pub message: BeaconBlockBase, + pub message: BeaconBlockBase, #[superstruct(only(Altair), partial_getter(rename = "message_altair"))] - pub message: BeaconBlockAltair, + pub message: BeaconBlockAltair, #[superstruct(only(Merge), partial_getter(rename = "message_merge"))] - pub message: BeaconBlockMerge, + pub message: BeaconBlockMerge, pub signature: Signature, } -impl SignedBeaconBlock { +impl> SignedBeaconBlock { /// Returns the name of the fork pertaining to `self`. /// /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork @@ -94,7 +94,7 @@ impl SignedBeaconBlock { /// SSZ decode with custom decode function. pub fn from_ssz_bytes_with( bytes: &[u8], - block_decoder: impl FnOnce(&[u8]) -> Result, ssz::DecodeError>, + block_decoder: impl FnOnce(&[u8]) -> Result, ssz::DecodeError>, ) -> Result { // We need the customer decoder for `BeaconBlock`, which doesn't compose with the other // SSZ utils, so we duplicate some parts of `ssz_derive` here. @@ -113,7 +113,7 @@ impl SignedBeaconBlock { } /// Create a new `SignedBeaconBlock` from a `BeaconBlock` and `Signature`. - pub fn from_block(block: BeaconBlock, signature: Signature) -> Self { + pub fn from_block(block: BeaconBlock, signature: Signature) -> Self { match block { BeaconBlock::Base(message) => { SignedBeaconBlock::Base(SignedBeaconBlockBase { message, signature }) @@ -131,7 +131,7 @@ impl SignedBeaconBlock { /// /// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because /// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_. - pub fn deconstruct(self) -> (BeaconBlock, Signature) { + pub fn deconstruct(self) -> (BeaconBlock, Signature) { match self { SignedBeaconBlock::Base(block) => (BeaconBlock::Base(block.message), block.signature), SignedBeaconBlock::Altair(block) => { @@ -142,7 +142,7 @@ impl SignedBeaconBlock { } /// Accessor for the block's `message` field as a ref. - pub fn message(&self) -> BeaconBlockRef<'_, E> { + pub fn message(&self) -> BeaconBlockRef<'_, E, Payload> { match self { SignedBeaconBlock::Base(inner) => BeaconBlockRef::Base(&inner.message), SignedBeaconBlock::Altair(inner) => BeaconBlockRef::Altair(&inner.message), @@ -151,7 +151,7 @@ impl SignedBeaconBlock { } /// Accessor for the block's `message` as a mutable reference (for testing only). - pub fn message_mut(&mut self) -> BeaconBlockRefMut<'_, E> { + pub fn message_mut(&mut self) -> BeaconBlockRefMut<'_, E, Payload> { match self { SignedBeaconBlock::Base(inner) => BeaconBlockRefMut::Base(&mut inner.message), SignedBeaconBlock::Altair(inner) => BeaconBlockRefMut::Altair(&mut inner.message), diff --git a/consensus/types/src/test_utils/test_random.rs b/consensus/types/src/test_utils/test_random.rs index 064b57f42..55135a8a2 100644 --- a/consensus/types/src/test_utils/test_random.rs +++ b/consensus/types/src/test_utils/test_random.rs @@ -3,6 +3,7 @@ use rand::RngCore; use rand::SeedableRng; use rand_xorshift::XorShiftRng; use ssz_types::typenum::Unsigned; +use std::marker::PhantomData; use std::sync::Arc; mod address; @@ -25,6 +26,12 @@ pub trait TestRandom { fn random_for_test(rng: &mut impl RngCore) -> Self; } +impl TestRandom for PhantomData { + fn random_for_test(_rng: &mut impl RngCore) -> Self { + PhantomData::default() + } +} + impl TestRandom for bool { fn random_for_test(rng: &mut impl RngCore) -> Self { (rng.next_u32() % 2) == 1 diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 2e7802225..f86148312 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -17,8 +17,8 @@ use state_processing::per_block_processing::{ use std::fmt::Debug; use std::path::Path; use types::{ - Attestation, AttesterSlashing, BeaconBlock, BeaconState, ChainSpec, Deposit, EthSpec, - ExecutionPayload, ForkName, ProposerSlashing, SignedVoluntaryExit, SyncAggregate, + Attestation, AttesterSlashing, BeaconBlock, BeaconState, ChainSpec, Deposit, EthSpec, ForkName, + FullPayload, ProposerSlashing, SignedVoluntaryExit, SyncAggregate, }; #[derive(Debug, Clone, Default, Deserialize)] @@ -183,7 +183,12 @@ impl Operation for BeaconBlock { spec: &ChainSpec, _: &Operations, ) -> Result<(), BlockProcessingError> { - process_block_header(state, self.to_ref(), VerifyBlockRoot::True, spec)?; + process_block_header( + state, + self.to_ref().temporary_block_header(), + VerifyBlockRoot::True, + spec, + )?; Ok(()) } } @@ -216,7 +221,7 @@ impl Operation for SyncAggregate { } } -impl Operation for ExecutionPayload { +impl Operation for FullPayload { fn handler_name() -> String { "execution_payload".into() } diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 4d068cb91..540fe6903 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -54,6 +54,7 @@ type_name!(DepositData); type_name!(DepositMessage); type_name!(Eth1Data); type_name_generic!(ExecutionPayload); +type_name_generic!(FullPayload, "ExecutionPayload"); type_name_generic!(ExecutionPayloadHeader); type_name!(Fork); type_name!(ForkData); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index bdefec001..a36253f24 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -72,8 +72,8 @@ fn operations_sync_aggregate() { #[test] fn operations_execution_payload() { - OperationsHandler::>::default().run(); - OperationsHandler::>::default().run(); + OperationsHandler::>::default().run(); + OperationsHandler::>::default().run(); } #[test] diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index b788a7565..95751d1a8 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -5,7 +5,8 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use task_executor::TaskExecutor; use tokio::time::sleep; use types::{ - Address, ChainSpec, EthSpec, ExecutionBlockHash, Hash256, MainnetEthSpec, Slot, Uint256, + Address, ChainSpec, EthSpec, ExecutionBlockHash, FullPayload, Hash256, MainnetEthSpec, Slot, + Uint256, }; const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(10); @@ -171,7 +172,7 @@ impl TestRig { let valid_payload = self .ee_a .execution_layer - .get_payload::( + .get_payload::>( parent_hash, timestamp, prev_randao, @@ -179,7 +180,8 @@ impl TestRig { proposer_index, ) .await - .unwrap(); + .unwrap() + .execution_payload; /* * Execution Engine A: @@ -262,7 +264,7 @@ impl TestRig { let second_payload = self .ee_a .execution_layer - .get_payload::( + .get_payload::>( parent_hash, timestamp, prev_randao, @@ -270,7 +272,8 @@ impl TestRig { proposer_index, ) .await - .unwrap(); + .unwrap() + .execution_payload; /* * Execution Engine A: diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 0cba70481..32e63b070 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -1,3 +1,4 @@ +use crate::beacon_node_fallback::{AllErrored, Error as FallbackError}; use crate::{ beacon_node_fallback::{BeaconNodeFallback, RequireSynced}, graffiti_file::GraffitiFile, @@ -10,7 +11,30 @@ use slot_clock::SlotClock; use std::ops::Deref; use std::sync::Arc; use tokio::sync::mpsc; -use types::{EthSpec, PublicKeyBytes, Slot}; +use types::{ + BlindedPayload, BlockType, Epoch, EthSpec, ExecPayload, FullPayload, PublicKeyBytes, Slot, +}; + +#[derive(Debug)] +pub enum BlockError { + Recoverable(String), + Irrecoverable(String), +} + +impl From> for BlockError { + fn from(e: AllErrored) -> Self { + if e.0.iter().any(|(_, error)| { + matches!( + error, + FallbackError::RequestFailed(BlockError::Irrecoverable(_)) + ) + }) { + BlockError::Irrecoverable(e.to_string()) + } else { + BlockError::Recoverable(e.to_string()) + } + } +} /// Builds a `BlockService`. pub struct BlockServiceBuilder { @@ -20,6 +44,7 @@ pub struct BlockServiceBuilder { context: Option>, graffiti: Option, graffiti_file: Option, + private_tx_proposals: bool, } impl BlockServiceBuilder { @@ -31,6 +56,7 @@ impl BlockServiceBuilder { context: None, graffiti: None, graffiti_file: None, + private_tx_proposals: false, } } @@ -64,6 +90,11 @@ impl BlockServiceBuilder { self } + pub fn private_tx_proposals(mut self, private_tx_proposals: bool) -> Self { + self.private_tx_proposals = private_tx_proposals; + self + } + pub fn build(self) -> Result, String> { Ok(BlockService { inner: Arc::new(Inner { @@ -81,6 +112,7 @@ impl BlockServiceBuilder { .ok_or("Cannot build BlockService without runtime_context")?, graffiti: self.graffiti, graffiti_file: self.graffiti_file, + private_tx_proposals: self.private_tx_proposals, }), }) } @@ -94,6 +126,7 @@ pub struct Inner { context: RuntimeContext, graffiti: Option, graffiti_file: Option, + private_tx_proposals: bool, } /// Attempts to produce attestations for any block producer(s) at the start of the epoch. @@ -202,16 +235,46 @@ impl BlockService { ) } + let private_tx_proposals = self.private_tx_proposals; + let merge_slot = self + .context + .eth2_config + .spec + .bellatrix_fork_epoch + .unwrap_or_else(Epoch::max_value) + .start_slot(E::slots_per_epoch()); for validator_pubkey in proposers { let service = self.clone(); let log = log.clone(); self.inner.context.executor.spawn( async move { - if let Err(e) = service.publish_block(slot, validator_pubkey).await { + let publish_result = if private_tx_proposals && slot >= merge_slot { + let mut result = service.clone() + .publish_block::>(slot, validator_pubkey) + .await; + match result.as_ref() { + Err(BlockError::Recoverable(e)) => { + error!(log, "Error whilst producing a blinded block, attempting to publish full block"; "error" => ?e); + result = service + .publish_block::>(slot, validator_pubkey) + .await; + }, + Err(BlockError::Irrecoverable(e)) => { + error!(log, "Error whilst producing a blinded block, cannot fallback because block was signed"; "error" => ?e); + }, + _ => {}, + }; + result + } else { + service + .publish_block::>(slot, validator_pubkey) + .await + }; + if let Err(e) = publish_result { crit!( log, "Error whilst producing block"; - "message" => e + "message" => ?e ); } }, @@ -223,25 +286,29 @@ impl BlockService { } /// Produce a block at the given slot for validator_pubkey - async fn publish_block( + async fn publish_block>( self, slot: Slot, validator_pubkey: PublicKeyBytes, - ) -> Result<(), String> { + ) -> Result<(), BlockError> { let log = self.context.log(); let _timer = metrics::start_timer_vec(&metrics::BLOCK_SERVICE_TIMES, &[metrics::BEACON_BLOCK]); - let current_slot = self - .slot_clock - .now() - .ok_or("Unable to determine current slot from clock")?; + let current_slot = self.slot_clock.now().ok_or_else(|| { + BlockError::Recoverable("Unable to determine current slot from clock".to_string()) + })?; let randao_reveal = self .validator_store .randao_reveal(validator_pubkey, slot.epoch(E::slots_per_epoch())) .await - .map_err(|e| format!("Unable to produce randao reveal signature: {:?}", e))? + .map_err(|e| { + BlockError::Recoverable(format!( + "Unable to produce randao reveal signature: {:?}", + e + )) + })? .into(); let graffiti = self @@ -268,41 +335,86 @@ impl BlockService { &metrics::BLOCK_SERVICE_TIMES, &[metrics::BEACON_BLOCK_HTTP_GET], ); - let block = beacon_node - .get_validator_blocks(slot, randao_reveal_ref, graffiti.as_ref()) - .await - .map_err(|e| format!("Error from beacon node when producing block: {:?}", e))? - .data; + let block = match Payload::block_type() { + BlockType::Full => { + beacon_node + .get_validator_blocks::( + slot, + randao_reveal_ref, + graffiti.as_ref(), + ) + .await + .map_err(|e| { + BlockError::Recoverable(format!( + "Error from beacon node when producing block: {:?}", + e + )) + })? + .data + } + BlockType::Blinded => { + beacon_node + .get_validator_blinded_blocks::( + slot, + randao_reveal_ref, + graffiti.as_ref(), + ) + .await + .map_err(|e| { + BlockError::Recoverable(format!( + "Error from beacon node when producing block: {:?}", + e + )) + })? + .data + } + }; drop(get_timer); if proposer_index != Some(block.proposer_index()) { - return Err( + return Err(BlockError::Recoverable( "Proposer index does not match block proposer. Beacon chain re-orged" .to_string(), - ); + )); } let signed_block = self_ref .validator_store - .sign_block(*validator_pubkey_ref, block, current_slot) + .sign_block::(*validator_pubkey_ref, block, current_slot) .await - .map_err(|e| format!("Unable to sign block: {:?}", e))?; + .map_err(|e| { + BlockError::Recoverable(format!("Unable to sign block: {:?}", e)) + })?; let _post_timer = metrics::start_timer_vec( &metrics::BLOCK_SERVICE_TIMES, &[metrics::BEACON_BLOCK_HTTP_POST], ); - beacon_node - .post_beacon_blocks(&signed_block) - .await - .map_err(|e| { - format!("Error from beacon node when publishing block: {:?}", e) - })?; - Ok::<_, String>(signed_block) + match Payload::block_type() { + BlockType::Full => beacon_node + .post_beacon_blocks(&signed_block) + .await + .map_err(|e| { + BlockError::Irrecoverable(format!( + "Error from beacon node when publishing block: {:?}", + e + )) + })?, + BlockType::Blinded => beacon_node + .post_beacon_blinded_blocks(&signed_block) + .await + .map_err(|e| { + BlockError::Irrecoverable(format!( + "Error from beacon node when publishing block: {:?}", + e + )) + })?, + } + + Ok::<_, BlockError>(signed_block) }) - .await - .map_err(|e| e.to_string())?; + .await?; info!( log, diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 49a8f5816..d02e26ace 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -258,4 +258,12 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { immediately.") .takes_value(false), ) + .arg( + Arg::with_name("private-tx-proposals") + .long("private-tx-proposals") + .help("If this flag is set, Lighthouse will query the Beacon Node for only block \ + headers during proposals and will sign over headers. Useful for outsourcing \ + execution payload construction during proposals.") + .takes_value(false), + ) } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 232526bac..45e10e39e 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -55,6 +55,7 @@ pub struct Config { /// If true, enable functionality that monitors the network for attestations or proposals from /// any of the validators managed by this client before starting up. pub enable_doppelganger_protection: bool, + pub private_tx_proposals: bool, /// A list of custom certificates that the validator client will additionally use when /// connecting to a beacon node over SSL/TLS. pub beacon_nodes_tls_certs: Option>, @@ -91,6 +92,7 @@ impl Default for Config { monitoring_api: None, enable_doppelganger_protection: false, beacon_nodes_tls_certs: None, + private_tx_proposals: false, } } } @@ -306,6 +308,10 @@ impl Config { config.enable_doppelganger_protection = true; } + if cli_args.is_present("private-tx-proposals") { + config.private_tx_proposals = true; + } + Ok(config) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index c58ac25f1..039b54496 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -400,6 +400,7 @@ impl ProductionValidatorClient { .runtime_context(context.service_context("block".into())) .graffiti(config.graffiti) .graffiti_file(config.graffiti_file.clone()) + .private_tx_proposals(config.private_tx_proposals) .build()?; let attestation_service = AttestationServiceBuilder::new() diff --git a/validator_client/src/signing_method.rs b/validator_client/src/signing_method.rs index 3c12ac1e6..0daefc43c 100644 --- a/validator_client/src/signing_method.rs +++ b/validator_client/src/signing_method.rs @@ -33,9 +33,9 @@ pub enum Error { } /// Enumerates all messages that can be signed by a validator. -pub enum SignableMessage<'a, T: EthSpec> { +pub enum SignableMessage<'a, T: EthSpec, Payload: ExecPayload = FullPayload> { RandaoReveal(Epoch), - BeaconBlock(&'a BeaconBlock), + BeaconBlock(&'a BeaconBlock), AttestationData(&'a AttestationData), SignedAggregateAndProof(&'a AggregateAndProof), SelectionProof(Slot), @@ -47,7 +47,7 @@ pub enum SignableMessage<'a, T: EthSpec> { SignedContributionAndProof(&'a ContributionAndProof), } -impl<'a, T: EthSpec> SignableMessage<'a, T> { +impl<'a, T: EthSpec, Payload: ExecPayload> SignableMessage<'a, T, Payload> { /// Returns the `SignedRoot` for the contained message. /// /// The actual `SignedRoot` trait is not used since it also requires a `TreeHash` impl, which is @@ -113,9 +113,9 @@ impl SigningContext { impl SigningMethod { /// Return the signature of `signable_message`, with respect to the `signing_context`. - pub async fn get_signature( + pub async fn get_signature>( &self, - signable_message: SignableMessage<'_, T>, + signable_message: SignableMessage<'_, T, Payload>, signing_context: SigningContext, spec: &ChainSpec, executor: &TaskExecutor, diff --git a/validator_client/src/signing_method/web3signer.rs b/validator_client/src/signing_method/web3signer.rs index b632986c9..9ac1655cc 100644 --- a/validator_client/src/signing_method/web3signer.rs +++ b/validator_client/src/signing_method/web3signer.rs @@ -34,7 +34,7 @@ pub struct ForkInfo { #[derive(Debug, PartialEq, Serialize)] #[serde(bound = "T: EthSpec", rename_all = "snake_case")] -pub enum Web3SignerObject<'a, T: EthSpec> { +pub enum Web3SignerObject<'a, T: EthSpec, Payload: ExecPayload> { AggregationSlot { slot: Slot, }, @@ -42,7 +42,7 @@ pub enum Web3SignerObject<'a, T: EthSpec> { Attestation(&'a AttestationData), BeaconBlock { version: ForkName, - block: &'a BeaconBlock, + block: &'a BeaconBlock, }, #[allow(dead_code)] Deposit { @@ -66,8 +66,8 @@ pub enum Web3SignerObject<'a, T: EthSpec> { ContributionAndProof(&'a ContributionAndProof), } -impl<'a, T: EthSpec> Web3SignerObject<'a, T> { - pub fn beacon_block(block: &'a BeaconBlock) -> Result { +impl<'a, T: EthSpec, Payload: ExecPayload> Web3SignerObject<'a, T, Payload> { + pub fn beacon_block(block: &'a BeaconBlock) -> Result { let version = match block { BeaconBlock::Base(_) => ForkName::Phase0, BeaconBlock::Altair(_) => ForkName::Altair, @@ -99,7 +99,7 @@ impl<'a, T: EthSpec> Web3SignerObject<'a, T> { #[derive(Debug, PartialEq, Serialize)] #[serde(bound = "T: EthSpec")] -pub struct SigningRequest<'a, T: EthSpec> { +pub struct SigningRequest<'a, T: EthSpec, Payload: ExecPayload> { #[serde(rename = "type")] pub message_type: MessageType, #[serde(skip_serializing_if = "Option::is_none")] @@ -107,7 +107,7 @@ pub struct SigningRequest<'a, T: EthSpec> { #[serde(rename = "signingRoot")] pub signing_root: Hash256, #[serde(flatten)] - pub object: Web3SignerObject<'a, T>, + pub object: Web3SignerObject<'a, T, Payload>, } #[derive(Debug, PartialEq, Deserialize)] diff --git a/validator_client/src/validator_store.rs b/validator_client/src/validator_store.rs index 3f4a01faa..5abe37f43 100644 --- a/validator_client/src/validator_store.rs +++ b/validator_client/src/validator_store.rs @@ -18,10 +18,11 @@ use std::sync::Arc; use task_executor::TaskExecutor; use types::{ attestation::Error as AttestationError, graffiti::GraffitiString, Address, AggregateAndProof, - Attestation, BeaconBlock, ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, - Graffiti, Hash256, Keypair, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SignedContributionAndProof, Slot, SyncAggregatorSelectionData, - SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, Domain, Epoch, + EthSpec, ExecPayload, Fork, Graffiti, Hash256, Keypair, PublicKeyBytes, SelectionProof, + Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, Slot, + SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage, + SyncSelectionProof, SyncSubnetId, }; use validator_dir::ValidatorDir; @@ -338,7 +339,7 @@ impl ValidatorStore { let signing_context = self.signing_context(Domain::Randao, signing_epoch); let signature = signing_method - .get_signature::( + .get_signature::>( SignableMessage::RandaoReveal(signing_epoch), signing_context, &self.spec, @@ -359,12 +360,12 @@ impl ValidatorStore { .suggested_fee_recipient(validator_pubkey) } - pub async fn sign_block( + pub async fn sign_block>( &self, validator_pubkey: PublicKeyBytes, - block: BeaconBlock, + block: BeaconBlock, current_slot: Slot, - ) -> Result, Error> { + ) -> Result, Error> { // Make sure the block slot is not higher than the current slot to avoid potential attacks. if block.slot() > current_slot { warn!( @@ -397,7 +398,7 @@ impl ValidatorStore { let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; let signature = signing_method - .get_signature( + .get_signature::( SignableMessage::BeaconBlock(&block), signing_context, &self.spec, @@ -466,7 +467,7 @@ impl ValidatorStore { Ok(Safe::Valid) => { let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; let signature = signing_method - .get_signature::( + .get_signature::>( SignableMessage::AttestationData(&attestation.data), signing_context, &self.spec, @@ -543,7 +544,7 @@ impl ValidatorStore { let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; let signature = signing_method - .get_signature( + .get_signature::>( SignableMessage::SignedAggregateAndProof(&message), signing_context, &self.spec, @@ -576,7 +577,7 @@ impl ValidatorStore { let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; let signature = signing_method - .get_signature::( + .get_signature::>( SignableMessage::SelectionProof(slot), signing_context, &self.spec, @@ -615,7 +616,7 @@ impl ValidatorStore { }; let signature = signing_method - .get_signature::( + .get_signature::>( SignableMessage::SyncSelectionProof(&message), signing_context, &self.spec, @@ -641,7 +642,7 @@ impl ValidatorStore { let signing_method = self.doppelganger_bypassed_signing_method(*validator_pubkey)?; let signature = signing_method - .get_signature::( + .get_signature::>( SignableMessage::SyncCommitteeSignature { beacon_block_root, slot, @@ -686,7 +687,7 @@ impl ValidatorStore { }; let signature = signing_method - .get_signature( + .get_signature::>( SignableMessage::SignedContributionAndProof(&message), signing_context, &self.spec,