feat: make payload builder generic over attributes type (#5948)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Dan Cline
2024-01-10 05:21:43 -05:00
committed by GitHub
parent 42326fd2a5
commit cb96fe6d09
50 changed files with 1912 additions and 999 deletions

256
Cargo.lock generated
View File

@ -93,7 +93,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if",
"getrandom 0.2.11",
"getrandom 0.2.12",
"once_cell",
"version_check",
"zerocopy",
@ -182,7 +182,7 @@ dependencies = [
"derive_arbitrary",
"derive_more",
"ethereum_ssz",
"getrandom 0.2.11",
"getrandom 0.2.12",
"hex-literal",
"itoa",
"keccak-asm",
@ -726,9 +726,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.5"
version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9"
[[package]]
name = "base64ct"
@ -1305,9 +1305,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.13"
version = "4.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2"
dependencies = [
"clap_builder",
"clap_derive",
@ -1315,9 +1315,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.12"
version = "4.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370"
dependencies = [
"anstream",
"anstyle",
@ -1409,7 +1409,7 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"bech32",
"bs58",
"digest 0.10.7",
@ -1464,14 +1464,14 @@ dependencies = [
[[package]]
name = "console"
version = "0.15.7"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys 0.45.0",
"windows-sys 0.52.0",
]
[[package]]
@ -1618,44 +1618,37 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
[[package]]
name = "crossbeam-channel"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2"
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.4"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.17"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.18"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
"cfg-if",
]
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crossterm"
@ -2323,7 +2316,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"bytes",
"ed25519-dalek",
"hex",
@ -2531,7 +2524,7 @@ dependencies = [
"serde",
"serde_json",
"syn 2.0.48",
"toml 0.8.2",
"toml 0.8.8",
"walkdir",
]
@ -2566,7 +2559,7 @@ dependencies = [
"ethabi",
"generic-array",
"k256",
"num_enum 0.7.1",
"num_enum 0.7.2",
"once_cell",
"open-fastrlp",
"rand 0.8.5",
@ -2615,7 +2608,7 @@ checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816"
dependencies = [
"async-trait",
"auto_impl",
"base64 0.21.5",
"base64 0.21.6",
"bytes",
"const-hex",
"enr",
@ -2989,9 +2982,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
@ -4007,7 +4000,7 @@ version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"pem",
"ring 0.16.20",
"serde",
@ -4017,9 +4010,9 @@ dependencies = [
[[package]]
name = "k256"
version = "0.13.2"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b"
checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
dependencies = [
"cfg-if",
"ecdsa",
@ -4065,9 +4058,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libffi"
@ -4289,7 +4282,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d4fa7ce7c4862db464a37b0b31d89bca874562f034bd7993895572783d02950"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"hyper",
"indexmap 1.9.3",
"ipnet",
@ -4618,11 +4611,11 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
dependencies = [
"num_enum_derive 0.7.1",
"num_enum_derive 0.7.2",
]
[[package]]
@ -4639,11 +4632,11 @@ dependencies = [
[[package]]
name = "num_enum_derive"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro-crate 2.0.1",
"proc-macro-crate 3.0.0",
"proc-macro2",
"quote",
"syn 2.0.48",
@ -4786,7 +4779,7 @@ version = "3.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b"
dependencies = [
"proc-macro-crate 2.0.1",
"proc-macro-crate 2.0.0",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -5219,12 +5212,20 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
version = "2.0.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a"
checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
dependencies = [
"toml_datetime",
"toml_edit 0.20.2",
"toml_edit 0.20.7",
]
[[package]]
name = "proc-macro-crate"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd"
dependencies = [
"toml_edit 0.21.0",
]
[[package]]
@ -5453,7 +5454,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.11",
"getrandom 0.2.12",
]
[[package]]
@ -5527,7 +5528,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom 0.2.11",
"getrandom 0.2.12",
"libredox",
"thiserror",
]
@ -5592,7 +5593,7 @@ version = "0.11.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"bytes",
"encoding_rs",
"futures-core",
@ -5685,6 +5686,8 @@ dependencies = [
"reth-network",
"reth-network-api",
"reth-nippy-jar",
"reth-node-api",
"reth-node-builder",
"reth-optimism-payload-builder",
"reth-payload-builder",
"reth-payload-validator",
@ -5712,7 +5715,7 @@ dependencies = [
"tempfile",
"thiserror",
"tokio",
"toml 0.8.2",
"toml 0.8.8",
"tracing",
"tui",
"vergen",
@ -5729,6 +5732,7 @@ dependencies = [
"reth",
"reth-beacon-consensus",
"reth-interfaces",
"reth-node-api",
"reth-primitives",
"reth-provider",
"reth-revm",
@ -5751,6 +5755,7 @@ dependencies = [
"metrics",
"reth-interfaces",
"reth-metrics",
"reth-node-api",
"reth-payload-builder",
"reth-primitives",
"reth-provider",
@ -5776,6 +5781,8 @@ dependencies = [
"reth-downloaders",
"reth-interfaces",
"reth-metrics",
"reth-node-api",
"reth-node-builder",
"reth-payload-builder",
"reth-payload-validator",
"reth-primitives",
@ -5855,7 +5862,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
"toml 0.8.2",
"toml 0.8.8",
]
[[package]]
@ -6288,11 +6295,34 @@ dependencies = [
"zstd 0.12.4",
]
[[package]]
name = "reth-node-api"
version = "0.1.0-alpha.13"
dependencies = [
"reth-payload-builder",
"reth-primitives",
"reth-rpc-types",
"serde",
"thiserror",
]
[[package]]
name = "reth-node-builder"
version = "0.1.0-alpha.13"
dependencies = [
"reth-node-api",
"reth-payload-builder",
"reth-primitives",
"reth-rpc-types",
"serde",
]
[[package]]
name = "reth-optimism-payload-builder"
version = "0.1.0-alpha.14"
dependencies = [
"reth-basic-payload-builder",
"reth-node-api",
"reth-payload-builder",
"reth-primitives",
"reth-provider",
@ -6308,10 +6338,12 @@ name = "reth-payload-builder"
version = "0.1.0-alpha.14"
dependencies = [
"alloy-rlp",
"async-trait",
"futures-util",
"metrics",
"reth-interfaces",
"reth-metrics",
"reth-node-api",
"reth-primitives",
"reth-provider",
"reth-rpc-types",
@ -6320,6 +6352,7 @@ dependencies = [
"reth-transaction-pool",
"revm",
"revm-primitives",
"serde",
"sha2",
"thiserror",
"tokio",
@ -6356,7 +6389,7 @@ dependencies = [
"hash-db",
"itertools 0.11.0",
"modular-bitfield",
"num_enum 0.7.1",
"num_enum 0.7.2",
"nybbles",
"once_cell",
"plain_hasher",
@ -6380,7 +6413,7 @@ dependencies = [
"tempfile",
"test-fuzz",
"thiserror",
"toml 0.8.2",
"toml 0.8.8",
"tracing",
"triehash",
"zstd 0.12.4",
@ -6510,6 +6543,8 @@ name = "reth-rpc-api"
version = "0.1.0-alpha.14"
dependencies = [
"jsonrpsee",
"reth-node-api",
"reth-payload-builder",
"reth-primitives",
"reth-rpc-types",
"serde_json",
@ -6542,6 +6577,8 @@ dependencies = [
"reth-ipc",
"reth-metrics",
"reth-network-api",
"reth-node-api",
"reth-node-builder",
"reth-payload-builder",
"reth-primitives",
"reth-provider",
@ -6576,6 +6613,8 @@ dependencies = [
"reth-beacon-consensus",
"reth-interfaces",
"reth-metrics",
"reth-node-api",
"reth-node-builder",
"reth-payload-builder",
"reth-primitives",
"reth-provider",
@ -6890,7 +6929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
dependencies = [
"cc",
"getrandom 0.2.11",
"getrandom 0.2.12",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
@ -7074,7 +7113,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
]
[[package]]
@ -7399,7 +7438,7 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
dependencies = [
"base64 0.21.5",
"base64 0.21.6",
"chrono",
"hex",
"indexmap 1.9.3",
@ -8191,21 +8230,21 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.2",
"toml_edit 0.21.0",
]
[[package]]
name = "toml_datetime"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
@ -8223,9 +8262,20 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.2"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap 2.1.0",
"toml_datetime",
"winnow",
]
[[package]]
name = "toml_edit"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap 2.1.0",
"serde",
@ -8262,7 +8312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"async-compression",
"base64 0.21.5",
"base64 0.21.6",
"bitflags 2.4.1",
"bytes",
"futures-core",
@ -8728,7 +8778,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.11",
"getrandom 0.2.12",
"serde",
]
@ -8738,7 +8788,7 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom 0.2.11",
"getrandom 0.2.12",
]
[[package]]
@ -8979,15 +9029,6 @@ dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@ -9006,21 +9047,6 @@ dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@ -9051,12 +9077,6 @@ dependencies = [
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@ -9069,12 +9089,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@ -9087,12 +9101,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@ -9105,12 +9113,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@ -9123,12 +9125,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@ -9141,12 +9137,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@ -9159,12 +9149,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"

View File

@ -36,6 +36,8 @@ members = [
"crates/rpc/rpc-testing-util/",
"crates/rpc/rpc-types/",
"crates/rpc/rpc-types-compat/",
"crates/node-builder/",
"crates/node-api/",
"crates/snapshot/",
"crates/stages/",
"crates/storage/codecs/",
@ -112,6 +114,8 @@ reth-consensus-common = { path = "crates/consensus/common" }
reth-db = { path = "crates/storage/db" }
reth-discv4 = { path = "crates/net/discv4" }
reth-dns-discovery = { path = "crates/net/dns" }
reth-node-builder = { path = "crates/node-builder" }
reth-node-api = { path = "crates/node-api" }
reth-downloaders = { path = "crates/net/downloaders" }
reth-ecies = { path = "crates/net/ecies" }
reth-eth-wire = { path = "crates/net/eth-wire" }

View File

@ -54,6 +54,8 @@ reth-prune.workspace = true
reth-snapshot = { workspace = true, features = ["clap"] }
reth-trie.workspace = true
reth-nippy-jar.workspace = true
reth-node-api.workspace = true
reth-node-builder.workspace = true
# crypto
alloy-rlp.workspace = true

View File

@ -18,6 +18,7 @@ use clap::{
};
use futures::TryFutureExt;
use reth_network_api::{NetworkInfo, Peers};
use reth_node_api::EngineTypes;
use reth_provider::{
AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader,
EvmEnvProvider, HeaderProvider, StateProviderFactory,
@ -224,7 +225,7 @@ impl RpcServerArgs {
/// Returns the handles for the launched regular RPC server(s) (if any) and the server handle
/// for the auth server that handles the `engine_` API that's accessed by the consensus
/// layer.
pub async fn start_servers<Reth, Engine, Conf>(
pub async fn start_servers<Reth, Engine, Conf, EngineT: EngineTypes>(
&self,
components: &Reth,
engine_api: Engine,
@ -233,7 +234,7 @@ impl RpcServerArgs {
) -> eyre::Result<RethRpcServerHandles>
where
Reth: RethNodeComponents,
Engine: EngineApiServer,
Engine: EngineApiServer<EngineT>,
Conf: RethNodeCommandConfig,
{
let auth_config = self.auth_server_config(jwt_secret)?;
@ -322,13 +323,13 @@ impl RpcServerArgs {
}
/// Create Engine API server.
pub async fn start_auth_server<Provider, Pool, Network, Tasks>(
pub async fn start_auth_server<Provider, Pool, Network, Tasks, EngineT>(
&self,
provider: Provider,
pool: Pool,
network: Network,
executor: Tasks,
engine_api: EngineApi<Provider>,
engine_api: EngineApi<Provider, EngineT>,
jwt_secret: JwtSecret,
) -> Result<AuthServerHandle, RpcError>
where
@ -343,6 +344,7 @@ impl RpcServerArgs {
Pool: TransactionPool + Clone + 'static,
Network: NetworkInfo + Peers + Clone + 'static,
Tasks: TaskSpawner + Clone + 'static,
EngineT: EngineTypes + 'static,
{
let socket_address = SocketAddr::new(self.auth_addr, self.auth_port);

View File

@ -59,6 +59,11 @@ use reth_interfaces::{
};
use reth_network::{NetworkBuilder, NetworkConfig, NetworkEvents, NetworkHandle, NetworkManager};
use reth_network_api::{NetworkInfo, PeersInfo};
#[cfg(not(feature = "optimism"))]
use reth_node_builder::EthEngineTypes;
#[cfg(feature = "optimism")]
use reth_node_builder::OptimismEngineTypes;
use reth_payload_builder::PayloadBuilderHandle;
use reth_primitives::{
constants::eip4844::{LoadKzgSettingsError, MAINNET_KZG_TRUSTED_SETUP},
kzg::KzgSettings,
@ -1109,8 +1114,24 @@ impl<DB: Database + DatabaseMetrics + DatabaseMetadata + 'static> NodeBuilderWit
ext.on_components_initialized(&components)?;
debug!(target: "reth::cli", "Spawning payload builder service");
let payload_builder =
ext.spawn_payload_builder_service(&self.config.builder, &components)?;
// TODO: stateful node builder should handle this in with_payload_builder
// Optimism's payload builder is implemented on the OptimismPayloadBuilder type.
#[cfg(feature = "optimism")]
let payload_builder = reth_optimism_payload_builder::OptimismPayloadBuilder::default()
.set_compute_pending_block(self.config.builder.compute_pending_block);
#[cfg(feature = "optimism")]
let payload_builder: PayloadBuilderHandle<OptimismEngineTypes> =
ext.spawn_payload_builder_service(&self.config.builder, &components, payload_builder)?;
// The default payload builder is implemented on the unit type.
#[cfg(not(feature = "optimism"))]
let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::default();
#[cfg(not(feature = "optimism"))]
let payload_builder: PayloadBuilderHandle<EthEngineTypes> =
ext.spawn_payload_builder_service(&self.config.builder, &components, payload_builder)?;
let (consensus_engine_tx, mut consensus_engine_rx) = unbounded_channel();
if let Some(store_path) = self.config.debug.engine_api_store.clone() {
@ -1279,7 +1300,7 @@ impl<DB: Database + DatabaseMetrics + DatabaseMetadata + 'static> NodeBuilderWit
#[cfg(feature = "optimism")]
if self.config.chain.is_optimism() && !self.config.rollup.enable_genesis_walkback {
let client = rpc_server_handles.auth.http_client();
reth_rpc_api::EngineApiClient::fork_choice_updated_v2(
reth_rpc_api::EngineApiClient::<OptimismEngineTypes>::fork_choice_updated_v2(
&client,
reth_rpc_types::engine::ForkchoiceState {
head_block_hash: head.hash,

View File

@ -5,7 +5,10 @@ use crate::cli::{
config::{PayloadBuilderConfig, RethNetworkConfig, RethRpcConfig},
};
use clap::Args;
use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig};
use reth_basic_payload_builder::{
BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig, PayloadBuilder,
};
use reth_node_api::EngineTypes;
use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService};
use reth_provider::CanonStateSubscriptions;
use reth_tasks::TaskSpawner;
@ -125,14 +128,22 @@ pub trait RethNodeCommandConfig: fmt::Debug {
///
/// By default this spawns a [BasicPayloadJobGenerator] with the default configuration
/// [BasicPayloadJobGeneratorConfig].
fn spawn_payload_builder_service<Conf, Reth>(
fn spawn_payload_builder_service<Conf, Reth, Builder, Engine>(
&mut self,
conf: &Conf,
components: &Reth,
) -> eyre::Result<PayloadBuilderHandle>
payload_builder: Builder,
) -> eyre::Result<PayloadBuilderHandle<Engine>>
where
Conf: PayloadBuilderConfig,
Reth: RethNodeComponents,
Engine: EngineTypes + 'static,
Builder: PayloadBuilder<
Reth::Pool,
Reth::Provider,
Attributes = Engine::PayloadBuilderAttributes,
> + Unpin
+ 'static,
{
let payload_job_config = BasicPayloadJobGeneratorConfig::default()
.interval(conf.interval())
@ -145,15 +156,6 @@ pub trait RethNodeCommandConfig: fmt::Debug {
#[cfg(feature = "optimism")]
let payload_job_config = payload_job_config.extradata(Default::default());
// The default payload builder is implemented on the unit type.
#[cfg(not(feature = "optimism"))]
let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::default();
// Optimism's payload builder is implemented on the OptimismPayloadBuilder type.
#[cfg(feature = "optimism")]
let payload_builder = reth_optimism_payload_builder::OptimismPayloadBuilder::default()
.set_compute_pending_block(conf.compute_pending_block());
let payload_generator = BasicPayloadJobGenerator::with_builder(
components.provider(),
components.pool(),
@ -315,18 +317,26 @@ impl<T: RethNodeCommandConfig> RethNodeCommandConfig for NoArgs<T> {
}
}
fn spawn_payload_builder_service<Conf, Reth>(
fn spawn_payload_builder_service<Conf, Reth, Builder, Engine>(
&mut self,
conf: &Conf,
components: &Reth,
) -> eyre::Result<PayloadBuilderHandle>
payload_builder: Builder,
) -> eyre::Result<PayloadBuilderHandle<Engine>>
where
Conf: PayloadBuilderConfig,
Reth: RethNodeComponents,
Engine: EngineTypes + 'static,
Builder: PayloadBuilder<
Reth::Pool,
Reth::Provider,
Attributes = Engine::PayloadBuilderAttributes,
> + Unpin
+ 'static,
{
self.inner_mut()
.ok_or_else(|| eyre::eyre!("config value must be set"))?
.spawn_payload_builder_service(conf, components)
.spawn_payload_builder_service(conf, components, payload_builder)
}
}

View File

@ -20,7 +20,10 @@ use reth_blockchain_tree::{
};
use reth_db::{init_db, DatabaseEnv};
use reth_interfaces::{consensus::Consensus, RethResult};
use reth_payload_builder::{database::CachedReads, PayloadBuilderAttributes};
use reth_node_api::PayloadBuilderAttributes;
use reth_payload_builder::database::CachedReads;
#[cfg(feature = "optimism")]
use reth_payload_builder::OptimismPayloadBuilderAttributes;
use reth_primitives::{
constants::eip4844::{LoadKzgSettingsError, MAINNET_KZG_TRUSTED_SETUP},
fs,
@ -34,6 +37,8 @@ use reth_provider::{
ProviderFactory, StageCheckpointReader, StateProviderFactory,
};
use reth_revm::EvmProcessorFactory;
#[cfg(feature = "optimism")]
use reth_rpc_types::engine::OptimismPayloadAttributes;
use reth_rpc_types::engine::{BlobsBundleV1, PayloadAttributes};
use reth_transaction_pool::{
blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin,
@ -42,6 +47,9 @@ use reth_transaction_pool::{
use std::{path::PathBuf, str::FromStr, sync::Arc};
use tracing::*;
#[cfg(not(feature = "optimism"))]
use reth_payload_builder::EthPayloadBuilderAttributes;
/// `reth debug build-block` command
/// This debug routine requires that the node is positioned at the block before the target.
/// The script will then parse the block and attempt to build a similar one.
@ -235,16 +243,31 @@ impl Command {
suggested_fee_recipient: self.suggested_fee_recipient,
// TODO: add support for withdrawals
withdrawals: None,
#[cfg(feature = "optimism")]
optimism_payload_attributes: reth_rpc_types::engine::OptimismPayloadAttributes::default(
),
};
#[cfg(feature = "optimism")]
let payload_config = PayloadConfig::new(
Arc::clone(&best_block),
Bytes::default(),
PayloadBuilderAttributes::try_new(best_block.hash, payload_attrs)?,
OptimismPayloadBuilderAttributes::try_new(
best_block.hash,
OptimismPayloadAttributes {
payload_attributes: payload_attrs,
transactions: None,
no_tx_pool: None,
gas_limit: None,
},
)?,
self.chain.clone(),
);
#[cfg(not(feature = "optimism"))]
let payload_config = PayloadConfig::new(
Arc::clone(&best_block),
Bytes::default(),
EthPayloadBuilderAttributes::try_new(best_block.hash, payload_attrs)?,
self.chain.clone(),
);
let args = BuildArguments::new(
blockchain_db.clone(),
transaction_pool,

View File

@ -21,7 +21,12 @@ use reth_db::{init_db, DatabaseEnv};
use reth_interfaces::consensus::Consensus;
use reth_network::NetworkHandle;
use reth_network_api::NetworkInfo;
use reth_payload_builder::PayloadBuilderService;
use reth_node_api::EngineTypes;
#[cfg(not(feature = "optimism"))]
use reth_node_builder::EthEngineTypes;
#[cfg(feature = "optimism")]
use reth_node_builder::OptimismEngineTypes;
use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService};
use reth_primitives::{
fs::{self},
ChainSpec,
@ -29,7 +34,7 @@ use reth_primitives::{
use reth_provider::{providers::BlockchainProvider, CanonStateSubscriptions, ProviderFactory};
use reth_revm::EvmProcessorFactory;
use reth_rpc_types::{
engine::{CancunPayloadFields, ForkchoiceState, PayloadAttributes},
engine::{CancunPayloadFields, ForkchoiceState},
ExecutionPayload,
};
use reth_stages::Pipeline;
@ -175,8 +180,17 @@ impl Command {
self.chain.clone(),
payload_builder,
);
let (payload_service, payload_builder) =
#[cfg(feature = "optimism")]
let (payload_service, payload_builder): (
_,
PayloadBuilderHandle<OptimismEngineTypes>,
) = PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream());
#[cfg(not(feature = "optimism"))]
let (payload_service, payload_builder): (_, PayloadBuilderHandle<EthEngineTypes>) =
PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream());
ctx.task_executor.spawn_critical("payload builder service", Box::pin(payload_service));
// Configure the consensus engine
@ -245,8 +259,8 @@ impl Command {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum StoredEngineApiMessage {
ForkchoiceUpdated { state: ForkchoiceState, payload_attrs: Option<PayloadAttributes> },
enum StoredEngineApiMessage<Attributes> {
ForkchoiceUpdated { state: ForkchoiceState, payload_attrs: Option<Attributes> },
NewPayload { payload: ExecutionPayload, cancun_fields: Option<CancunPayloadFields> },
}
@ -260,7 +274,14 @@ impl EngineApiStore {
Self { path }
}
fn on_message(&self, msg: &BeaconEngineMessage, received_at: SystemTime) -> eyre::Result<()> {
fn on_message<Engine>(
&self,
msg: &BeaconEngineMessage<Engine>,
received_at: SystemTime,
) -> eyre::Result<()>
where
Engine: EngineTypes,
{
fs::create_dir_all(&self.path)?; // ensure that store path had been created
let timestamp = received_at.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis();
match msg {
@ -278,10 +299,12 @@ impl EngineApiStore {
let filename = format!("{}-new_payload-{}.json", timestamp, payload.block_hash());
fs::write(
self.path.join(filename),
serde_json::to_vec(&StoredEngineApiMessage::NewPayload {
payload: payload.clone(),
cancun_fields: cancun_fields.clone(),
})?,
serde_json::to_vec(
&StoredEngineApiMessage::<Engine::PayloadAttributes>::NewPayload {
payload: payload.clone(),
cancun_fields: cancun_fields.clone(),
},
)?,
)?;
}
// noop
@ -310,11 +333,14 @@ impl EngineApiStore {
Ok(filenames_by_ts.into_iter().flat_map(|(_, paths)| paths))
}
pub(crate) async fn intercept(
pub(crate) async fn intercept<Engine>(
self,
mut rx: UnboundedReceiver<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage>,
) {
mut rx: UnboundedReceiver<BeaconEngineMessage<Engine>>,
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
) where
Engine: EngineTypes,
BeaconEngineMessage<Engine>: std::fmt::Debug,
{
loop {
let Some(msg) = rx.recv().await else { break };
if let Err(error) = self.on_message(&msg, SystemTime::now()) {

View File

@ -20,6 +20,7 @@ reth-provider.workspace = true
reth-stages.workspace = true
reth-revm.workspace = true
reth-transaction-pool.workspace = true
reth-node-api.workspace = true
# async
futures-util.workspace = true

View File

@ -19,6 +19,7 @@ use reth_interfaces::{
consensus::{Consensus, ConsensusError},
executor::{BlockExecutionError, BlockValidationError},
};
use reth_node_api::EngineTypes;
use reth_primitives::{
constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT},
proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Bloom,
@ -93,28 +94,30 @@ impl Consensus for AutoSealConsensus {
/// Builder type for configuring the setup
#[derive(Debug)]
pub struct AutoSealBuilder<Client, Pool> {
pub struct AutoSealBuilder<Client, Pool, Engine: EngineTypes> {
client: Client,
consensus: AutoSealConsensus,
pool: Pool,
mode: MiningMode,
storage: Storage,
to_engine: UnboundedSender<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
canon_state_notification: CanonStateNotificationSender,
}
// === impl AutoSealBuilder ===
impl<Client, Pool: TransactionPool> AutoSealBuilder<Client, Pool>
impl<Client, Pool, Engine> AutoSealBuilder<Client, Pool, Engine>
where
Client: BlockReaderIdExt,
Pool: TransactionPool,
Engine: EngineTypes,
{
/// Creates a new builder instance to configure all parts.
pub fn new(
chain_spec: Arc<ChainSpec>,
client: Client,
pool: Pool,
to_engine: UnboundedSender<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
canon_state_notification: CanonStateNotificationSender,
mode: MiningMode,
) -> Self {
@ -143,7 +146,7 @@ where
/// Consumes the type and returns all components
#[track_caller]
pub fn build(self) -> (AutoSealConsensus, AutoSealClient, MiningTask<Client, Pool>) {
pub fn build(self) -> (AutoSealConsensus, AutoSealClient, MiningTask<Client, Pool, Engine>) {
let Self { client, consensus, pool, mode, storage, to_engine, canon_state_notification } =
self;
let auto_client = AutoSealClient::new(storage.clone());

View File

@ -2,6 +2,7 @@ use crate::{mode::MiningMode, Storage};
use futures_util::{future::BoxFuture, FutureExt};
use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus};
use reth_interfaces::consensus::ForkchoiceState;
use reth_node_api::EngineTypes;
use reth_primitives::{Block, ChainSpec, IntoRecoveredTransaction, SealedBlockWithSenders};
use reth_provider::{CanonChainTracker, CanonStateNotificationSender, Chain, StateProviderFactory};
use reth_stages::PipelineEvent;
@ -18,7 +19,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use tracing::{debug, error, warn};
/// A Future that listens for new ready transactions and puts new blocks into storage
pub struct MiningTask<Client, Pool: TransactionPool> {
pub struct MiningTask<Client, Pool: TransactionPool, Engine: EngineTypes> {
/// The configured chain spec
chain_spec: Arc<ChainSpec>,
/// The client used to interact with the state
@ -34,7 +35,7 @@ pub struct MiningTask<Client, Pool: TransactionPool> {
/// backlog of sets of transactions ready to be mined
queued: VecDeque<Vec<Arc<ValidPoolTransaction<<Pool as TransactionPool>::Transaction>>>>,
// TODO: ideally this would just be a sender of hashes
to_engine: UnboundedSender<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
/// Used to notify consumers of new blocks
canon_state_notification: CanonStateNotificationSender,
/// The pipeline events to listen on
@ -43,12 +44,12 @@ pub struct MiningTask<Client, Pool: TransactionPool> {
// === impl MiningTask ===
impl<Client, Pool: TransactionPool> MiningTask<Client, Pool> {
impl<Client, Pool: TransactionPool, Engine: EngineTypes> MiningTask<Client, Pool, Engine> {
/// Creates a new instance of the task
pub(crate) fn new(
chain_spec: Arc<ChainSpec>,
miner: MiningMode,
to_engine: UnboundedSender<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
canon_state_notification: CanonStateNotificationSender,
storage: Storage,
client: Client,
@ -74,11 +75,12 @@ impl<Client, Pool: TransactionPool> MiningTask<Client, Pool> {
}
}
impl<Client, Pool> Future for MiningTask<Client, Pool>
impl<Client, Pool, Engine> Future for MiningTask<Client, Pool, Engine>
where
Client: StateProviderFactory + CanonChainTracker + Clone + Unpin + 'static,
Pool: TransactionPool + Unpin + 'static,
<Pool as TransactionPool>::Transaction: IntoRecoveredTransaction,
Engine: EngineTypes + 'static,
{
type Output = ();
@ -228,7 +230,9 @@ where
}
}
impl<Client, Pool: TransactionPool> std::fmt::Debug for MiningTask<Client, Pool> {
impl<Client, Pool: TransactionPool, Engine: EngineTypes> std::fmt::Debug
for MiningTask<Client, Pool, Engine>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MiningTask").finish_non_exhaustive()
}

View File

@ -25,6 +25,7 @@ reth-payload-validator.workspace = true
reth-prune.workspace = true
reth-snapshot.workspace = true
reth-tokio-util.workspace = true
reth-node-api.workspace = true
# async
tokio = { workspace = true, features = ["sync"] }
@ -53,6 +54,7 @@ reth-rpc-types-compat.workspace = true
reth-tracing.workspace = true
reth-revm.workspace = true
reth-downloaders.workspace = true
reth-node-builder.workspace = true
assert_matches.workspace = true

View File

@ -6,9 +6,9 @@ use crate::{
};
use futures::TryFutureExt;
use reth_interfaces::RethResult;
use reth_node_api::EngineTypes;
use reth_rpc_types::engine::{
CancunPayloadFields, ExecutionPayload, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes,
PayloadStatus,
CancunPayloadFields, ExecutionPayload, ForkchoiceState, ForkchoiceUpdated, PayloadStatus,
};
use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};
use tokio_stream::wrappers::UnboundedReceiverStream;
@ -17,16 +17,31 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
/// engine task.
///
/// See also [`BeaconConsensusEngine`](crate::engine::BeaconConsensusEngine).
#[derive(Clone, Debug)]
pub struct BeaconConsensusEngineHandle {
pub(crate) to_engine: UnboundedSender<BeaconEngineMessage>,
#[derive(Debug)]
pub struct BeaconConsensusEngineHandle<Engine>
where
Engine: EngineTypes,
{
pub(crate) to_engine: UnboundedSender<BeaconEngineMessage<Engine>>,
}
impl<Engine> Clone for BeaconConsensusEngineHandle<Engine>
where
Engine: EngineTypes,
{
fn clone(&self) -> Self {
Self { to_engine: self.to_engine.clone() }
}
}
// === impl BeaconConsensusEngineHandle ===
impl BeaconConsensusEngineHandle {
impl<Engine> BeaconConsensusEngineHandle<Engine>
where
Engine: EngineTypes,
{
/// Creates a new beacon consensus engine handle.
pub fn new(to_engine: UnboundedSender<BeaconEngineMessage>) -> Self {
pub fn new(to_engine: UnboundedSender<BeaconEngineMessage<Engine>>) -> Self {
Self { to_engine }
}
@ -49,7 +64,7 @@ impl BeaconConsensusEngineHandle {
pub async fn fork_choice_updated(
&self,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<Engine::PayloadAttributes>,
) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
Ok(self
.send_fork_choice_updated(state, payload_attrs)
@ -63,7 +78,7 @@ impl BeaconConsensusEngineHandle {
fn send_fork_choice_updated(
&self,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<Engine::PayloadAttributes>,
) -> oneshot::Receiver<RethResult<OnForkChoiceUpdated>> {
let (tx, rx) = oneshot::channel();
let _ = self.to_engine.send(BeaconEngineMessage::ForkchoiceUpdated {

View File

@ -4,10 +4,11 @@ use crate::{
};
use futures::{future::Either, FutureExt};
use reth_interfaces::{consensus::ForkchoiceState, RethResult};
use reth_node_api::EngineTypes;
use reth_payload_builder::error::PayloadBuilderError;
use reth_rpc_types::engine::{
CancunPayloadFields, ExecutionPayload, ForkChoiceUpdateResult, ForkchoiceUpdateError,
ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus, PayloadStatusEnum,
ForkchoiceUpdated, PayloadId, PayloadStatus, PayloadStatusEnum,
};
use std::{
future::Future,
@ -141,7 +142,7 @@ impl Future for PendingPayloadId {
/// consensus layer).
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BeaconEngineMessage {
pub enum BeaconEngineMessage<Engine: EngineTypes> {
/// Message with new payload.
NewPayload {
/// The execution payload received by Engine API.
@ -156,7 +157,7 @@ pub enum BeaconEngineMessage {
/// The updated forkchoice state.
state: ForkchoiceState,
/// The payload attributes for block building.
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<Engine::PayloadAttributes>,
/// The sender for returning forkchoice updated result.
tx: oneshot::Sender<RethResult<OnForkChoiceUpdated>>,
},

View File

@ -20,7 +20,8 @@ use reth_interfaces::{
sync::{NetworkSyncUpdater, SyncState},
RethError, RethResult,
};
use reth_payload_builder::{PayloadBuilderAttributes, PayloadBuilderHandle};
use reth_node_api::{EngineTypes, PayloadAttributes, PayloadBuilderAttributes};
use reth_payload_builder::PayloadBuilderHandle;
use reth_primitives::{
constants::EPOCH_SLOTS, stage::StageId, BlockNumHash, BlockNumber, Head, Header, SealedBlock,
SealedHeader, B256,
@ -30,8 +31,7 @@ use reth_provider::{
StageCheckpointReader,
};
use reth_rpc_types::engine::{
CancunPayloadFields, ExecutionPayload, PayloadAttributes, PayloadStatus, PayloadStatusEnum,
PayloadValidationError,
CancunPayloadFields, ExecutionPayload, PayloadStatus, PayloadStatusEnum, PayloadValidationError,
};
use reth_stages::{ControlFlow, Pipeline, PipelineError};
@ -164,7 +164,7 @@ pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
/// If the future is polled more than once. Leads to undefined state.
#[must_use = "Future does nothing unless polled"]
#[allow(missing_debug_implementations)]
pub struct BeaconConsensusEngine<DB, BT, Client>
pub struct BeaconConsensusEngine<DB, BT, Client, EngineT>
where
DB: Database,
Client: HeadersClient + BodiesClient,
@ -173,6 +173,7 @@ where
+ BlockIdReader
+ CanonChainTracker
+ StageCheckpointReader,
EngineT: EngineTypes,
{
/// Controls syncing triggered by engine updates.
sync: EngineSyncController<DB, Client>,
@ -181,13 +182,13 @@ where
/// Used for emitting updates about whether the engine is syncing or not.
sync_state_updater: Box<dyn NetworkSyncUpdater>,
/// The Engine API message receiver.
engine_message_rx: UnboundedReceiverStream<BeaconEngineMessage>,
engine_message_rx: UnboundedReceiverStream<BeaconEngineMessage<EngineT>>,
/// A clone of the handle
handle: BeaconConsensusEngineHandle,
handle: BeaconConsensusEngineHandle<EngineT>,
/// Tracks the received forkchoice state updates received by the CL.
forkchoice_state_tracker: ForkchoiceStateTracker,
/// The payload store.
payload_builder: PayloadBuilderHandle,
payload_builder: PayloadBuilderHandle<EngineT>,
/// Validator for execution payloads
payload_validator: ExecutionPayloadValidator,
/// Listeners for engine events.
@ -212,7 +213,7 @@ where
hooks: EngineHooksController,
}
impl<DB, BT, Client> BeaconConsensusEngine<DB, BT, Client>
impl<DB, BT, Client, EngineT> BeaconConsensusEngine<DB, BT, Client, EngineT>
where
DB: Database + Unpin + 'static,
BT: BlockchainTreeEngine
@ -223,6 +224,7 @@ where
+ ChainSpecProvider
+ 'static,
Client: HeadersClient + BodiesClient + Clone + Unpin + 'static,
EngineT: EngineTypes + Unpin + 'static,
{
/// Create a new instance of the [BeaconConsensusEngine].
#[allow(clippy::too_many_arguments)]
@ -234,11 +236,11 @@ where
sync_state_updater: Box<dyn NetworkSyncUpdater>,
max_block: Option<BlockNumber>,
run_pipeline_continuously: bool,
payload_builder: PayloadBuilderHandle,
payload_builder: PayloadBuilderHandle<EngineT>,
target: Option<B256>,
pipeline_run_threshold: u64,
hooks: EngineHooks,
) -> RethResult<(Self, BeaconConsensusEngineHandle)> {
) -> RethResult<(Self, BeaconConsensusEngineHandle<EngineT>)> {
let (to_engine, rx) = mpsc::unbounded_channel();
Self::with_channel(
client,
@ -278,13 +280,13 @@ where
sync_state_updater: Box<dyn NetworkSyncUpdater>,
max_block: Option<BlockNumber>,
run_pipeline_continuously: bool,
payload_builder: PayloadBuilderHandle,
payload_builder: PayloadBuilderHandle<EngineT>,
target: Option<B256>,
pipeline_run_threshold: u64,
to_engine: UnboundedSender<BeaconEngineMessage>,
rx: UnboundedReceiver<BeaconEngineMessage>,
to_engine: UnboundedSender<BeaconEngineMessage<EngineT>>,
rx: UnboundedReceiver<BeaconEngineMessage<EngineT>>,
hooks: EngineHooks,
) -> RethResult<(Self, BeaconConsensusEngineHandle)> {
) -> RethResult<(Self, BeaconConsensusEngineHandle<EngineT>)> {
let handle = BeaconConsensusEngineHandle { to_engine };
let sync = EngineSyncController::new(
pipeline,
@ -323,6 +325,219 @@ where
Ok((this, handle))
}
/// Called to resolve chain forks and ensure that the Execution layer is working with the latest
/// valid chain.
///
/// These responses should adhere to the [Engine API Spec for
/// `engine_forkchoiceUpdated`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-1).
///
/// Returns an error if an internal error occurred like a database error.
fn forkchoice_updated(
&mut self,
state: ForkchoiceState,
attrs: Option<EngineT::PayloadAttributes>,
) -> RethResult<OnForkChoiceUpdated> {
trace!(target: "consensus::engine", ?state, "Received new forkchoice state update");
if state.head_block_hash.is_zero() {
return Ok(OnForkChoiceUpdated::invalid_state());
}
// check if the new head hash is connected to any ancestor that we previously marked as
// invalid
let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu) {
return Ok(OnForkChoiceUpdated::with_invalid(status));
}
if self.sync.is_pipeline_active() {
// We can only process new forkchoice updates if the pipeline is idle, since it requires
// exclusive access to the database
trace!(target: "consensus::engine", "Pipeline is syncing, skipping forkchoice update");
return Ok(OnForkChoiceUpdated::syncing());
}
if let Some(hook) = self.hooks.active_db_write_hook() {
// We can only process new forkchoice updates if no hook with db write is running,
// since it requires exclusive access to the database
warn!(
target: "consensus::engine",
hook = %hook.name(),
"Hook is in progress, skipping forkchoice update. \
This may affect the performance of your node as a validator."
);
return Ok(OnForkChoiceUpdated::syncing());
}
let start = Instant::now();
let make_canonical_result = self.blockchain.make_canonical(&state.head_block_hash);
let elapsed = self.record_make_canonical_latency(start, &make_canonical_result);
let status = match make_canonical_result {
Ok(outcome) => {
match &outcome {
CanonicalOutcome::AlreadyCanonical { header } => {
// On Optimism, the proposers are allowed to reorg their own chain at will.
cfg_if::cfg_if! {
if #[cfg(feature = "optimism")] {
if self.blockchain.chain_spec().is_optimism() {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"[Optimism] Allowing beacon reorg to old head"
);
let _ = self.update_head(header.clone());
self.listeners.notify(
BeaconConsensusEngineEvent::CanonicalChainCommitted(
Box::new(header.clone()),
elapsed,
),
);
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
}
}
CanonicalOutcome::Committed { head } => {
debug!(
target: "consensus::engine",
hash=?state.head_block_hash,
number=head.number,
"Canonicalized new head"
);
// new VALID update that moved the canonical chain forward
let _ = self.update_head(head.clone());
self.listeners.notify(BeaconConsensusEngineEvent::CanonicalChainCommitted(
Box::new(head.clone()),
elapsed,
));
}
}
if let Some(attrs) = attrs {
// if we return early then we wouldn't perform these consistency checks, so we
// need to do them here, and should do them before we process any payload
// attributes
if let Some(invalid_fcu_response) = self.ensure_consistent_state(state)? {
trace!(target: "consensus::engine", ?state, head=?state.head_block_hash, "Forkchoice state is inconsistent, returning invalid response");
return Ok(invalid_fcu_response);
}
// the CL requested to build a new payload on top of this new VALID head
let payload_response = self.process_payload_attributes(
attrs,
outcome.into_header().unseal(),
state,
);
trace!(target: "consensus::engine", status = ?payload_response, ?state, "Returning forkchoice status");
return Ok(payload_response);
}
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash))
}
Err(error) => {
if let RethError::Canonical(ref err) = error {
if err.is_fatal() {
tracing::error!(target: "consensus::engine", ?err, "Encountered fatal error");
return Err(error);
}
}
self.on_failed_canonical_forkchoice_update(&state, error)
}
};
if let Some(invalid_fcu_response) =
self.ensure_consistent_state_with_status(state, &status)?
{
trace!(target: "consensus::engine", ?status, ?state, "Forkchoice state is inconsistent, returning invalid response");
return Ok(invalid_fcu_response);
}
trace!(target: "consensus::engine", ?status, ?state, "Returning forkchoice status");
Ok(OnForkChoiceUpdated::valid(status))
}
/// Invoked when we receive a new forkchoice update message.
///
/// Returns `true` if the engine now reached its maximum block number, See
/// [EngineSyncController::has_reached_max_block].
fn on_forkchoice_updated(
&mut self,
state: ForkchoiceState,
attrs: Option<EngineT::PayloadAttributes>,
tx: oneshot::Sender<Result<OnForkChoiceUpdated, RethError>>,
) -> OnForkchoiceUpdateOutcome {
self.metrics.forkchoice_updated_messages.increment(1);
self.blockchain.on_forkchoice_update_received(&state);
let on_updated = match self.forkchoice_updated(state, attrs) {
Ok(response) => response,
Err(error) => {
if let RethError::Execution(ref err) = error {
if err.is_fatal() {
// FCU resulted in a fatal error from which we can't recover
let err = err.clone();
let _ = tx.send(Err(error));
return OnForkchoiceUpdateOutcome::Fatal(err);
}
}
let _ = tx.send(Err(error));
return OnForkchoiceUpdateOutcome::Processed;
}
};
let fcu_status = on_updated.forkchoice_status();
// update the forkchoice state tracker
self.forkchoice_state_tracker.set_latest(state, fcu_status);
// send the response to the CL ASAP
let _ = tx.send(Ok(on_updated));
match fcu_status {
ForkchoiceStatus::Invalid => {}
ForkchoiceStatus::Valid => {
// FCU head is valid, we're no longer syncing
self.sync_state_updater.update_sync_state(SyncState::Idle);
// node's fully synced, clear active download requests
self.sync.clear_block_download_requests();
// check if we reached the maximum configured block
let tip_number = self.blockchain.canonical_tip().number;
if self.sync.has_reached_max_block(tip_number) {
// Terminate the sync early if it's reached the maximum user
// configured block.
return OnForkchoiceUpdateOutcome::ReachedMaxBlock;
}
}
ForkchoiceStatus::Syncing => {
// we're syncing
self.sync_state_updater.update_sync_state(SyncState::Syncing);
}
}
// notify listeners about new processed FCU
self.listeners.notify(BeaconConsensusEngineEvent::ForkchoiceUpdated(state, fcu_status));
OnForkchoiceUpdateOutcome::Processed
}
/// Check if the pipeline is consistent (all stages have the checkpoint block numbers no less
/// than the checkpoint of the first stage).
///
@ -370,7 +585,7 @@ where
///
/// The [`BeaconConsensusEngineHandle`] can be used to interact with this
/// [`BeaconConsensusEngine`]
pub fn handle(&self) -> BeaconConsensusEngineHandle {
pub fn handle(&self) -> BeaconConsensusEngineHandle<EngineT> {
self.handle.clone()
}
@ -554,219 +769,6 @@ where
Some(status)
}
/// Invoked when we receive a new forkchoice update message.
///
/// Returns `true` if the engine now reached its maximum block number, See
/// [EngineSyncController::has_reached_max_block].
fn on_forkchoice_updated(
&mut self,
state: ForkchoiceState,
attrs: Option<PayloadAttributes>,
tx: oneshot::Sender<Result<OnForkChoiceUpdated, RethError>>,
) -> OnForkchoiceUpdateOutcome {
self.metrics.forkchoice_updated_messages.increment(1);
self.blockchain.on_forkchoice_update_received(&state);
let on_updated = match self.forkchoice_updated(state, attrs) {
Ok(response) => response,
Err(error) => {
if let RethError::Execution(ref err) = error {
if err.is_fatal() {
// FCU resulted in a fatal error from which we can't recover
let err = err.clone();
let _ = tx.send(Err(error));
return OnForkchoiceUpdateOutcome::Fatal(err);
}
}
let _ = tx.send(Err(error));
return OnForkchoiceUpdateOutcome::Processed;
}
};
let fcu_status = on_updated.forkchoice_status();
// update the forkchoice state tracker
self.forkchoice_state_tracker.set_latest(state, fcu_status);
// send the response to the CL ASAP
let _ = tx.send(Ok(on_updated));
match fcu_status {
ForkchoiceStatus::Invalid => {}
ForkchoiceStatus::Valid => {
// FCU head is valid, we're no longer syncing
self.sync_state_updater.update_sync_state(SyncState::Idle);
// node's fully synced, clear active download requests
self.sync.clear_block_download_requests();
// check if we reached the maximum configured block
let tip_number = self.blockchain.canonical_tip().number;
if self.sync.has_reached_max_block(tip_number) {
// Terminate the sync early if it's reached the maximum user
// configured block.
return OnForkchoiceUpdateOutcome::ReachedMaxBlock;
}
}
ForkchoiceStatus::Syncing => {
// we're syncing
self.sync_state_updater.update_sync_state(SyncState::Syncing);
}
}
// notify listeners about new processed FCU
self.listeners.notify(BeaconConsensusEngineEvent::ForkchoiceUpdated(state, fcu_status));
OnForkchoiceUpdateOutcome::Processed
}
/// Called to resolve chain forks and ensure that the Execution layer is working with the latest
/// valid chain.
///
/// These responses should adhere to the [Engine API Spec for
/// `engine_forkchoiceUpdated`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-1).
///
/// Returns an error if an internal error occurred like a database error.
fn forkchoice_updated(
&mut self,
state: ForkchoiceState,
attrs: Option<PayloadAttributes>,
) -> RethResult<OnForkChoiceUpdated> {
trace!(target: "consensus::engine", ?state, "Received new forkchoice state update");
if state.head_block_hash.is_zero() {
return Ok(OnForkChoiceUpdated::invalid_state());
}
// check if the new head hash is connected to any ancestor that we previously marked as
// invalid
let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu) {
return Ok(OnForkChoiceUpdated::with_invalid(status));
}
if self.sync.is_pipeline_active() {
// We can only process new forkchoice updates if the pipeline is idle, since it requires
// exclusive access to the database
trace!(target: "consensus::engine", "Pipeline is syncing, skipping forkchoice update");
return Ok(OnForkChoiceUpdated::syncing());
}
if let Some(hook) = self.hooks.active_db_write_hook() {
// We can only process new forkchoice updates if no hook with db write is running,
// since it requires exclusive access to the database
warn!(
target: "consensus::engine",
hook = %hook.name(),
"Hook is in progress, skipping forkchoice update. \
This may affect the performance of your node as a validator."
);
return Ok(OnForkChoiceUpdated::syncing());
}
let start = Instant::now();
let make_canonical_result = self.blockchain.make_canonical(&state.head_block_hash);
let elapsed = self.record_make_canonical_latency(start, &make_canonical_result);
let status = match make_canonical_result {
Ok(outcome) => {
match &outcome {
CanonicalOutcome::AlreadyCanonical { header } => {
// On Optimism, the proposers are allowed to reorg their own chain at will.
cfg_if::cfg_if! {
if #[cfg(feature = "optimism")] {
if self.blockchain.chain_spec().is_optimism() {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"[Optimism] Allowing beacon reorg to old head"
);
let _ = self.update_head(header.clone());
self.listeners.notify(
BeaconConsensusEngineEvent::CanonicalChainCommitted(
Box::new(header.clone()),
elapsed,
),
);
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
}
}
CanonicalOutcome::Committed { head } => {
debug!(
target: "consensus::engine",
hash=?state.head_block_hash,
number=head.number,
"Canonicalized new head"
);
// new VALID update that moved the canonical chain forward
let _ = self.update_head(head.clone());
self.listeners.notify(BeaconConsensusEngineEvent::CanonicalChainCommitted(
Box::new(head.clone()),
elapsed,
));
}
}
if let Some(attrs) = attrs {
// if we return early then we wouldn't perform these consistency checks, so we
// need to do them here, and should do them before we process any payload
// attributes
if let Some(invalid_fcu_response) = self.ensure_consistent_state(state)? {
trace!(target: "consensus::engine", ?state, head=?state.head_block_hash, "Forkchoice state is inconsistent, returning invalid response");
return Ok(invalid_fcu_response);
}
// the CL requested to build a new payload on top of this new VALID head
let payload_response = self.process_payload_attributes(
attrs,
outcome.into_header().unseal(),
state,
);
trace!(target: "consensus::engine", status = ?payload_response, ?state, "Returning forkchoice status");
return Ok(payload_response);
}
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash))
}
Err(error) => {
if let RethError::Canonical(ref err) = error {
if err.is_fatal() {
tracing::error!(target: "consensus::engine", ?err, "Encountered fatal error");
return Err(error);
}
}
self.on_failed_canonical_forkchoice_update(&state, error)
}
};
if let Some(invalid_fcu_response) =
self.ensure_consistent_state_with_status(state, &status)?
{
trace!(target: "consensus::engine", ?status, ?state, "Forkchoice state is inconsistent, returning invalid response");
return Ok(invalid_fcu_response);
}
trace!(target: "consensus::engine", ?status, ?state, "Returning forkchoice status");
Ok(OnForkChoiceUpdated::valid(status))
}
/// Record latency metrics for one call to make a block canonical
/// Takes start time of the call and result of the make canonical call
///
@ -1051,55 +1053,6 @@ where
.unwrap_or_else(|| hash)
}
/// Validates the payload attributes with respect to the header and fork choice state.
///
/// Note: At this point, the fork choice update is considered to be VALID, however, we can still
/// return an error if the payload attributes are invalid.
fn process_payload_attributes(
&self,
attrs: PayloadAttributes,
head: Header,
state: ForkchoiceState,
) -> OnForkChoiceUpdated {
// 7. Client software MUST ensure that payloadAttributes.timestamp is greater than timestamp
// of a block referenced by forkchoiceState.headBlockHash. If this condition isn't held
// client software MUST respond with -38003: `Invalid payload attributes` and MUST NOT
// begin a payload build process. In such an event, the forkchoiceState update MUST NOT
// be rolled back.
if attrs.timestamp <= head.timestamp {
return OnForkChoiceUpdated::invalid_payload_attributes();
}
// 8. Client software MUST begin a payload build process building on top of
// forkchoiceState.headBlockHash and identified via buildProcessId value if
// payloadAttributes is not null and the forkchoice state has been updated successfully.
// The build process is specified in the Payload building section.
match PayloadBuilderAttributes::try_new(state.head_block_hash, attrs) {
Ok(attributes) => {
// send the payload to the builder and return the receiver for the pending payload
// id, initiating payload job is handled asynchronously
let pending_payload_id = self.payload_builder.send_new_payload(attributes);
// Client software MUST respond to this method call in the following way:
// {
// payloadStatus: {
// status: VALID,
// latestValidHash: forkchoiceState.headBlockHash,
// validationError: null
// },
// payloadId: buildProcessId
// }
//
// if the payload is deemed VALID and the build process has begun.
OnForkChoiceUpdated::updated_with_pending_payload_id(
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
pending_payload_id,
)
}
Err(_) => OnForkChoiceUpdated::invalid_payload_attributes(),
}
}
/// When the Consensus layer receives a new block via the consensus gossip protocol,
/// the transactions in the block are sent to the execution layer in the form of a
/// [`ExecutionPayload`]. The Execution layer executes the transactions and validates the
@ -1224,6 +1177,58 @@ where
}
}
/// Validates the payload attributes with respect to the header and fork choice state.
///
/// Note: At this point, the fork choice update is considered to be VALID, however, we can still
/// return an error if the payload attributes are invalid.
fn process_payload_attributes(
&self,
attrs: EngineT::PayloadAttributes,
head: Header,
state: ForkchoiceState,
) -> OnForkChoiceUpdated {
// 7. Client software MUST ensure that payloadAttributes.timestamp is greater than timestamp
// of a block referenced by forkchoiceState.headBlockHash. If this condition isn't held
// client software MUST respond with -38003: `Invalid payload attributes` and MUST NOT
// begin a payload build process. In such an event, the forkchoiceState update MUST NOT
// be rolled back.
if attrs.timestamp() <= head.timestamp {
return OnForkChoiceUpdated::invalid_payload_attributes();
}
// 8. Client software MUST begin a payload build process building on top of
// forkchoiceState.headBlockHash and identified via buildProcessId value if
// payloadAttributes is not null and the forkchoice state has been updated successfully.
// The build process is specified in the Payload building section.
match <EngineT::PayloadBuilderAttributes as PayloadBuilderAttributes>::try_new(
state.head_block_hash,
attrs,
) {
Ok(attributes) => {
// send the payload to the builder and return the receiver for the pending payload
// id, initiating payload job is handled asynchronously
let pending_payload_id = self.payload_builder.send_new_payload(attributes);
// Client software MUST respond to this method call in the following way:
// {
// payloadStatus: {
// status: VALID,
// latestValidHash: forkchoiceState.headBlockHash,
// validationError: null
// },
// payloadId: buildProcessId
// }
//
// if the payload is deemed VALID and the build process has begun.
OnForkChoiceUpdated::updated_with_pending_payload_id(
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
pending_payload_id,
)
}
Err(_) => OnForkChoiceUpdated::invalid_payload_attributes(),
}
}
/// When the pipeline is active, the tree is unable to commit any additional blocks since the
/// pipeline holds exclusive access to the database.
///
@ -1735,7 +1740,7 @@ where
/// local forkchoice state, it will launch the pipeline to sync to the head hash.
/// While the pipeline is syncing, the consensus engine will keep processing messages from the
/// receiver and forwarding them to the blockchain tree.
impl<DB, BT, Client> Future for BeaconConsensusEngine<DB, BT, Client>
impl<DB, BT, Client, EngineT> Future for BeaconConsensusEngine<DB, BT, Client, EngineT>
where
DB: Database + Unpin + 'static,
Client: HeadersClient + BodiesClient + Clone + Unpin + 'static,
@ -1747,6 +1752,7 @@ where
+ ChainSpecProvider
+ Unpin
+ 'static,
EngineT: EngineTypes + Unpin + 'static,
{
type Output = Result<(), BeaconConsensusEngineError>;

View File

@ -22,6 +22,7 @@ use reth_interfaces::{
sync::NoopSyncStateUpdater,
test_utils::{NoopFullBlockClient, TestConsensus},
};
use reth_node_builder::EthEngineTypes;
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_primitives::{BlockNumber, ChainSpec, PruneModes, Receipt, B256, U256};
use reth_provider::{
@ -49,6 +50,7 @@ type TestBeaconConsensusEngine<Client> = BeaconConsensusEngine<
>,
>,
Arc<EitherDownloader<Client, NoopFullBlockClient>>,
EthEngineTypes,
>;
#[derive(Debug)]
@ -57,14 +59,14 @@ pub struct TestEnv<DB> {
// Keep the tip receiver around, so it's not dropped.
#[allow(dead_code)]
tip_rx: watch::Receiver<B256>,
engine_handle: BeaconConsensusEngineHandle,
engine_handle: BeaconConsensusEngineHandle<EthEngineTypes>,
}
impl<DB> TestEnv<DB> {
fn new(
db: DB,
tip_rx: watch::Receiver<B256>,
engine_handle: BeaconConsensusEngineHandle,
engine_handle: BeaconConsensusEngineHandle<EthEngineTypes>,
) -> Self {
Self { db, tip_rx, engine_handle }
}
@ -453,7 +455,7 @@ where
}
TestConsensusConfig::Test => Arc::new(TestConsensus::default()),
};
let payload_builder = spawn_test_payload_service();
let payload_builder = spawn_test_payload_service::<EthEngineTypes>();
// use either noop client or a user provided client (for example TestFullBlockClient)
let client = Arc::new(
@ -552,9 +554,12 @@ where
}
}
pub fn spawn_consensus_engine<Client: HeadersClient + BodiesClient + 'static>(
pub fn spawn_consensus_engine<Client>(
engine: TestBeaconConsensusEngine<Client>,
) -> oneshot::Receiver<Result<(), BeaconConsensusEngineError>> {
) -> oneshot::Receiver<Result<(), BeaconConsensusEngineError>>
where
Client: HeadersClient + BodiesClient + 'static,
{
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let result = engine.await;

View File

@ -0,0 +1,24 @@
[package]
name = "reth-node-api"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
# reth
reth-primitives.workspace = true
reth-rpc-types.workspace = true
thiserror.workspace = true
# io
serde.workspace = true
[dev-dependencies]
# for examples
reth-payload-builder.workspace = true

View File

@ -0,0 +1,41 @@
//! Defines a payload validation error type
use thiserror::Error;
/// Thrown when the payload or attributes are known to be invalid before processing.
#[derive(Error, Debug)]
pub enum AttributesValidationError {
/// Thrown if `PayloadAttributes` provided in engine_forkchoiceUpdated before V3 contains a
/// parent beacon block root
#[error("parent beacon block root not supported before V3")]
ParentBeaconBlockRootNotSupportedBeforeV3,
/// Thrown if engine_forkchoiceUpdatedV1 contains withdrawals
#[error("withdrawals not supported in V1")]
WithdrawalsNotSupportedInV1,
/// Thrown if engine_forkchoiceUpdated contains no withdrawals after Shanghai
#[error("no withdrawals post-Shanghai")]
NoWithdrawalsPostShanghai,
/// Thrown if engine_forkchoiceUpdated contains withdrawals before Shanghai
#[error("withdrawals pre-Shanghai")]
HasWithdrawalsPreShanghai,
/// Thrown if the `PayloadAttributes` provided in engine_forkchoiceUpdated contains no parent
/// beacon block root after Cancun
#[error("no parent beacon block root post-cancun")]
NoParentBeaconBlockRootPostCancun,
/// Thrown if `PayloadAttributes` were provided with a timestamp, but the version of the engine
/// method called is meant for a fork that occurs after the provided timestamp.
#[error("Unsupported fork")]
UnsupportedFork,
/// Another type of error that is not covered by the above variants.
#[error("Invalid params: {0}")]
InvalidParams(#[from] Box<dyn std::error::Error + Send + Sync>),
}
impl AttributesValidationError {
/// Creates an instance of the `InvalidParams` variant with the given error.
pub fn invalid_params<E>(error: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
Self::InvalidParams(Box::new(error))
}
}

View File

@ -0,0 +1,323 @@
//! This contains the [EngineTypes] trait and implementations for ethereum mainnet types.
//!
//! The [EngineTypes] trait can be implemented to configure the engine to work with custom types,
//! as long as those types implement certain traits.
//!
//! Custom payload attributes can be supported by implementing two main traits:
//!
//! [PayloadAttributes] can be implemented for payload attributes types that are used as
//! arguments to the `engine_forkchoiceUpdated` method. This type should be used to define and
//! _spawn_ payload jobs.
//!
//! [PayloadBuilderAttributes] can be implemented for payload attributes types that _describe_
//! running payload jobs.
//!
//! Once traits are implemented and custom types are defined, the [EngineTypes] trait can be
//! implemented:
//! ```no_run
//! # use reth_rpc_types::engine::{PayloadAttributes as EthPayloadAttributes, PayloadId, Withdrawal};
//! # use reth_primitives::{B256, ChainSpec, Address};
//! # use reth_node_api::{EngineTypes, EngineApiMessageVersion, validate_version_specific_fields, AttributesValidationError, PayloadAttributes, PayloadBuilderAttributes};
//! # use reth_payload_builder::EthPayloadBuilderAttributes;
//! # use serde::{Deserialize, Serialize};
//! # use thiserror::Error;
//! # use std::convert::Infallible;
//!
//! /// A custom payload attributes type.
//! #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
//! pub struct CustomPayloadAttributes {
//! /// An inner payload type
//! #[serde(flatten)]
//! pub inner: EthPayloadAttributes,
//! /// A custom field
//! pub custom: u64,
//! }
//!
//! /// Custom error type used in payload attributes validation
//! #[derive(Debug, Error)]
//! pub enum CustomError {
//! #[error("Custom field is not zero")]
//! CustomFieldIsNotZero,
//! }
//!
//! impl PayloadAttributes for CustomPayloadAttributes {
//! fn timestamp(&self) -> u64 {
//! self.inner.timestamp()
//! }
//!
//! fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
//! self.inner.withdrawals()
//! }
//!
//! fn parent_beacon_block_root(&self) -> Option<B256> {
//! self.inner.parent_beacon_block_root()
//! }
//!
//! fn ensure_well_formed_attributes(
//! &self,
//! chain_spec: &ChainSpec,
//! version: EngineApiMessageVersion,
//! ) -> Result<(), AttributesValidationError> {
//! validate_version_specific_fields(chain_spec, version, &self.into())?;
//!
//! // custom validation logic - ensure that the custom field is not zero
//! if self.custom == 0 {
//! return Err(AttributesValidationError::invalid_params(
//! CustomError::CustomFieldIsNotZero,
//! ))
//! }
//!
//! Ok(())
//! }
//! }
//!
//! /// Newtype around the payload builder attributes type
//! #[derive(Clone, Debug, PartialEq, Eq)]
//! pub struct CustomPayloadBuilderAttributes(EthPayloadBuilderAttributes);
//!
//! impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes {
//! type RpcPayloadAttributes = CustomPayloadAttributes;
//! type Error = Infallible;
//!
//! fn try_new(parent: B256, attributes: CustomPayloadAttributes) -> Result<Self, Infallible> {
//! Ok(Self(EthPayloadBuilderAttributes::new(parent, attributes.inner)))
//! }
//!
//! fn parent(&self) -> B256 {
//! self.0.parent
//! }
//!
//! fn payload_id(&self) -> PayloadId {
//! self.0.id
//! }
//!
//! fn timestamp(&self) -> u64 {
//! self.0.timestamp
//! }
//!
//! fn parent_beacon_block_root(&self) -> Option<B256> {
//! self.0.parent_beacon_block_root
//! }
//!
//! fn suggested_fee_recipient(&self) -> Address {
//! self.0.suggested_fee_recipient
//! }
//!
//! fn prev_randao(&self) -> B256 {
//! self.0.prev_randao
//! }
//!
//! fn withdrawals(&self) -> &Vec<reth_primitives::Withdrawal> {
//! &self.0.withdrawals
//! }
//! }
//!
//! /// Custom engine types - uses a custom payload attributes RPC type, but uses the default
//! /// payload builder attributes type.
//! #[derive(Clone, Debug, Default)]
//! #[non_exhaustive]
//! pub struct CustomEngineTypes;
//!
//! impl EngineTypes for CustomEngineTypes {
//! type PayloadAttributes = CustomPayloadAttributes;
//! type PayloadBuilderAttributes = CustomPayloadBuilderAttributes;
//! }
//! ```
use reth_primitives::{ChainSpec, Hardfork};
/// Contains traits to abstract over payload attributes types and default implementations of the
/// [PayloadAttributes] trait for ethereum mainnet and optimism types.
pub mod traits;
pub use traits::{PayloadAttributes, PayloadBuilderAttributes};
/// Contains error types used in the traits defined in this crate.
pub mod error;
pub use error::AttributesValidationError;
/// Contains types used in implementations of the [PayloadAttributes] trait.
pub mod payload;
pub use payload::PayloadOrAttributes;
/// The types that are used by the engine.
pub trait EngineTypes: Send + Sync {
/// The RPC payload attributes type the CL node emits via the engine API.
type PayloadAttributes: PayloadAttributes + Unpin;
/// The payload attributes type that contains information about a running payload job.
type PayloadBuilderAttributes: PayloadBuilderAttributes<RpcPayloadAttributes = Self::PayloadAttributes>
+ Clone
+ Unpin;
// TODO: payload type
}
/// Validates the timestamp depending on the version called:
///
/// * If V2, this ensure that the payload timestamp is pre-Cancun.
/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp.
///
/// Otherwise, this will return [AttributesValidationError::UnsupportedFork].
pub fn validate_payload_timestamp(
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
timestamp: u64,
) -> Result<(), AttributesValidationError> {
let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp);
if version == EngineApiMessageVersion::V2 && is_cancun {
// From the Engine API spec:
//
// ### Update the methods of previous forks
//
// This document defines how Cancun payload should be handled by the [`Shanghai
// API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md).
//
// For the following methods:
//
// - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2)
// - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2)
// - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2)
//
// a validation **MUST** be added:
//
// 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
// payload or payloadAttributes greater or equal to the Cancun activation timestamp.
return Err(AttributesValidationError::UnsupportedFork)
}
if version == EngineApiMessageVersion::V3 && !is_cancun {
// From the Engine API spec:
// <https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/cancun.md#specification-2>
//
// 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of
// the built payload does not fall within the time frame of the Cancun fork.
return Err(AttributesValidationError::UnsupportedFork)
}
Ok(())
}
/// Validates the presence of the `withdrawals` field according to the payload timestamp.
/// After Shanghai, withdrawals field must be [Some].
/// Before Shanghai, withdrawals field must be [None];
pub fn validate_withdrawals_presence(
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
timestamp: u64,
has_withdrawals: bool,
) -> Result<(), AttributesValidationError> {
let is_shanghai = chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(timestamp);
match version {
EngineApiMessageVersion::V1 => {
if has_withdrawals {
return Err(AttributesValidationError::WithdrawalsNotSupportedInV1)
}
if is_shanghai {
return Err(AttributesValidationError::NoWithdrawalsPostShanghai)
}
}
EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 => {
if is_shanghai && !has_withdrawals {
return Err(AttributesValidationError::NoWithdrawalsPostShanghai)
}
if !is_shanghai && has_withdrawals {
return Err(AttributesValidationError::HasWithdrawalsPreShanghai)
}
}
};
Ok(())
}
/// Validate the presence of the `parentBeaconBlockRoot` field according to the payload
/// timestamp.
///
/// After Cancun, `parentBeaconBlockRoot` field must be [Some].
/// Before Cancun, `parentBeaconBlockRoot` field must be [None].
///
/// If the engine API message version is V1 or V2, and the payload attribute's timestamp is
/// post-Cancun, then this will return [AttributesValidationError::UnsupportedFork].
///
/// If the payload attribute's timestamp is before the Cancun fork and the engine API message
/// version is V3, then this will return [AttributesValidationError::UnsupportedFork].
///
/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then
/// this will return [AttributesValidationError::NoParentBeaconBlockRootPostCancun].
///
/// This implements the following Engine API spec rules:
///
/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any
/// field having `null` value **MUST** be considered as not provided.
///
/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `payloadAttributes`
/// is set and the `payloadAttributes.timestamp` does not fall within the time frame of the
/// Cancun fork.
pub fn validate_parent_beacon_block_root_presence(
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
timestamp: u64,
has_parent_beacon_block_root: bool,
) -> Result<(), AttributesValidationError> {
// 1. Client software **MUST** check that provided set of parameters and their fields strictly
// matches the expected one and return `-32602: Invalid params` error if this check fails.
// Any field having `null` value **MUST** be considered as not provided.
match version {
EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => {
if has_parent_beacon_block_root {
return Err(AttributesValidationError::ParentBeaconBlockRootNotSupportedBeforeV3)
}
}
EngineApiMessageVersion::V3 => {
if !has_parent_beacon_block_root {
return Err(AttributesValidationError::NoParentBeaconBlockRootPostCancun)
}
}
};
// 2. Client software **MUST** return `-38005: Unsupported fork` error if the
// `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the
// time frame of the Cancun fork.
validate_payload_timestamp(chain_spec, version, timestamp)?;
Ok(())
}
/// Validates the presence or exclusion of fork-specific fields based on the payload attributes
/// and the message version.
pub fn validate_version_specific_fields<Type>(
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
payload_or_attrs: &PayloadOrAttributes<'_, Type>,
) -> Result<(), AttributesValidationError>
where
Type: PayloadAttributes,
{
validate_withdrawals_presence(
chain_spec,
version,
payload_or_attrs.timestamp(),
payload_or_attrs.withdrawals().is_some(),
)?;
validate_parent_beacon_block_root_presence(
chain_spec,
version,
payload_or_attrs.timestamp(),
payload_or_attrs.parent_beacon_block_root().is_some(),
)
}
/// The version of Engine API message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineApiMessageVersion {
/// Version 1
V1,
/// Version 2
///
/// Added for shanghai hardfork.
V2,
/// Version 3
///
/// Added for cancun hardfork.
V3,
}

View File

@ -1,8 +1,10 @@
use crate::PayloadAttributes;
use reth_primitives::B256;
use reth_rpc_types::engine::ExecutionPayload;
use reth_rpc_types::engine::{ExecutionPayload, PayloadAttributes};
/// Either an [ExecutionPayload] or a [PayloadAttributes].
pub(crate) enum PayloadOrAttributes<'a> {
/// Either an [ExecutionPayload] or a types that implements the [PayloadAttributes] trait.
#[derive(Debug)]
pub enum PayloadOrAttributes<'a, AttributesType> {
/// An [ExecutionPayload] and optional parent beacon block root.
ExecutionPayload {
/// The inner execution payload
@ -10,14 +12,17 @@ pub(crate) enum PayloadOrAttributes<'a> {
/// The parent beacon block root
parent_beacon_block_root: Option<B256>,
},
/// A [PayloadAttributes].
PayloadAttributes(&'a PayloadAttributes),
/// A payload attributes type.
PayloadAttributes(&'a AttributesType),
}
impl<'a> PayloadOrAttributes<'a> {
impl<'a, AttributesType> PayloadOrAttributes<'a, AttributesType>
where
AttributesType: PayloadAttributes,
{
/// Construct a [PayloadOrAttributes] from an [ExecutionPayload] and optional parent beacon
/// block root.
pub(crate) fn from_execution_payload(
pub fn from_execution_payload(
payload: &'a ExecutionPayload,
parent_beacon_block_root: Option<B256>,
) -> Self {
@ -25,32 +30,35 @@ impl<'a> PayloadOrAttributes<'a> {
}
/// Return the withdrawals for the payload or attributes.
pub(crate) fn withdrawals(&self) -> Option<&Vec<reth_rpc_types::engine::payload::Withdrawal>> {
pub fn withdrawals(&self) -> Option<&Vec<reth_rpc_types::engine::payload::Withdrawal>> {
match self {
Self::ExecutionPayload { payload, .. } => payload.withdrawals(),
Self::PayloadAttributes(attributes) => attributes.withdrawals.as_ref(),
Self::PayloadAttributes(attributes) => attributes.withdrawals(),
}
}
/// Return the timestamp for the payload or attributes.
pub(crate) fn timestamp(&self) -> u64 {
pub fn timestamp(&self) -> u64 {
match self {
Self::ExecutionPayload { payload, .. } => payload.timestamp(),
Self::PayloadAttributes(attributes) => attributes.timestamp,
Self::PayloadAttributes(attributes) => attributes.timestamp(),
}
}
/// Return the parent beacon block root for the payload or attributes.
pub(crate) fn parent_beacon_block_root(&self) -> Option<B256> {
pub fn parent_beacon_block_root(&self) -> Option<B256> {
match self {
Self::ExecutionPayload { parent_beacon_block_root, .. } => *parent_beacon_block_root,
Self::PayloadAttributes(attributes) => attributes.parent_beacon_block_root,
Self::PayloadAttributes(attributes) => attributes.parent_beacon_block_root(),
}
}
}
impl<'a> From<&'a PayloadAttributes> for PayloadOrAttributes<'a> {
fn from(attributes: &'a PayloadAttributes) -> Self {
impl<'a, AttributesType> From<&'a AttributesType> for PayloadOrAttributes<'a, AttributesType>
where
AttributesType: PayloadAttributes,
{
fn from(attributes: &'a AttributesType) -> Self {
Self::PayloadAttributes(attributes)
}
}

View File

@ -0,0 +1,189 @@
use crate::{validate_version_specific_fields, AttributesValidationError, EngineApiMessageVersion};
use reth_primitives::{
revm::config::revm_spec_by_timestamp_after_merge,
revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId},
Address, ChainSpec, Header, B256, U256,
};
use reth_rpc_types::engine::{
OptimismPayloadAttributes, PayloadAttributes as EthPayloadAttributes, PayloadId, Withdrawal,
};
/// This can be implemented by types that describe a currently running payload job.
///
/// This is used as a conversion type, transforming a payload attributes type that the engine API
/// receives, into a type that the payload builder can use.
pub trait PayloadBuilderAttributes: Send + Sync + std::fmt::Debug {
/// The payload attributes that can be used to construct this type. Used as the argument in
/// [PayloadBuilderAttributes::try_new].
type RpcPayloadAttributes;
/// The error type used in [PayloadBuilderAttributes::try_new].
type Error: std::error::Error;
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
fn try_new(
parent: B256,
rpc_payload_attributes: Self::RpcPayloadAttributes,
) -> Result<Self, Self::Error>
where
Self: Sized;
/// Returns the [PayloadId] for the running payload job.
fn payload_id(&self) -> PayloadId;
/// Returns the parent block hash for the running payload job.
fn parent(&self) -> B256;
/// Returns the timestmap for the running payload job.
fn timestamp(&self) -> u64;
/// Returns the parent beacon block root for the running payload job, if it exists.
fn parent_beacon_block_root(&self) -> Option<B256>;
/// Returns the suggested fee recipient for the running payload job.
fn suggested_fee_recipient(&self) -> Address;
/// Returns the prevrandao field for the running payload job.
fn prev_randao(&self) -> B256;
/// Returns the withdrawals for the running payload job.
fn withdrawals(&self) -> &Vec<reth_primitives::Withdrawal>;
/// Returns the configured [CfgEnv] and [BlockEnv] for the targeted payload (that has the
/// `parent` as its parent).
///
/// The `chain_spec` is used to determine the correct chain id and hardfork for the payload
/// based on its timestamp.
///
/// Block related settings are derived from the `parent` block and the configured attributes.
///
/// NOTE: This is only intended for beacon consensus (after merge).
fn cfg_and_block_env(&self, chain_spec: &ChainSpec, parent: &Header) -> (CfgEnv, BlockEnv) {
// TODO: should be different once revm has configurable cfgenv
// configure evm env based on parent block
let mut cfg = CfgEnv::default();
cfg.chain_id = chain_spec.chain().id();
#[cfg(feature = "optimism")]
{
cfg.optimism = chain_spec.is_optimism();
}
// ensure we're not missing any timestamp based hardforks
cfg.spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp());
// if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
// cancun now, we need to set the excess blob gas to the default value
let blob_excess_gas_and_price = parent
.next_block_excess_blob_gas()
.map_or_else(
|| {
if cfg.spec_id == SpecId::CANCUN {
// default excess blob gas is zero
Some(0)
} else {
None
}
},
Some,
)
.map(BlobExcessGasAndPrice::new);
let block_env = BlockEnv {
number: U256::from(parent.number + 1),
coinbase: self.suggested_fee_recipient(),
timestamp: U256::from(self.timestamp()),
difficulty: U256::ZERO,
prevrandao: Some(self.prev_randao()),
gas_limit: U256::from(parent.gas_limit),
// calculate basefee based on parent block's gas usage
basefee: U256::from(
parent
.next_block_base_fee(chain_spec.base_fee_params(self.timestamp()))
.unwrap_or_default(),
),
// calculate excess gas based on parent block's blob gas usage
blob_excess_gas_and_price,
};
(cfg, block_env)
}
}
/// The execution payload attribute type the CL node emits via the engine API.
/// This trait should be implemented by types that could be used to spawn a payload job.
///
/// This type is emitted as part of the forkchoiceUpdated call
pub trait PayloadAttributes:
serde::de::DeserializeOwned + serde::Serialize + std::fmt::Debug + Clone + Send + Sync + 'static
{
/// Returns the timestamp to be used in the payload job.
fn timestamp(&self) -> u64;
/// Returns the withdrawals for the given payload attributes.
fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
/// Return the parent beacon block root for the payload attributes.
fn parent_beacon_block_root(&self) -> Option<B256>;
/// Ensures that the payload attributes are valid for the given [ChainSpec] and
/// [EngineApiMessageVersion].
fn ensure_well_formed_attributes(
&self,
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
) -> Result<(), AttributesValidationError>;
}
impl PayloadAttributes for EthPayloadAttributes {
fn timestamp(&self) -> u64 {
self.timestamp
}
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
self.withdrawals.as_ref()
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.parent_beacon_block_root
}
fn ensure_well_formed_attributes(
&self,
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
) -> Result<(), AttributesValidationError> {
validate_version_specific_fields(chain_spec, version, &self.into())
}
}
impl PayloadAttributes for OptimismPayloadAttributes {
fn timestamp(&self) -> u64 {
self.payload_attributes.timestamp
}
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
self.payload_attributes.withdrawals.as_ref()
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.payload_attributes.parent_beacon_block_root
}
fn ensure_well_formed_attributes(
&self,
chain_spec: &ChainSpec,
version: EngineApiMessageVersion,
) -> Result<(), AttributesValidationError> {
validate_version_specific_fields(chain_spec, version, &self.into())?;
if self.gas_limit.is_none() && chain_spec.is_optimism() {
return Err(AttributesValidationError::InvalidParams(
"MissingGasLimitInPayloadAttributes".to_string().into(),
))
}
Ok(())
}
}

View File

@ -0,0 +1,18 @@
//! Standalone crate for Reth configuration traits and builder types.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
/// Traits, validation methods, and helper types used to abstract over engine types.
///
/// Notably contains the [EngineTypes] trait and implementations for ethereum mainnet types.
pub mod engine;
pub use engine::{
validate_payload_timestamp, validate_version_specific_fields, validate_withdrawals_presence,
AttributesValidationError, EngineApiMessageVersion, EngineTypes, PayloadAttributes,
PayloadBuilderAttributes, PayloadOrAttributes,
};

View File

@ -0,0 +1,21 @@
[package]
name = "reth-node-builder"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
# reth
reth-primitives.workspace = true
reth-payload-builder.workspace = true
reth-rpc-types.workspace = true
reth-node-api.workspace = true
# io
serde.workspace = true

View File

@ -0,0 +1,23 @@
use reth_node_api::EngineTypes;
use reth_payload_builder::{EthPayloadBuilderAttributes, OptimismPayloadBuilderAttributes};
use reth_rpc_types::engine::{OptimismPayloadAttributes, PayloadAttributes};
/// The types used in the default mainnet ethereum beacon consensus engine.
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct EthEngineTypes;
impl EngineTypes for EthEngineTypes {
type PayloadAttributes = PayloadAttributes;
type PayloadBuilderAttributes = EthPayloadBuilderAttributes;
}
/// The types used in the optimism beacon consensus engine.
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct OptimismEngineTypes;
impl EngineTypes for OptimismEngineTypes {
type PayloadAttributes = OptimismPayloadAttributes;
type PayloadBuilderAttributes = OptimismPayloadBuilderAttributes;
}

View File

@ -0,0 +1,13 @@
//! Standalone crate for Reth configuration and builder types.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
/// Exports commonly used concrete instances of the [EngineTypes](reth_node_api::EngineTypes)
/// trait.
pub mod engine;
pub use engine::{EthEngineTypes, OptimismEngineTypes};

View File

@ -20,6 +20,7 @@ reth-provider.workspace = true
reth-payload-builder.workspace = true
reth-tasks.workspace = true
reth-interfaces.workspace = true
reth-node-api.workspace = true
# ethereum
alloy-rlp.workspace = true

View File

@ -7,13 +7,15 @@
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
use crate::metrics::PayloadBuilderMetrics;
use alloy_rlp::Encodable;
use futures_core::ready;
use futures_util::FutureExt;
use reth_interfaces::RethResult;
use reth_node_api::PayloadBuilderAttributes;
use reth_payload_builder::{
database::CachedReads, error::PayloadBuilderError, BuiltPayload, KeepPayloadJobAlive,
PayloadBuilderAttributes, PayloadId, PayloadJob, PayloadJobGenerator,
PayloadId, PayloadJob, PayloadJobGenerator,
};
use reth_primitives::{
bytes::BytesMut,
@ -52,8 +54,6 @@ use tokio::{
};
use tracing::{debug, trace, warn};
use crate::metrics::PayloadBuilderMetrics;
mod metrics;
/// The [`PayloadJobGenerator`] that creates [`BasicPayloadJob`]s.
@ -153,27 +153,28 @@ where
Pool: TransactionPool + Unpin + 'static,
Tasks: TaskSpawner + Clone + Unpin + 'static,
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
<Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
{
type Job = BasicPayloadJob<Client, Pool, Tasks, Builder>;
fn new_payload_job(
&self,
attributes: PayloadBuilderAttributes,
attributes: <Self::Job as PayloadJob>::PayloadAttributes,
) -> Result<Self::Job, PayloadBuilderError> {
let parent_block = if attributes.parent.is_zero() {
let parent_block = if attributes.parent().is_zero() {
// use latest block if parent is zero: genesis block
self.client
.block_by_number_or_tag(BlockNumberOrTag::Latest)?
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent))?
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?
.seal_slow()
} else {
let block = self
.client
.find_block_by_hash(attributes.parent, BlockSource::Any)?
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent))?;
.find_block_by_hash(attributes.parent(), BlockSource::Any)?
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?;
// we already know the hash, so we can seal it
block.seal(attributes.parent)
block.seal(attributes.parent())
};
let config = PayloadConfig::new(
@ -183,7 +184,7 @@ where
Arc::clone(&self.chain_spec),
);
let until = self.job_deadline(config.attributes.timestamp);
let until = self.job_deadline(config.attributes.timestamp());
let deadline = Box::pin(tokio::time::sleep_until(until));
let cached_reads = self.maybe_pre_cached(config.parent_block.hash());
@ -325,9 +326,12 @@ impl Default for BasicPayloadJobGeneratorConfig {
/// A basic payload job that continuously builds a payload with the best transactions from the pool.
#[derive(Debug)]
pub struct BasicPayloadJob<Client, Pool, Tasks, Builder> {
pub struct BasicPayloadJob<Client, Pool, Tasks, Builder>
where
Builder: PayloadBuilder<Pool, Client>,
{
/// The configuration for how the payload will be created.
config: PayloadConfig,
config: PayloadConfig<Builder::Attributes>,
/// The client that can interact with the chain.
client: Client,
/// The transaction pool.
@ -363,6 +367,7 @@ where
Pool: TransactionPool + Unpin + 'static,
Tasks: TaskSpawner + Clone + 'static,
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
<Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
{
type Output = Result<(), PayloadBuilderError>;
@ -452,7 +457,9 @@ where
Pool: TransactionPool + Unpin + 'static,
Tasks: TaskSpawner + Clone + 'static,
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
<Builder as PayloadBuilder<Pool, Client>>::Attributes: PayloadBuilderAttributes + Unpin + Clone,
{
type PayloadAttributes = Builder::Attributes;
type ResolvePayloadFuture = ResolveBestPayload;
fn best_payload(&self) -> Result<Arc<BuiltPayload>, PayloadBuilderError> {
@ -469,7 +476,7 @@ where
build_empty_payload(&self.client, self.config.clone()).map(Arc::new)
}
fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError> {
fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
Ok(self.config.attributes.clone())
}
@ -490,6 +497,9 @@ where
best_payload: None,
};
// TODO: create optimism payload job, that wraps this type, that implements PayloadJob
// with this branch. remove this branch from the non-op code. remove
// `on_missing_payload` requirement from builder trait
if let Some(payload) = self.builder.on_missing_payload(args) {
return (
ResolveBestPayload { best_payload: Some(payload), maybe_better, empty_payload },
@ -618,7 +628,7 @@ impl Drop for Cancelled {
/// Static config for how to build a payload.
#[derive(Clone, Debug)]
pub struct PayloadConfig {
pub struct PayloadConfig<Attributes> {
/// Pre-configured block environment.
pub initialized_block_env: BlockEnv,
/// Configuration for the environment.
@ -628,29 +638,27 @@ pub struct PayloadConfig {
/// Block extra data.
pub extra_data: Bytes,
/// Requested attributes for the payload.
pub attributes: PayloadBuilderAttributes,
pub attributes: Attributes,
/// The chain spec.
pub chain_spec: Arc<ChainSpec>,
}
impl PayloadConfig {
impl<Attributes> PayloadConfig<Attributes> {
/// Returns an owned instance of the [PayloadConfig]'s extra_data bytes.
pub fn extra_data(&self) -> Bytes {
self.extra_data.clone()
}
/// Returns the payload id.
pub fn payload_id(&self) -> PayloadId {
self.attributes.id
}
}
impl PayloadConfig {
impl<Attributes> PayloadConfig<Attributes>
where
Attributes: PayloadBuilderAttributes,
{
/// Create new payload config.
pub fn new(
parent_block: Arc<SealedBlock>,
extra_data: Bytes,
attributes: PayloadBuilderAttributes,
attributes: Attributes,
chain_spec: Arc<ChainSpec>,
) -> Self {
// configure evm env based on parent block
@ -666,6 +674,11 @@ impl PayloadConfig {
chain_spec,
}
}
/// Returns the payload id.
pub fn payload_id(&self) -> PayloadId {
self.attributes.payload_id()
}
}
/// The possible outcomes of a payload building attempt.
@ -695,7 +708,7 @@ pub enum BuildOutcome {
/// building process. It holds references to the Ethereum client, transaction pool, cached reads,
/// payload configuration, cancellation status, and the best payload achieved so far.
#[derive(Debug)]
pub struct BuildArguments<Pool, Client> {
pub struct BuildArguments<Pool, Client, Attributes> {
/// How to interact with the chain.
pub client: Client,
/// The transaction pool.
@ -703,20 +716,20 @@ pub struct BuildArguments<Pool, Client> {
/// Previously cached disk reads
pub cached_reads: CachedReads,
/// How to configure the payload.
pub config: PayloadConfig,
pub config: PayloadConfig<Attributes>,
/// A marker that can be used to cancel the job.
pub cancel: Cancelled,
/// The best payload achieved so far.
pub best_payload: Option<Arc<BuiltPayload>>,
}
impl<Pool, Client> BuildArguments<Pool, Client> {
impl<Pool, Client, Attributes> BuildArguments<Pool, Client, Attributes> {
/// Create new build arguments.
pub fn new(
client: Client,
pool: Pool,
cached_reads: CachedReads,
config: PayloadConfig,
config: PayloadConfig<Attributes>,
cancel: Cancelled,
best_payload: Option<Arc<BuiltPayload>>,
) -> Self {
@ -733,6 +746,9 @@ impl<Pool, Client> BuildArguments<Pool, Client> {
/// Generic parameters `Pool` and `Client` represent the transaction pool and
/// Ethereum client types.
pub trait PayloadBuilder<Pool, Client>: Send + Sync + Clone {
/// The payload attributes type to accept for building.
type Attributes: PayloadBuilderAttributes + Send + Sync + std::fmt::Debug;
/// Tries to build a transaction payload using provided arguments.
///
/// Constructs a transaction payload based on the given arguments,
@ -747,7 +763,7 @@ pub trait PayloadBuilder<Pool, Client>: Send + Sync + Clone {
/// A `Result` indicating the build outcome or an error.
fn try_build(
&self,
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, Self::Attributes>,
) -> Result<BuildOutcome, PayloadBuilderError>;
/// Invoked when the payload job is being resolved and there is no payload yet.
@ -755,19 +771,23 @@ pub trait PayloadBuilder<Pool, Client>: Send + Sync + Clone {
/// If this returns a payload, it will be used as the final payload for the job.
///
/// TODO(mattsse): This needs to be refined a bit because this only exists for OP atm
fn on_missing_payload(&self, args: BuildArguments<Pool, Client>) -> Option<Arc<BuiltPayload>> {
fn on_missing_payload(
&self,
args: BuildArguments<Pool, Client, Self::Attributes>,
) -> Option<Arc<BuiltPayload>> {
let _args = args;
None
}
}
/// Builds an empty payload without any transactions.
fn build_empty_payload<Client>(
fn build_empty_payload<Client, Attributes>(
client: &Client,
config: PayloadConfig,
config: PayloadConfig<Attributes>,
) -> Result<BuiltPayload, PayloadBuilderError>
where
Client: StateProviderFactory,
Attributes: PayloadBuilderAttributes,
{
let extra_data = config.extra_data();
let PayloadConfig {
@ -808,7 +828,7 @@ where
})?;
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals).map_err(|err| {
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp(), attributes.withdrawals().clone()).map_err(|err| {
warn!(target: "payload_builder", parent_hash=%parent_block.hash,?err, "failed to commit withdrawals for empty payload");
err
})?;
@ -834,8 +854,8 @@ where
withdrawals_root,
receipts_root: EMPTY_RECEIPTS,
logs_bloom: Default::default(),
timestamp: attributes.timestamp,
mix_hash: attributes.prev_randao,
timestamp: attributes.timestamp(),
mix_hash: attributes.prev_randao(),
nonce: BEACON_NONCE,
base_fee_per_gas: Some(base_fee),
number: parent_block.number + 1,
@ -845,13 +865,13 @@ where
extra_data,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: attributes.parent_beacon_block_root,
parent_beacon_block_root: attributes.parent_beacon_block_root(),
};
let block = Block { header, body: vec![], ommers: vec![], withdrawals };
let sealed_block = block.seal_slow();
Ok(BuiltPayload::new(attributes.id, sealed_block, U256::ZERO))
Ok(BuiltPayload::new(attributes.payload_id(), sealed_block, U256::ZERO))
}
/// Represents the outcome of committing withdrawals to the runtime database and post state.
@ -919,16 +939,17 @@ pub fn commit_withdrawals<DB: Database<Error = ProviderError>>(
///
/// This uses [apply_beacon_root_contract_call] to ultimately apply the beacon root contract state
/// change.
pub fn pre_block_beacon_root_contract_call<DB: Database + DatabaseCommit>(
pub fn pre_block_beacon_root_contract_call<DB: Database + DatabaseCommit, Attributes>(
db: &mut DB,
chain_spec: &ChainSpec,
block_number: u64,
initialized_cfg: &CfgEnv,
initialized_block_env: &BlockEnv,
attributes: &PayloadBuilderAttributes,
attributes: &Attributes,
) -> Result<(), PayloadBuilderError>
where
DB::Error: std::fmt::Display,
Attributes: PayloadBuilderAttributes,
{
// Configure the environment for the block.
let env = Env {
@ -944,9 +965,9 @@ where
// initialize a block from the env, because the pre block call needs the block itself
apply_beacon_root_contract_call(
chain_spec,
attributes.timestamp,
attributes.timestamp(),
block_number,
attributes.parent_beacon_block_root,
attributes.parent_beacon_block_root(),
&mut evm_pre_block,
)
.map_err(|err| PayloadBuilderError::Internal(err.into()))

View File

@ -20,6 +20,7 @@ reth-interfaces.workspace = true
reth-rpc-types-compat.workspace = true
reth-provider.workspace = true
reth-tasks.workspace = true
reth-node-api.workspace = true
# ethereum
alloy-rlp.workspace = true
@ -29,6 +30,7 @@ revm-primitives.workspace = true
tokio = { workspace = true, features = ["sync"] }
tokio-stream.workspace = true
futures-util.workspace = true
async-trait.workspace = true
# metrics
reth-metrics.workspace = true
@ -39,6 +41,9 @@ thiserror.workspace = true
sha2 = { version = "0.10", default-features = false }
tracing.workspace = true
# serde, for traits
serde.workspace = true
[dev-dependencies]
revm.workspace = true

View File

@ -28,7 +28,7 @@
//! use std::pin::Pin;
//! use std::sync::Arc;
//! use std::task::{Context, Poll};
//! use reth_payload_builder::{BuiltPayload, KeepPayloadJobAlive, PayloadBuilderAttributes, PayloadJob, PayloadJobGenerator};
//! use reth_payload_builder::{BuiltPayload, KeepPayloadJobAlive, EthPayloadBuilderAttributes, PayloadJob, PayloadJobGenerator};
//! use reth_payload_builder::error::PayloadBuilderError;
//! use reth_primitives::{Block, Header, U256};
//!
@ -39,7 +39,7 @@
//! type Job = EmptyBlockPayloadJob;
//!
//! /// This is invoked when the node receives payload attributes from the beacon node via `engine_forkchoiceUpdatedV1`
//! fn new_payload_job(&self, attr: PayloadBuilderAttributes) -> Result<Self::Job, PayloadBuilderError> {
//! fn new_payload_job(&self, attr: EthPayloadBuilderAttributes) -> Result<Self::Job, PayloadBuilderError> {
//! Ok(EmptyBlockPayloadJob{ attributes: attr,})
//! }
//!
@ -47,10 +47,11 @@
//!
//! /// A [PayloadJob] that builds empty blocks.
//! pub struct EmptyBlockPayloadJob {
//! attributes: PayloadBuilderAttributes,
//! attributes: EthPayloadBuilderAttributes,
//! }
//!
//! impl PayloadJob for EmptyBlockPayloadJob {
//! type PayloadAttributes = EthPayloadBuilderAttributes;
//! type ResolvePayloadFuture = futures_util::future::Ready<Result<Arc<BuiltPayload>, PayloadBuilderError>>;
//!
//! fn best_payload(&self) -> Result<Arc<BuiltPayload>, PayloadBuilderError> {
@ -68,7 +69,7 @@
//! Ok(Arc::new(payload))
//! }
//!
//! fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError> {
//! fn payload_attributes(&self) -> Result<EthPayloadBuilderAttributes, PayloadBuilderError> {
//! Ok(self.attributes.clone())
//! }
//!
@ -111,7 +112,7 @@ pub mod noop;
#[cfg(any(test, feature = "test-utils"))]
pub mod test_utils;
pub use payload::{BuiltPayload, PayloadBuilderAttributes};
pub use payload::{BuiltPayload, EthPayloadBuilderAttributes, OptimismPayloadBuilderAttributes};
pub use reth_rpc_types::engine::PayloadId;
pub use service::{PayloadBuilderHandle, PayloadBuilderService, PayloadStore};
pub use traits::{KeepPayloadJobAlive, PayloadJob, PayloadJobGenerator};

View File

@ -2,6 +2,7 @@
use crate::{service::PayloadServiceCommand, PayloadBuilderHandle};
use futures_util::{ready, StreamExt};
use reth_node_api::{EngineTypes, PayloadBuilderAttributes};
use std::{
future::Future,
pin::Pin,
@ -12,21 +13,27 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
/// A service task that does not build any payloads.
#[derive(Debug)]
pub struct NoopPayloadBuilderService {
pub struct NoopPayloadBuilderService<Engine: EngineTypes> {
/// Receiver half of the command channel.
command_rx: UnboundedReceiverStream<PayloadServiceCommand>,
command_rx: UnboundedReceiverStream<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
}
impl NoopPayloadBuilderService {
impl<Engine> NoopPayloadBuilderService<Engine>
where
Engine: EngineTypes,
{
/// Creates a new [NoopPayloadBuilderService].
pub fn new() -> (Self, PayloadBuilderHandle) {
pub fn new() -> (Self, PayloadBuilderHandle<Engine>) {
let (service_tx, command_rx) = mpsc::unbounded_channel();
let handle = PayloadBuilderHandle::new(service_tx);
(Self { command_rx: UnboundedReceiverStream::new(command_rx) }, handle)
}
}
impl Future for NoopPayloadBuilderService {
impl<Engine> Future for NoopPayloadBuilderService<Engine>
where
Engine: EngineTypes,
{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

View File

@ -1,24 +1,24 @@
//! Contains types required for building a payload.
use std::convert::Infallible;
use alloy_rlp::{Encodable, Error as DecodeError};
use reth_node_api::PayloadBuilderAttributes;
use reth_primitives::{
revm::config::revm_spec_by_timestamp_after_merge,
revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId},
Address, BlobTransactionSidecar, ChainSpec, Header, SealedBlock, Withdrawal, B256, U256,
Address, BlobTransactionSidecar, ChainSpec, Header, SealedBlock, TransactionSigned, Withdrawal,
B256, U256,
};
use reth_rpc_types::engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes,
PayloadId,
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1,
OptimismPayloadAttributes, PayloadAttributes, PayloadId,
};
use reth_rpc_types_compat::engine::payload::{
block_to_payload_v3, convert_block_to_payload_field_v2,
convert_standalone_withdraw_to_withdrawal, try_block_to_payload_v1,
};
#[cfg(feature = "optimism")]
use reth_primitives::TransactionSigned;
/// Contains the built payload.
///
/// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue.
@ -123,7 +123,7 @@ impl From<BuiltPayload> for ExecutionPayloadEnvelopeV3 {
/// Container type for all components required to build a payload.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PayloadBuilderAttributes {
pub struct EthPayloadBuilderAttributes {
/// Id of the payload
pub id: PayloadId,
/// Parent block to build the payload on top
@ -140,71 +140,11 @@ pub struct PayloadBuilderAttributes {
pub withdrawals: Vec<Withdrawal>,
/// Root of the parent beacon block
pub parent_beacon_block_root: Option<B256>,
/// Optimism Payload Builder Attributes
#[cfg(feature = "optimism")]
pub optimism_payload_attributes: OptimismPayloadBuilderAttributes,
}
/// Optimism Payload Builder Attributes
#[cfg(feature = "optimism")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OptimismPayloadBuilderAttributes {
/// NoTxPool option for the generated payload
pub no_tx_pool: bool,
/// Transactions for the generated payload
pub transactions: Vec<TransactionSigned>,
/// The gas limit for the generated payload
pub gas_limit: Option<u64>,
}
// === impl EthPayloadBuilderAttributes ===
// === impl PayloadBuilderAttributes ===
impl PayloadBuilderAttributes {
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
pub fn try_new(parent: B256, attributes: PayloadAttributes) -> Result<Self, DecodeError> {
#[cfg(not(feature = "optimism"))]
let id = payload_id(&parent, &attributes);
#[cfg(feature = "optimism")]
let (id, transactions) = {
let transactions: Vec<_> = attributes
.optimism_payload_attributes
.transactions
.as_deref()
.unwrap_or(&[])
.iter()
.map(|tx| TransactionSigned::decode_enveloped(&mut tx.as_ref()))
.collect::<Result<_, _>>()?;
(payload_id(&parent, &attributes, &transactions), transactions)
};
let withdraw = attributes.withdrawals.map(
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
withdrawals
.into_iter()
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
.collect::<Vec<_>>()
},
);
Ok(Self {
id,
parent,
timestamp: attributes.timestamp,
suggested_fee_recipient: attributes.suggested_fee_recipient,
prev_randao: attributes.prev_randao,
withdrawals: withdraw.unwrap_or_default(),
parent_beacon_block_root: attributes.parent_beacon_block_root,
#[cfg(feature = "optimism")]
optimism_payload_attributes: OptimismPayloadBuilderAttributes {
no_tx_pool: attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default(),
transactions,
gas_limit: attributes.optimism_payload_attributes.gas_limit,
},
})
}
impl EthPayloadBuilderAttributes {
/// Returns the configured [CfgEnv] and [BlockEnv] for the targeted payload (that has the
/// `parent` as its parent).
///
@ -268,16 +208,205 @@ impl PayloadBuilderAttributes {
pub fn payload_id(&self) -> PayloadId {
self.id
}
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
let id = payload_id(&parent, &attributes);
let withdraw = attributes.withdrawals.map(
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
withdrawals
.into_iter()
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
.collect::<Vec<_>>()
},
);
Self {
id,
parent,
timestamp: attributes.timestamp,
suggested_fee_recipient: attributes.suggested_fee_recipient,
prev_randao: attributes.prev_randao,
withdrawals: withdraw.unwrap_or_default(),
parent_beacon_block_root: attributes.parent_beacon_block_root,
}
}
}
/// Generates the payload id for the configured payload
impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
type RpcPayloadAttributes = PayloadAttributes;
type Error = Infallible;
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
fn try_new(parent: B256, attributes: PayloadAttributes) -> Result<Self, Infallible> {
Ok(Self::new(parent, attributes))
}
fn parent(&self) -> B256 {
self.parent
}
fn payload_id(&self) -> PayloadId {
self.id
}
fn timestamp(&self) -> u64 {
self.timestamp
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.parent_beacon_block_root
}
fn suggested_fee_recipient(&self) -> Address {
self.suggested_fee_recipient
}
fn prev_randao(&self) -> B256 {
self.prev_randao
}
fn withdrawals(&self) -> &Vec<Withdrawal> {
&self.withdrawals
}
}
/// Optimism Payload Builder Attributes
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OptimismPayloadBuilderAttributes {
/// Inner ethereum payload builder attributes
pub payload_attributes: EthPayloadBuilderAttributes,
/// NoTxPool option for the generated payload
pub no_tx_pool: bool,
/// Transactions for the generated payload
pub transactions: Vec<TransactionSigned>,
/// The gas limit for the generated payload
pub gas_limit: Option<u64>,
}
impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes {
type RpcPayloadAttributes = OptimismPayloadAttributes;
type Error = DecodeError;
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
fn try_new(parent: B256, attributes: OptimismPayloadAttributes) -> Result<Self, DecodeError> {
let (id, transactions) = {
let transactions: Vec<_> = attributes
.transactions
.as_deref()
.unwrap_or(&[])
.iter()
.map(|tx| TransactionSigned::decode_enveloped(&mut tx.as_ref()))
.collect::<Result<_, _>>()?;
(payload_id_optimism(&parent, &attributes, &transactions), transactions)
};
let withdraw = attributes.payload_attributes.withdrawals.map(
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
withdrawals
.into_iter()
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
.collect::<Vec<_>>()
},
);
let payload_attributes = EthPayloadBuilderAttributes {
id,
parent,
timestamp: attributes.payload_attributes.timestamp,
suggested_fee_recipient: attributes.payload_attributes.suggested_fee_recipient,
prev_randao: attributes.payload_attributes.prev_randao,
withdrawals: withdraw.unwrap_or_default(),
parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root,
};
Ok(Self {
payload_attributes,
no_tx_pool: attributes.no_tx_pool.unwrap_or_default(),
transactions,
gas_limit: attributes.gas_limit,
})
}
fn parent(&self) -> B256 {
self.payload_attributes.parent
}
fn payload_id(&self) -> PayloadId {
self.payload_attributes.id
}
fn timestamp(&self) -> u64 {
self.payload_attributes.timestamp
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.payload_attributes.parent_beacon_block_root
}
fn suggested_fee_recipient(&self) -> Address {
self.payload_attributes.suggested_fee_recipient
}
fn prev_randao(&self) -> B256 {
self.payload_attributes.prev_randao
}
fn withdrawals(&self) -> &Vec<Withdrawal> {
&self.payload_attributes.withdrawals
}
}
/// Generates the payload id for the configured payload from the [OptimismPayloadAttributes].
///
/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
pub(crate) fn payload_id(
pub(crate) fn payload_id_optimism(
parent: &B256,
attributes: &PayloadAttributes,
#[cfg(feature = "optimism")] txs: &[TransactionSigned],
attributes: &OptimismPayloadAttributes,
txs: &[TransactionSigned],
) -> PayloadId {
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
hasher.update(parent.as_slice());
hasher.update(&attributes.payload_attributes.timestamp.to_be_bytes()[..]);
hasher.update(attributes.payload_attributes.prev_randao.as_slice());
hasher.update(attributes.payload_attributes.suggested_fee_recipient.as_slice());
if let Some(withdrawals) = &attributes.payload_attributes.withdrawals {
let mut buf = Vec::new();
withdrawals.encode(&mut buf);
hasher.update(buf);
}
if let Some(parent_beacon_block) = attributes.payload_attributes.parent_beacon_block_root {
hasher.update(parent_beacon_block);
}
let no_tx_pool = attributes.no_tx_pool.unwrap_or_default();
if no_tx_pool || !txs.is_empty() {
hasher.update([no_tx_pool as u8]);
hasher.update(txs.len().to_be_bytes());
txs.iter().for_each(|tx| hasher.update(tx.hash()));
}
if let Some(gas_limit) = attributes.gas_limit {
hasher.update(gas_limit.to_be_bytes());
}
let out = hasher.finalize();
PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
}
/// Generates the payload id for the configured payload from the [PayloadAttributes].
///
/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
hasher.update(parent.as_slice());
@ -294,20 +423,6 @@ pub(crate) fn payload_id(
hasher.update(parent_beacon_block);
}
#[cfg(feature = "optimism")]
{
let no_tx_pool = attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default();
if no_tx_pool || !txs.is_empty() {
hasher.update([no_tx_pool as u8]);
hasher.update(txs.len().to_be_bytes());
txs.iter().for_each(|tx| hasher.update(tx.hash()));
}
if let Some(gas_limit) = attributes.optimism_payload_attributes.gas_limit {
hasher.update(gas_limit.to_be_bytes());
}
}
let out = hasher.finalize();
PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
}

View File

@ -5,9 +5,10 @@
use crate::{
error::PayloadBuilderError, metrics::PayloadBuilderServiceMetrics, traits::PayloadJobGenerator,
BuiltPayload, KeepPayloadJobAlive, PayloadBuilderAttributes, PayloadJob,
BuiltPayload, KeepPayloadJobAlive, PayloadJob,
};
use futures_util::{future::FutureExt, Stream, StreamExt};
use reth_node_api::{EngineTypes, PayloadBuilderAttributes};
use reth_provider::CanonStateNotification;
use reth_rpc_types::engine::PayloadId;
use std::{
@ -23,13 +24,16 @@ use tracing::{debug, info, trace, warn};
/// A communication channel to the [PayloadBuilderService] that can retrieve payloads.
#[derive(Debug, Clone)]
pub struct PayloadStore {
inner: PayloadBuilderHandle,
pub struct PayloadStore<Engine: EngineTypes> {
inner: PayloadBuilderHandle<Engine>,
}
// === impl PayloadStore ===
impl PayloadStore {
impl<Engine> PayloadStore<Engine>
where
Engine: EngineTypes,
{
/// Resolves the payload job and returns the best payload that has been built so far.
///
/// Note: depending on the installed [PayloadJobGenerator], this may or may not terminate the
@ -57,13 +61,16 @@ impl PayloadStore {
pub async fn payload_attributes(
&self,
id: PayloadId,
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
) -> Option<Result<Engine::PayloadBuilderAttributes, PayloadBuilderError>> {
self.inner.payload_attributes(id).await
}
}
impl From<PayloadBuilderHandle> for PayloadStore {
fn from(inner: PayloadBuilderHandle) -> Self {
impl<Engine> From<PayloadBuilderHandle<Engine>> for PayloadStore<Engine>
where
Engine: EngineTypes,
{
fn from(inner: PayloadBuilderHandle<Engine>) -> Self {
Self { inner }
}
}
@ -72,18 +79,24 @@ impl From<PayloadBuilderHandle> for PayloadStore {
///
/// This is the API used to create new payloads and to get the current state of existing ones.
#[derive(Debug, Clone)]
pub struct PayloadBuilderHandle {
pub struct PayloadBuilderHandle<Engine: EngineTypes> {
/// Sender half of the message channel to the [PayloadBuilderService].
to_service: mpsc::UnboundedSender<PayloadServiceCommand>,
to_service: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
}
// === impl PayloadBuilderHandle ===
impl PayloadBuilderHandle {
impl<Engine> PayloadBuilderHandle<Engine>
where
Engine: EngineTypes,
{
/// Creates a new payload builder handle for the given channel.
///
/// Note: this is only used internally by the [PayloadBuilderService] to manage the payload
/// building flow See [PayloadBuilderService::poll] for implementation details.
pub fn new(to_service: mpsc::UnboundedSender<PayloadServiceCommand>) -> Self {
pub fn new(
to_service: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
) -> Self {
Self { to_service }
}
@ -91,7 +104,7 @@ impl PayloadBuilderHandle {
///
/// Note: depending on the installed [PayloadJobGenerator], this may or may not terminate the
/// job, See [PayloadJob::resolve].
pub async fn resolve(
async fn resolve(
&self,
id: PayloadId,
) -> Option<Result<Arc<BuiltPayload>, PayloadBuilderError>> {
@ -104,7 +117,7 @@ impl PayloadBuilderHandle {
}
/// Returns the best payload for the given identifier.
pub async fn best_payload(
async fn best_payload(
&self,
id: PayloadId,
) -> Option<Result<Arc<BuiltPayload>, PayloadBuilderError>> {
@ -116,10 +129,10 @@ impl PayloadBuilderHandle {
/// Returns the payload attributes associated with the given identifier.
///
/// Note: this returns the attributes of the payload and does not resolve the job.
pub async fn payload_attributes(
async fn payload_attributes(
&self,
id: PayloadId,
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
) -> Option<Result<Engine::PayloadBuilderAttributes, PayloadBuilderError>> {
let (tx, rx) = oneshot::channel();
self.to_service.send(PayloadServiceCommand::PayloadAttributes(id, tx)).ok()?;
rx.await.ok()?
@ -131,7 +144,7 @@ impl PayloadBuilderHandle {
/// returns the receiver instead
pub fn send_new_payload(
&self,
attr: PayloadBuilderAttributes,
attr: Engine::PayloadBuilderAttributes,
) -> oneshot::Receiver<Result<PayloadId, PayloadBuilderError>> {
let (tx, rx) = oneshot::channel();
let _ = self.to_service.send(PayloadServiceCommand::BuildNewPayload(attr, tx));
@ -145,7 +158,7 @@ impl PayloadBuilderHandle {
/// Note: if there's already payload in progress with same identifier, it will be returned.
pub async fn new_payload(
&self,
attr: PayloadBuilderAttributes,
attr: Engine::PayloadBuilderAttributes,
) -> Result<PayloadId, PayloadBuilderError> {
self.send_new_payload(attr).await?
}
@ -161,18 +174,20 @@ impl PayloadBuilderHandle {
/// does know nothing about how to build them, it just drives their jobs to completion.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct PayloadBuilderService<Gen, St>
pub struct PayloadBuilderService<Gen, St, Engine>
where
Engine: EngineTypes,
Gen: PayloadJobGenerator,
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
{
/// The type that knows how to create new payloads.
generator: Gen,
/// All active payload jobs.
payload_jobs: Vec<(Gen::Job, PayloadId)>,
/// Copy of the sender half, so new [`PayloadBuilderHandle`] can be created on demand.
service_tx: mpsc::UnboundedSender<PayloadServiceCommand>,
service_tx: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
/// Receiver half of the command channel.
command_rx: UnboundedReceiverStream<PayloadServiceCommand>,
command_rx: UnboundedReceiverStream<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
/// Metrics for the payload builder service
metrics: PayloadBuilderServiceMetrics,
/// Chain events notification stream
@ -181,16 +196,18 @@ where
// === impl PayloadBuilderService ===
impl<Gen, St> PayloadBuilderService<Gen, St>
impl<Gen, St, Engine> PayloadBuilderService<Gen, St, Engine>
where
Engine: EngineTypes,
Gen: PayloadJobGenerator,
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
{
/// Creates a new payload builder service and returns the [PayloadBuilderHandle] to interact
/// with it.
///
/// This also takes a stream of chain events that will be forwarded to the generator to apply
/// additional logic when new state is committed. See also [PayloadJobGenerator::on_new_state].
pub fn new(generator: Gen, chain_events: St) -> (Self, PayloadBuilderHandle) {
pub fn new(generator: Gen, chain_events: St) -> (Self, PayloadBuilderHandle<Engine>) {
let (service_tx, command_rx) = mpsc::unbounded_channel();
let service = Self {
generator,
@ -206,7 +223,7 @@ where
}
/// Returns a handle to the service.
pub fn handle(&self) -> PayloadBuilderHandle {
pub fn handle(&self) -> PayloadBuilderHandle<Engine> {
PayloadBuilderHandle::new(self.service_tx.clone())
}
@ -232,24 +249,6 @@ where
res
}
/// Returns the payload attributes for the given payload.
fn payload_attributes(
&self,
id: PayloadId,
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
let attributes = self
.payload_jobs
.iter()
.find(|(_, job_id)| *job_id == id)
.map(|(j, _)| j.payload_attributes());
if attributes.is_none() {
trace!(%id, "no matching payload job found to get attributes for");
}
attributes
}
/// Returns the best payload for the given identifier that has been built so far and terminates
/// the job if requested.
fn resolve(&mut self, id: PayloadId) -> Option<PayloadFuture> {
@ -279,11 +278,38 @@ where
}
}
impl<Gen, St> Future for PayloadBuilderService<Gen, St>
impl<Gen, St, Engine> PayloadBuilderService<Gen, St, Engine>
where
Engine: EngineTypes,
Gen: PayloadJobGenerator,
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
{
/// Returns the payload attributes for the given payload.
fn payload_attributes(
&self,
id: PayloadId,
) -> Option<Result<<Gen::Job as PayloadJob>::PayloadAttributes, PayloadBuilderError>> {
let attributes = self
.payload_jobs
.iter()
.find(|(_, job_id)| *job_id == id)
.map(|(j, _)| j.payload_attributes());
if attributes.is_none() {
trace!(%id, "no matching payload job found to get attributes for");
}
attributes
}
}
impl<Gen, St, Engine> Future for PayloadBuilderService<Gen, St, Engine>
where
Engine: EngineTypes,
Gen: PayloadJobGenerator + Unpin + 'static,
<Gen as PayloadJobGenerator>::Job: Unpin + 'static,
St: Stream<Item = CanonStateNotification> + Send + Unpin + 'static,
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
{
type Output = ();
@ -330,10 +356,10 @@ where
let mut res = Ok(id);
if this.contains_payload(id) {
debug!(%id, parent = %attr.parent, "Payload job already in progress, ignoring.");
debug!(%id, parent = %attr.parent(), "Payload job already in progress, ignoring.");
} else {
// no job for this payload yet, create one
let parent = attr.parent;
let parent = attr.parent();
match this.generator.new_payload_job(attr) {
Ok(job) => {
info!(%id, %parent, "New payload job created");
@ -371,28 +397,26 @@ where
}
}
// TODO: make generic over built payload type
type PayloadFuture =
Pin<Box<dyn Future<Output = Result<Arc<BuiltPayload>, PayloadBuilderError>> + Send + Sync>>;
/// Message type for the [PayloadBuilderService].
pub enum PayloadServiceCommand {
pub enum PayloadServiceCommand<T> {
/// Start building a new payload.
BuildNewPayload(
PayloadBuilderAttributes,
oneshot::Sender<Result<PayloadId, PayloadBuilderError>>,
),
BuildNewPayload(T, oneshot::Sender<Result<PayloadId, PayloadBuilderError>>),
/// Get the best payload so far
BestPayload(PayloadId, oneshot::Sender<Option<Result<Arc<BuiltPayload>, PayloadBuilderError>>>),
/// Get the payload attributes for the given payload
PayloadAttributes(
PayloadId,
oneshot::Sender<Option<Result<PayloadBuilderAttributes, PayloadBuilderError>>>,
),
PayloadAttributes(PayloadId, oneshot::Sender<Option<Result<T, PayloadBuilderError>>>),
/// Resolve the payload and return the payload
Resolve(PayloadId, oneshot::Sender<Option<PayloadFuture>>),
}
impl fmt::Debug for PayloadServiceCommand {
impl<T> fmt::Debug for PayloadServiceCommand<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PayloadServiceCommand::BuildNewPayload(f0, f1) => {

View File

@ -2,9 +2,10 @@
use crate::{
error::PayloadBuilderError, traits::KeepPayloadJobAlive, BuiltPayload,
PayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, PayloadJob,
EthPayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, PayloadJob,
PayloadJobGenerator,
};
use reth_node_api::EngineTypes;
use reth_primitives::{Block, U256};
use reth_provider::CanonStateNotification;
use std::{
@ -15,18 +16,25 @@ use std::{
};
/// Creates a new [PayloadBuilderService] for testing purposes.
pub fn test_payload_service() -> (
pub fn test_payload_service<Engine>() -> (
PayloadBuilderService<
TestPayloadJobGenerator,
futures_util::stream::Empty<CanonStateNotification>,
Engine,
>,
PayloadBuilderHandle,
) {
PayloadBuilderHandle<Engine>,
)
where
Engine: EngineTypes<PayloadBuilderAttributes = EthPayloadBuilderAttributes>,
{
PayloadBuilderService::new(Default::default(), futures_util::stream::empty())
}
/// Creates a new [PayloadBuilderService] for testing purposes and spawns it in the background.
pub fn spawn_test_payload_service() -> PayloadBuilderHandle {
pub fn spawn_test_payload_service<Engine>() -> PayloadBuilderHandle<Engine>
where
Engine: EngineTypes<PayloadBuilderAttributes = EthPayloadBuilderAttributes> + 'static,
{
let (service, handle) = test_payload_service();
tokio::spawn(service);
handle
@ -42,7 +50,7 @@ impl PayloadJobGenerator for TestPayloadJobGenerator {
fn new_payload_job(
&self,
attr: PayloadBuilderAttributes,
attr: EthPayloadBuilderAttributes,
) -> Result<Self::Job, PayloadBuilderError> {
Ok(TestPayloadJob { attr })
}
@ -51,7 +59,7 @@ impl PayloadJobGenerator for TestPayloadJobGenerator {
/// A [PayloadJobGenerator] for testing purposes
#[derive(Debug)]
pub struct TestPayloadJob {
attr: PayloadBuilderAttributes,
attr: EthPayloadBuilderAttributes,
}
impl Future for TestPayloadJob {
@ -63,6 +71,7 @@ impl Future for TestPayloadJob {
}
impl PayloadJob for TestPayloadJob {
type PayloadAttributes = EthPayloadBuilderAttributes;
type ResolvePayloadFuture =
futures_util::future::Ready<Result<Arc<BuiltPayload>, PayloadBuilderError>>;
@ -74,7 +83,7 @@ impl PayloadJob for TestPayloadJob {
)))
}
fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError> {
fn payload_attributes(&self) -> Result<EthPayloadBuilderAttributes, PayloadBuilderError> {
Ok(self.attr.clone())
}

View File

@ -1,8 +1,8 @@
//! Trait abstractions used by the payload crate.
use crate::{error::PayloadBuilderError, BuiltPayload};
use reth_node_api::PayloadBuilderAttributes;
use reth_provider::CanonStateNotification;
use crate::{error::PayloadBuilderError, BuiltPayload, PayloadBuilderAttributes};
use std::{future::Future, sync::Arc};
/// A type that can build a payload.
@ -17,6 +17,8 @@ use std::{future::Future, sync::Arc};
///
/// Note: A `PayloadJob` need to be cancel safe because it might be dropped after the CL has requested the payload via `engine_getPayloadV1` (see also [engine API docs](https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1))
pub trait PayloadJob: Future<Output = Result<(), PayloadBuilderError>> + Send + Sync {
/// Represents the payload attributes type that is used to spawn this payload job.
type PayloadAttributes: PayloadBuilderAttributes + std::fmt::Debug;
/// Represents the future that resolves the block that's returned to the CL.
type ResolvePayloadFuture: Future<Output = Result<Arc<BuiltPayload>, PayloadBuilderError>>
+ Send
@ -29,7 +31,7 @@ pub trait PayloadJob: Future<Output = Result<(), PayloadBuilderError>> + Send +
fn best_payload(&self) -> Result<Arc<BuiltPayload>, PayloadBuilderError>;
/// Returns the payload attributes for the payload being built.
fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError>;
fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError>;
/// Called when the payload is requested by the CL.
///
@ -80,7 +82,7 @@ pub trait PayloadJobGenerator: Send + Sync {
/// returned directly.
fn new_payload_job(
&self,
attr: PayloadBuilderAttributes,
attr: <Self::Job as PayloadJob>::PayloadAttributes,
) -> Result<Self::Job, PayloadBuilderError>;
/// Handles new chain state events

View File

@ -16,7 +16,9 @@ mod builder {
commit_withdrawals, is_better_payload, pre_block_beacon_root_contract_call, BuildArguments,
BuildOutcome, PayloadBuilder, PayloadConfig, WithdrawalsOutcome,
};
use reth_payload_builder::{error::PayloadBuilderError, BuiltPayload};
use reth_payload_builder::{
error::PayloadBuilderError, BuiltPayload, EthPayloadBuilderAttributes,
};
use reth_primitives::{
constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE},
eip4844::calculate_excess_blob_gas,
@ -45,9 +47,11 @@ mod builder {
Client: StateProviderFactory,
Pool: TransactionPool,
{
type Attributes = EthPayloadBuilderAttributes;
fn try_build(
&self,
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, EthPayloadBuilderAttributes>,
) -> Result<BuildOutcome, PayloadBuilderError> {
default_ethereum_payload_builder(args)
}
@ -60,7 +64,7 @@ mod builder {
/// a result indicating success with the payload or an error in case of failure.
#[inline]
pub fn default_ethereum_payload_builder<Pool, Client>(
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, EthPayloadBuilderAttributes>,
) -> Result<BuildOutcome, PayloadBuilderError>
where
Client: StateProviderFactory,

View File

@ -19,6 +19,7 @@ reth-transaction-pool.workspace = true
reth-provider.workspace = true
reth-payload-builder.workspace = true
reth-basic-payload-builder.workspace = true
reth-node-api.workspace = true
# ethereum
revm.workspace = true

View File

@ -16,7 +16,10 @@ pub mod error;
mod builder {
use crate::error::OptimismPayloadBuilderError;
use reth_basic_payload_builder::*;
use reth_payload_builder::{error::PayloadBuilderError, BuiltPayload};
use reth_node_api::PayloadBuilderAttributes;
use reth_payload_builder::{
error::PayloadBuilderError, BuiltPayload, OptimismPayloadBuilderAttributes,
};
use reth_primitives::{
constants::BEACON_NONCE,
proofs,
@ -68,22 +71,24 @@ mod builder {
Client: StateProviderFactory,
Pool: TransactionPool,
{
type Attributes = OptimismPayloadBuilderAttributes;
fn try_build(
&self,
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, OptimismPayloadBuilderAttributes>,
) -> Result<BuildOutcome, PayloadBuilderError> {
optimism_payload_builder(args, self.compute_pending_block)
}
fn on_missing_payload(
&self,
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, OptimismPayloadBuilderAttributes>,
) -> Option<Arc<BuiltPayload>> {
// In Optimism, the PayloadAttributes can specify a `no_tx_pool` option that implies we
// should not pull transactions from the tx pool. In this case, we build the payload
// upfront with the list of transactions sent in the attributes without caring about
// the results of the polling job, if a best payload has not already been built.
if args.config.attributes.optimism_payload_attributes.no_tx_pool {
if args.config.attributes.no_tx_pool {
if let Ok(BuildOutcome::Better { payload, .. }) = self.try_build(args) {
trace!(target: "payload_builder", "[OPTIMISM] Forced best payload");
let payload = Arc::new(payload);
@ -105,7 +110,7 @@ mod builder {
/// a result indicating success with the payload or an error in case of failure.
#[inline]
pub(crate) fn optimism_payload_builder<Pool, Client>(
args: BuildArguments<Pool, Client>,
args: BuildArguments<Pool, Client, OptimismPayloadBuilderAttributes>,
_compute_pending_block: bool,
) -> Result<BuildOutcome, PayloadBuilderError>
where
@ -130,10 +135,9 @@ mod builder {
..
} = config;
debug!(target: "payload_builder", id=%attributes.id, parent_hash = ?parent_block.hash, parent_number = parent_block.number, "building new payload");
debug!(target: "payload_builder", id=%attributes.payload_id(), parent_hash = ?parent_block.hash, parent_number = parent_block.number, "building new payload");
let mut cumulative_gas_used = 0;
let block_gas_limit: u64 = attributes
.optimism_payload_attributes
.gas_limit
.unwrap_or(initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX));
let base_fee = initialized_block_env.basefee.to::<u64>();
@ -146,7 +150,7 @@ mod builder {
let block_number = initialized_block_env.number.to::<u64>();
let is_regolith =
chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, attributes.timestamp);
chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, attributes.timestamp());
// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism
// blocks will always have at least a single transaction in them (the L1 info transaction),
@ -154,7 +158,7 @@ mod builder {
// the above check for empty blocks will never be hit on OP chains.
reth_revm::optimism::ensure_create2_deployer(
chain_spec.clone(),
attributes.timestamp,
attributes.timestamp(),
&mut db,
)
.map_err(|_| {
@ -162,7 +166,7 @@ mod builder {
})?;
let mut receipts = Vec::new();
for sequencer_tx in attributes.optimism_payload_attributes.transactions {
for sequencer_tx in &attributes.transactions {
// Check if the job was cancelled, if so we can exit early.
if cancel.is_cancelled() {
return Ok(BuildOutcome::Cancelled)
@ -238,7 +242,7 @@ mod builder {
// receipt hashes should be computed when set. The state transition process
// ensures this is only set for post-Canyon deposit transactions.
deposit_receipt_version: chain_spec
.is_fork_active_at_timestamp(Hardfork::Canyon, attributes.timestamp)
.is_fork_active_at_timestamp(Hardfork::Canyon, attributes.timestamp())
.then_some(1),
}));
@ -246,7 +250,7 @@ mod builder {
executed_txs.push(sequencer_tx.into_signed());
}
if !attributes.optimism_payload_attributes.no_tx_pool {
if !attributes.no_tx_pool {
while let Some(pool_tx) = best_txs.next() {
// ensure we still have capacity for this transaction
if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
@ -336,8 +340,12 @@ mod builder {
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
}
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;
let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals(
&mut db,
&chain_spec,
attributes.timestamp(),
attributes.withdrawals().clone(),
)?;
// merge all transitions into bundle state, this would apply the withdrawal balance changes
// and 4788 contract call
@ -349,7 +357,7 @@ mod builder {
block_number,
);
let receipts_root = bundle
.receipts_root_slow(block_number, chain_spec.as_ref(), attributes.timestamp)
.receipts_root_slow(block_number, chain_spec.as_ref(), attributes.timestamp())
.expect("Number is in range");
let logs_bloom = bundle.block_logs_bloom(block_number).expect("Number is in range");
@ -373,8 +381,8 @@ mod builder {
receipts_root,
withdrawals_root,
logs_bloom,
timestamp: attributes.timestamp,
mix_hash: attributes.prev_randao,
timestamp: attributes.timestamp(),
mix_hash: attributes.prev_randao(),
nonce: BEACON_NONCE,
base_fee_per_gas: Some(base_fee),
number: parent_block.number + 1,
@ -382,7 +390,7 @@ mod builder {
difficulty: U256::ZERO,
gas_used: cumulative_gas_used,
extra_data,
parent_beacon_block_root: attributes.parent_beacon_block_root,
parent_beacon_block_root: attributes.parent_beacon_block_root(),
blob_gas_used,
excess_blob_gas,
};
@ -393,7 +401,7 @@ mod builder {
let sealed_block = block.seal_slow();
debug!(target: "payload_builder", ?sealed_block, "sealed built block");
let mut payload = BuiltPayload::new(attributes.id, sealed_block, total_fees);
let mut payload = BuiltPayload::new(attributes.payload_id(), sealed_block, total_fees);
// extend the payload with the blob sidecars from the executed txs
payload.extend_sidecars(blob_sidecars);

View File

@ -15,6 +15,8 @@ workspace = true
# reth
reth-primitives.workspace = true
reth-rpc-types.workspace = true
reth-payload-builder.workspace = true
reth-node-api.workspace = true
# misc
jsonrpsee = { workspace = true, features = ["server", "macros"] }

View File

@ -1,18 +1,19 @@
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_node_api::EngineTypes;
use reth_primitives::{Address, BlockHash, BlockId, BlockNumberOrTag, Bytes, B256, U256, U64};
use reth_rpc_types::{
engine::{
ExecutionPayloadBodiesV1, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
ExecutionPayloadInputV2, ExecutionPayloadV1, ExecutionPayloadV3, ForkchoiceState,
ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus, TransitionConfiguration,
ForkchoiceUpdated, PayloadId, PayloadStatus, TransitionConfiguration,
},
state::StateOverride,
BlockOverrides, CallRequest, Filter, Log, RichBlock, SyncStatus,
};
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "engine"))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "engine"))]
pub trait EngineApi {
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "engine"), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "engine", client_bounds(Engine::PayloadAttributes: jsonrpsee::core::Serialize + Clone), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned)))]
pub trait EngineApi<Engine: EngineTypes> {
/// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_newpayloadv1>
/// Caution: This should not accept the `withdrawals` field
#[method(name = "newPayloadV1")]
@ -40,13 +41,13 @@ pub trait EngineApi {
async fn fork_choice_updated_v1(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<Engine::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated>;
/// Post Shanghai forkchoice update handler
///
/// This is the same as `forkchoiceUpdatedV1`, but expects an additional `withdrawals` field in
/// the [PayloadAttributes], if payload attributes are provided.
/// the `payloadAttributes`, if payload attributes are provided.
///
/// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#engine_forkchoiceupdatedv2>
///
@ -56,21 +57,21 @@ pub trait EngineApi {
async fn fork_choice_updated_v2(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<Engine::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated>;
/// Post Cancun forkchoice update handler
///
/// This is the same as `forkchoiceUpdatedV2`, but expects an additional
/// `parentBeaconBlockRoot` field in the the [PayloadAttributes], if payload attributes are
/// provided.
/// `parentBeaconBlockRoot` field in the the `payloadAttributes`, if payload attributes
/// are provided.
///
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_forkchoiceupdatedv3>
#[method(name = "forkchoiceUpdatedV3")]
async fn fork_choice_updated_v3(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<Engine::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated>;
/// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1>
@ -147,6 +148,13 @@ pub trait EngineApi {
async fn exchange_capabilities(&self, capabilities: Vec<String>) -> RpcResult<Vec<String>>;
}
// NOTE: We can't use associated types in the `EngineApi` trait because of jsonrpsee, so we use a
// generic here. It would be nice if the rpc macro would understand which types need to have serde.
// By default, if the trait has a generic, the rpc macro will add e.g. `Engine: DeserializeOwned` to
// the trait bounds, which is not what we want, because `Types` is not used directly in any of the
// trait methods. Instead, we have to add the bounds manually. This would be disastrous if we had
// more than one associated type used in the trait methods.
/// A subset of the ETH rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
///
/// Specifically for the engine auth server: <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#underlying-protocol>

View File

@ -25,6 +25,7 @@ reth-rpc-types.workspace = true
reth-tasks.workspace = true
reth-transaction-pool.workspace = true
reth-rpc-types-compat.workspace = true
reth-node-api.workspace = true
# rpc/net
jsonrpsee = { workspace = true, features = ["server"] }
@ -51,6 +52,7 @@ reth-network-api.workspace = true
reth-interfaces = { workspace = true, features = ["test-utils"] }
reth-beacon-consensus.workspace = true
reth-payload-builder = { workspace = true, features = ["test-utils"] }
reth-node-builder.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
serde_json.workspace = true

View File

@ -11,6 +11,7 @@ use jsonrpsee::{
server::{RpcModule, ServerHandle},
};
use reth_network_api::{NetworkInfo, Peers};
use reth_node_api::EngineTypes;
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, ReceiptProviderIdExt,
StateProviderFactory,
@ -33,7 +34,7 @@ use std::{
/// Configure and launch a _standalone_ auth server with `engine` and a _new_ `eth` namespace.
#[allow(clippy::too_many_arguments)]
pub async fn launch<Provider, Pool, Network, Tasks, EngineApi>(
pub async fn launch<Provider, Pool, Network, Tasks, EngineApi, EngineT>(
provider: Provider,
pool: Pool,
network: Network,
@ -55,7 +56,8 @@ where
Pool: TransactionPool + Clone + 'static,
Network: NetworkInfo + Peers + Clone + 'static,
Tasks: TaskSpawner + Clone + 'static,
EngineApi: EngineApiServer,
EngineT: EngineTypes,
EngineApi: EngineApiServer<EngineT>,
{
// spawn a new cache task
let eth_cache =
@ -85,7 +87,7 @@ where
}
/// Configure and launch a _standalone_ auth server with existing EthApi implementation.
pub async fn launch_with_eth_api<Provider, Pool, Network, EngineApi>(
pub async fn launch_with_eth_api<Provider, Pool, Network, EngineApi, EngineT>(
eth_api: EthApi<Provider, Pool, Network>,
eth_filter: EthFilter<Provider, Pool>,
engine_api: EngineApi,
@ -103,7 +105,8 @@ where
+ 'static,
Pool: TransactionPool + Clone + 'static,
Network: NetworkInfo + Peers + Clone + 'static,
EngineApi: EngineApiServer,
EngineT: EngineTypes,
EngineApi: EngineApiServer<EngineT>,
{
// Configure the module and start the server.
let mut module = RpcModule::new(());
@ -253,9 +256,10 @@ pub struct AuthRpcModule {
impl AuthRpcModule {
/// Create a new `AuthRpcModule` with the given `engine_api`.
pub fn new<EngineApi>(engine: EngineApi) -> Self
pub fn new<EngineApi, EngineT>(engine: EngineApi) -> Self
where
EngineApi: EngineApiServer,
EngineT: EngineTypes,
EngineApi: EngineApiServer<EngineT>,
{
let mut module = RpcModule::new(());
module.merge(engine.into_rpc()).expect("No conflicting methods");

View File

@ -69,6 +69,7 @@
//!
//! ```
//! use reth_network_api::{NetworkInfo, Peers};
//! use reth_node_api::EngineTypes;
//! use reth_provider::{
//! AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider,
//! ChangeSetReader, EvmEnvProvider, StateProviderFactory,
@ -82,7 +83,7 @@
//! use reth_tasks::TokioTaskExecutor;
//! use reth_transaction_pool::TransactionPool;
//! use tokio::try_join;
//! pub async fn launch<Provider, Pool, Network, Events, EngineApi>(
//! pub async fn launch<Provider, Pool, Network, Events, EngineApi, EngineT>(
//! provider: Provider,
//! pool: Pool,
//! network: Network,
@ -101,7 +102,8 @@
//! Pool: TransactionPool + Clone + 'static,
//! Network: NetworkInfo + Peers + Clone + 'static,
//! Events: CanonStateSubscriptions + Clone + 'static,
//! EngineApi: EngineApiServer,
//! EngineApi: EngineApiServer<EngineT>,
//! EngineT: EngineTypes,
//! {
//! // configure the rpc module per transport
//! let transports = TransportRpcModuleConfig::default().with_http(vec![
@ -148,6 +150,7 @@ use jsonrpsee::{
server::{IdProvider, Server, ServerHandle},
Methods, RpcModule,
};
use reth_node_api::EngineTypes;
use serde::{Deserialize, Serialize, Serializer};
use strum::{AsRefStr, EnumIter, EnumVariantNames, IntoStaticStr, ParseError, VariantNames};
use tower::layer::util::{Identity, Stack};
@ -379,7 +382,7 @@ where
///
/// This behaves exactly as [RpcModuleBuilder::build] for the [TransportRpcModules], but also
/// configures the auth (engine api) server, which exposes a subset of the `eth_` namespace.
pub fn build_with_auth_server<EngineApi>(
pub fn build_with_auth_server<EngineApi, EngineT: EngineTypes>(
self,
module_config: TransportRpcModuleConfig,
engine: EngineApi,
@ -389,7 +392,7 @@ where
RethModuleRegistry<Provider, Pool, Network, Tasks, Events>,
)
where
EngineApi: EngineApiServer,
EngineApi: EngineApiServer<EngineT>,
{
let mut modules = TransportRpcModules::default();
@ -1020,9 +1023,10 @@ where
/// * `api_` namespace
///
/// Note: This does _not_ register the `engine_` in this registry.
pub fn create_auth_module<EngineApi>(&mut self, engine_api: EngineApi) -> AuthRpcModule
pub fn create_auth_module<EngineApi, EngineT>(&mut self, engine_api: EngineApi) -> AuthRpcModule
where
EngineApi: EngineApiServer,
EngineT: EngineTypes,
EngineApi: EngineApiServer<EngineT>,
{
let eth_handlers = self.eth_handlers();
let mut module = RpcModule::new(());

View File

@ -2,6 +2,7 @@
use crate::utils::launch_auth;
use jsonrpsee::core::client::{ClientT, SubscriptionClientT};
use reth_node_builder::EthEngineTypes;
use reth_primitives::{Block, U64};
use reth_rpc::JwtSecret;
use reth_rpc_api::clients::EngineApiClient;
@ -13,6 +14,7 @@ use reth_rpc_types_compat::engine::payload::{
async fn test_basic_engine_calls<C>(client: &C)
where
C: ClientT + SubscriptionClientT + Sync,
C: EngineApiClient<EthEngineTypes>,
{
let block = Block::default().seal_slow();
EngineApiClient::new_payload_v1(client, try_block_to_payload_v1(block.clone())).await;

View File

@ -1,5 +1,6 @@
use reth_beacon_consensus::BeaconConsensusEngineHandle;
use reth_network_api::noop::NoopNetwork;
use reth_node_builder::EthEngineTypes;
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_primitives::MAINNET;
use reth_provider::test_utils::{NoopProvider, TestCanonStateSubscriptions};
@ -24,7 +25,7 @@ pub fn test_address() -> SocketAddr {
pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle {
let config = AuthServerConfig::builder(secret).socket_addr(test_address()).build();
let (tx, _rx) = unbounded_channel();
let beacon_engine_handle = BeaconConsensusEngineHandle::new(tx);
let beacon_engine_handle = BeaconConsensusEngineHandle::<EthEngineTypes>::new(tx);
let engine_api = EngineApi::new(
NoopProvider::default(),
MAINNET.clone(),

View File

@ -22,6 +22,8 @@ reth-beacon-consensus.workspace = true
reth-payload-builder.workspace = true
reth-tasks.workspace = true
reth-rpc-types-compat.workspace = true
reth-node-api.workspace = true
# async
tokio = { workspace = true, features = ["sync"] }
@ -39,6 +41,7 @@ serde.workspace = true
[dev-dependencies]
alloy-rlp.workspace = true
reth-node-builder.workspace = true
reth-interfaces = { workspace = true, features = ["test-utils"] }
reth-provider = { workspace = true, features = ["test-utils"] }
reth-payload-builder = { workspace = true, features = ["test-utils"] }

View File

@ -1,20 +1,20 @@
use crate::{
metrics::EngineApiMetrics, payload::PayloadOrAttributes, EngineApiError,
EngineApiMessageVersion, EngineApiResult,
};
use crate::{metrics::EngineApiMetrics, EngineApiError, EngineApiResult};
use async_trait::async_trait;
use jsonrpsee_core::RpcResult;
use reth_beacon_consensus::BeaconConsensusEngineHandle;
use reth_interfaces::consensus::ForkchoiceState;
use reth_payload_builder::{PayloadBuilderAttributes, PayloadStore};
use reth_node_api::{
validate_payload_timestamp, validate_version_specific_fields, EngineApiMessageVersion,
EngineTypes, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes,
};
use reth_payload_builder::PayloadStore;
use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Hardfork, B256, U64};
use reth_provider::{BlockReader, EvmEnvProvider, HeaderProvider, StateProviderFactory};
use reth_rpc_api::EngineApiServer;
use reth_rpc_types::engine::{
CancunPayloadFields, ExecutionPayload, ExecutionPayloadBodiesV1, ExecutionPayloadEnvelopeV2,
ExecutionPayloadEnvelopeV3, ExecutionPayloadInputV2, ExecutionPayloadV1, ExecutionPayloadV3,
ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus, TransitionConfiguration,
CAPABILITIES,
ForkchoiceUpdated, PayloadId, PayloadStatus, TransitionConfiguration, CAPABILITIES,
};
use reth_rpc_types_compat::engine::payload::{
convert_payload_input_v2_to_payload, convert_to_payload_body_v1,
@ -32,35 +32,37 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024;
/// The Engine API implementation that grants the Consensus layer access to data and
/// functions in the Execution layer that are crucial for the consensus process.
pub struct EngineApi<Provider> {
inner: Arc<EngineApiInner<Provider>>,
pub struct EngineApi<Provider, EngineT: EngineTypes> {
inner: Arc<EngineApiInner<Provider, EngineT>>,
}
struct EngineApiInner<Provider> {
struct EngineApiInner<Provider, EngineT: EngineTypes> {
/// The provider to interact with the chain.
provider: Provider,
/// Consensus configuration
chain_spec: Arc<ChainSpec>,
/// The channel to send messages to the beacon consensus engine.
beacon_consensus: BeaconConsensusEngineHandle,
beacon_consensus: BeaconConsensusEngineHandle<EngineT>,
/// The type that can communicate with the payload service to retrieve payloads.
payload_store: PayloadStore,
payload_store: PayloadStore<EngineT>,
/// For spawning and executing async tasks
task_spawner: Box<dyn TaskSpawner>,
/// The metrics for engine api calls
metrics: EngineApiMetrics,
}
impl<Provider> EngineApi<Provider>
impl<Provider, EngineT> EngineApi<Provider, EngineT>
where
Provider: HeaderProvider + BlockReader + StateProviderFactory + EvmEnvProvider + 'static,
EngineT: EngineTypes + 'static,
EngineT::PayloadBuilderAttributes: Send,
{
/// Create new instance of [EngineApi].
pub fn new(
provider: Provider,
chain_spec: Arc<ChainSpec>,
beacon_consensus: BeaconConsensusEngineHandle,
payload_store: PayloadStore,
beacon_consensus: BeaconConsensusEngineHandle<EngineT>,
payload_store: PayloadStore<EngineT>,
task_spawner: Box<dyn TaskSpawner>,
) -> Self {
let inner = Arc::new(EngineApiInner {
@ -78,7 +80,7 @@ where
async fn get_payload_attributes(
&self,
payload_id: PayloadId,
) -> EngineApiResult<PayloadBuilderAttributes> {
) -> EngineApiResult<EngineT::PayloadBuilderAttributes> {
Ok(self
.inner
.payload_store
@ -94,8 +96,15 @@ where
payload: ExecutionPayloadV1,
) -> EngineApiResult<PayloadStatus> {
let payload = ExecutionPayload::from(payload);
let payload_or_attrs = PayloadOrAttributes::from_execution_payload(&payload, None);
self.validate_version_specific_fields(EngineApiMessageVersion::V1, &payload_or_attrs)?;
let payload_or_attrs =
PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload(
&payload, None,
);
validate_version_specific_fields(
&self.inner.chain_spec,
EngineApiMessageVersion::V1,
&payload_or_attrs,
)?;
Ok(self.inner.beacon_consensus.new_payload(payload, None).await?)
}
@ -105,8 +114,15 @@ where
payload: ExecutionPayloadInputV2,
) -> EngineApiResult<PayloadStatus> {
let payload = convert_payload_input_v2_to_payload(payload);
let payload_or_attrs = PayloadOrAttributes::from_execution_payload(&payload, None);
self.validate_version_specific_fields(EngineApiMessageVersion::V2, &payload_or_attrs)?;
let payload_or_attrs =
PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload(
&payload, None,
);
validate_version_specific_fields(
&self.inner.chain_spec,
EngineApiMessageVersion::V2,
&payload_or_attrs,
)?;
Ok(self.inner.beacon_consensus.new_payload(payload, None).await?)
}
@ -119,8 +135,15 @@ where
) -> EngineApiResult<PayloadStatus> {
let payload = ExecutionPayload::from(payload);
let payload_or_attrs =
PayloadOrAttributes::from_execution_payload(&payload, Some(parent_beacon_block_root));
self.validate_version_specific_fields(EngineApiMessageVersion::V3, &payload_or_attrs)?;
PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload(
&payload,
Some(parent_beacon_block_root),
);
validate_version_specific_fields(
&self.inner.chain_spec,
EngineApiMessageVersion::V3,
&payload_or_attrs,
)?;
let cancun_fields = CancunPayloadFields { versioned_hashes, parent_beacon_block_root };
@ -136,7 +159,7 @@ where
pub async fn fork_choice_updated_v1(
&self,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<EngineT::PayloadAttributes>,
) -> EngineApiResult<ForkchoiceUpdated> {
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V1, state, payload_attrs)
.await
@ -149,7 +172,7 @@ where
pub async fn fork_choice_updated_v2(
&self,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<EngineT::PayloadAttributes>,
) -> EngineApiResult<ForkchoiceUpdated> {
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V2, state, payload_attrs)
.await
@ -162,7 +185,7 @@ where
pub async fn fork_choice_updated_v3(
&self,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<EngineT::PayloadAttributes>,
) -> EngineApiResult<ForkchoiceUpdated> {
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V3, state, payload_attrs)
.await
@ -205,7 +228,11 @@ where
let attributes = self.get_payload_attributes(payload_id).await?;
// validate timestamp according to engine rules
self.validate_payload_timestamp(EngineApiMessageVersion::V2, attributes.timestamp)?;
validate_payload_timestamp(
&self.inner.chain_spec,
EngineApiMessageVersion::V2,
attributes.timestamp(),
)?;
// Now resolve the payload
Ok(self
@ -232,7 +259,11 @@ where
let attributes = self.get_payload_attributes(payload_id).await?;
// validate timestamp according to engine rules
self.validate_payload_timestamp(EngineApiMessageVersion::V3, attributes.timestamp)?;
validate_payload_timestamp(
&self.inner.chain_spec,
EngineApiMessageVersion::V3,
attributes.timestamp(),
)?;
// Now resolve the payload
Ok(self
@ -385,159 +416,6 @@ where
}
}
/// Validates the timestamp depending on the version called:
///
/// * If V2, this ensure that the payload timestamp is pre-Cancun.
/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp.
///
/// Otherwise, this will return [EngineApiError::UnsupportedFork].
fn validate_payload_timestamp(
&self,
version: EngineApiMessageVersion,
timestamp: u64,
) -> EngineApiResult<()> {
let is_cancun = self.inner.chain_spec.is_cancun_active_at_timestamp(timestamp);
if version == EngineApiMessageVersion::V2 && is_cancun {
// From the Engine API spec:
//
// ### Update the methods of previous forks
//
// This document defines how Cancun payload should be handled by the [`Shanghai
// API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md).
//
// For the following methods:
//
// - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2)
// - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2)
// - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2)
//
// a validation **MUST** be added:
//
// 1. Client software **MUST** return `-38005: Unsupported fork` error if the
// `timestamp` of payload or payloadAttributes greater or equal to the Cancun
// activation timestamp.
return Err(EngineApiError::UnsupportedFork)
}
if version == EngineApiMessageVersion::V3 && !is_cancun {
// From the Engine API spec:
// <https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/cancun.md#specification-2>
//
// 1. Client software **MUST** return `-38005: Unsupported fork` error if the
// `timestamp` of the built payload does not fall within the time frame of the Cancun
// fork.
return Err(EngineApiError::UnsupportedFork)
}
Ok(())
}
/// Validates the presence of the `withdrawals` field according to the payload timestamp.
/// After Shanghai, withdrawals field must be [Some].
/// Before Shanghai, withdrawals field must be [None];
fn validate_withdrawals_presence(
&self,
version: EngineApiMessageVersion,
timestamp: u64,
has_withdrawals: bool,
) -> EngineApiResult<()> {
let is_shanghai =
self.inner.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(timestamp);
match version {
EngineApiMessageVersion::V1 => {
if has_withdrawals {
return Err(EngineApiError::WithdrawalsNotSupportedInV1)
}
if is_shanghai {
return Err(EngineApiError::NoWithdrawalsPostShanghai)
}
}
EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 => {
if is_shanghai && !has_withdrawals {
return Err(EngineApiError::NoWithdrawalsPostShanghai)
}
if !is_shanghai && has_withdrawals {
return Err(EngineApiError::HasWithdrawalsPreShanghai)
}
}
};
Ok(())
}
/// Validate the presence of the `parentBeaconBlockRoot` field according to the payload
/// timestamp.
///
/// After Cancun, `parentBeaconBlockRoot` field must be [Some].
/// Before Cancun, `parentBeaconBlockRoot` field must be [None].
///
/// If the engine API message version is V1 or V2, and the payload attribute's timestamp is
/// post-Cancun, then this will return [EngineApiError::UnsupportedFork].
///
/// If the payload attribute's timestamp is before the Cancun fork and the engine API message
/// version is V3, then this will return [EngineApiError::UnsupportedFork].
///
/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then
/// this will return [EngineApiError::NoParentBeaconBlockRootPostCancun].
///
/// This implements the following Engine API spec rules:
///
/// 1. Client software **MUST** check that provided set of parameters and their fields strictly
/// matches the expected one and return `-32602: Invalid params` error if this check fails.
/// Any field having `null` value **MUST** be considered as not provided.
///
/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the
/// `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the
/// time frame of the Cancun fork.
fn validate_parent_beacon_block_root_presence(
&self,
version: EngineApiMessageVersion,
timestamp: u64,
has_parent_beacon_block_root: bool,
) -> EngineApiResult<()> {
// 1. Client software **MUST** check that provided set of parameters and their fields
// strictly matches the expected one and return `-32602: Invalid params` error if this
// check fails. Any field having `null` value **MUST** be considered as not provided.
match version {
EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => {
if has_parent_beacon_block_root {
return Err(EngineApiError::ParentBeaconBlockRootNotSupportedBeforeV3)
}
}
EngineApiMessageVersion::V3 => {
if !has_parent_beacon_block_root {
return Err(EngineApiError::NoParentBeaconBlockRootPostCancun)
}
}
};
// 2. Client software **MUST** return `-38005: Unsupported fork` error if the
// `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within
// the time frame of the Cancun fork.
self.validate_payload_timestamp(version, timestamp)?;
Ok(())
}
/// Validates the presence or exclusion of fork-specific fields based on the payload attributes
/// and the message version.
fn validate_version_specific_fields(
&self,
version: EngineApiMessageVersion,
payload_or_attrs: &PayloadOrAttributes<'_>,
) -> EngineApiResult<()> {
self.validate_withdrawals_presence(
version,
payload_or_attrs.timestamp(),
payload_or_attrs.withdrawals().is_some(),
)?;
self.validate_parent_beacon_block_root_presence(
version,
payload_or_attrs.timestamp(),
payload_or_attrs.parent_beacon_block_root().is_some(),
)
}
/// Validates the `engine_forkchoiceUpdated` payload attributes and executes the forkchoice
/// update.
///
@ -555,17 +433,11 @@ where
&self,
version: EngineApiMessageVersion,
state: ForkchoiceState,
payload_attrs: Option<PayloadAttributes>,
payload_attrs: Option<EngineT::PayloadAttributes>,
) -> EngineApiResult<ForkchoiceUpdated> {
if let Some(ref attrs) = payload_attrs {
let attr_validation_res = self.validate_version_specific_fields(version, &attrs.into());
#[cfg(feature = "optimism")]
if attrs.optimism_payload_attributes.gas_limit.is_none() &&
self.inner.chain_spec.is_optimism()
{
return Err(EngineApiError::MissingGasLimitInPayloadAttributes)
}
let attr_validation_res =
attrs.ensure_well_formed_attributes(&self.inner.chain_spec, version);
// From the engine API spec:
//
@ -587,7 +459,7 @@ where
if fcu_res.is_invalid() {
return Ok(fcu_res)
}
return Err(err)
return Err(err.into())
}
}
@ -596,9 +468,12 @@ where
}
#[async_trait]
impl<Provider> EngineApiServer for EngineApi<Provider>
impl<Provider, EngineT> EngineApiServer<EngineT> for EngineApi<Provider, EngineT>
where
Provider: HeaderProvider + BlockReader + StateProviderFactory + EvmEnvProvider + 'static,
EngineT: EngineTypes + 'static + Send,
EngineT::PayloadAttributes: Send,
EngineT::PayloadBuilderAttributes: Send,
{
/// Handler for `engine_newPayloadV1`
/// See also <https://github.com/ethereum/execution-apis/blob/3d627c95a4d3510a8187dd02e0250ecb4331d27e/src/engine/paris.md#engine_newpayloadv1>
@ -645,7 +520,7 @@ where
async fn fork_choice_updated_v1(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<EngineT::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV1");
let start = Instant::now();
@ -660,7 +535,7 @@ where
async fn fork_choice_updated_v2(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<EngineT::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV2");
let start = Instant::now();
@ -676,7 +551,7 @@ where
async fn fork_choice_updated_v3(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
payload_attributes: Option<EngineT::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV3");
let start = Instant::now();
@ -800,7 +675,10 @@ where
}
}
impl<Provider> std::fmt::Debug for EngineApi<Provider> {
impl<Provider, EngineT> std::fmt::Debug for EngineApi<Provider, EngineT>
where
EngineT: EngineTypes,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EngineApi").finish_non_exhaustive()
}
@ -812,6 +690,7 @@ mod tests {
use assert_matches::assert_matches;
use reth_beacon_consensus::BeaconEngineMessage;
use reth_interfaces::test_utils::generators::random_block;
use reth_node_builder::EthEngineTypes;
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_primitives::{SealedBlock, B256, MAINNET};
use reth_provider::test_utils::MockEthProvider;
@ -820,7 +699,8 @@ mod tests {
use std::sync::Arc;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
fn setup_engine_api() -> (EngineApiTestHandle, EngineApi<Arc<MockEthProvider>>) {
fn setup_engine_api() -> (EngineApiTestHandle, EngineApi<Arc<MockEthProvider>, EthEngineTypes>)
{
let chain_spec: Arc<ChainSpec> = MAINNET.clone();
let provider = Arc::new(MockEthProvider::default());
let payload_store = spawn_test_payload_service();
@ -840,7 +720,7 @@ mod tests {
struct EngineApiTestHandle {
chain_spec: Arc<ChainSpec>,
provider: Arc<MockEthProvider>,
from_api: UnboundedReceiver<BeaconEngineMessage>,
from_api: UnboundedReceiver<BeaconEngineMessage<EthEngineTypes>>,
}
#[tokio::test]

View File

@ -2,6 +2,7 @@ use jsonrpsee_types::error::{
INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG,
};
use reth_beacon_consensus::{BeaconForkChoiceUpdateError, BeaconOnNewPayloadError};
use reth_node_api::AttributesValidationError;
use reth_payload_builder::error::PayloadBuilderError;
use reth_primitives::{B256, U256};
use thiserror::Error;
@ -43,27 +44,6 @@ pub enum EngineApiError {
/// Requested number of items
count: u64,
},
/// Thrown if `PayloadAttributes` provided in engine_forkchoiceUpdated before V3 contains a
/// parent beacon block root
#[error("parent beacon block root not supported before V3")]
ParentBeaconBlockRootNotSupportedBeforeV3,
/// Thrown if engine_forkchoiceUpdatedV1 contains withdrawals
#[error("withdrawals not supported in V1")]
WithdrawalsNotSupportedInV1,
/// Thrown if engine_forkchoiceUpdated contains no withdrawals after Shanghai
#[error("no withdrawals post-Shanghai")]
NoWithdrawalsPostShanghai,
/// Thrown if engine_forkchoiceUpdated contains withdrawals before Shanghai
#[error("withdrawals pre-Shanghai")]
HasWithdrawalsPreShanghai,
/// Thrown if the `PayloadAttributes` provided in engine_forkchoiceUpdated contains no parent
/// beacon block root after Cancun
#[error("no parent beacon block root post-cancun")]
NoParentBeaconBlockRootPostCancun,
/// Thrown if `PayloadAttributes` were provided with a timestamp, but the version of the engine
/// method called is meant for a fork that occurs after the provided timestamp.
#[error("Unsupported fork")]
UnsupportedFork,
/// Terminal total difficulty mismatch during transition configuration exchange.
#[error(
"invalid transition terminal total difficulty: \
@ -98,6 +78,9 @@ pub enum EngineApiError {
/// Fetching the payload failed
#[error(transparent)]
GetPayloadError(#[from] PayloadBuilderError),
/// The payload or attributes are known to be malformed before processing.
#[error(transparent)]
AttributesValidationError(#[from] AttributesValidationError),
/// If the optimism feature flag is enabled, the payload attributes must have a present
/// gas limit for the forkchoice updated method.
#[cfg(feature = "optimism")]
@ -123,11 +106,24 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
fn from(error: EngineApiError) -> Self {
match error {
EngineApiError::InvalidBodiesRange { .. } |
EngineApiError::WithdrawalsNotSupportedInV1 |
EngineApiError::ParentBeaconBlockRootNotSupportedBeforeV3 |
EngineApiError::NoParentBeaconBlockRootPostCancun |
EngineApiError::NoWithdrawalsPostShanghai |
EngineApiError::HasWithdrawalsPreShanghai => {
EngineApiError::AttributesValidationError(
AttributesValidationError::WithdrawalsNotSupportedInV1,
) |
EngineApiError::AttributesValidationError(
AttributesValidationError::ParentBeaconBlockRootNotSupportedBeforeV3,
) |
EngineApiError::AttributesValidationError(
AttributesValidationError::NoParentBeaconBlockRootPostCancun,
) |
EngineApiError::AttributesValidationError(
AttributesValidationError::NoWithdrawalsPostShanghai,
) |
EngineApiError::AttributesValidationError(
AttributesValidationError::HasWithdrawalsPreShanghai,
) |
EngineApiError::AttributesValidationError(
AttributesValidationError::InvalidParams(_),
) => {
// Note: the data field is not required by the spec, but is also included by other
// clients
jsonrpsee_types::error::ErrorObject::owned(
@ -148,7 +144,9 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
Some(ErrorData::new(error)),
)
}
EngineApiError::UnsupportedFork => jsonrpsee_types::error::ErrorObject::owned(
EngineApiError::AttributesValidationError(
AttributesValidationError::UnsupportedFork,
) => jsonrpsee_types::error::ErrorObject::owned(
UNSUPPORTED_FORK_CODE,
error.to_string(),
None::<()>,
@ -238,7 +236,7 @@ mod tests {
ensure_engine_rpc_error(
UNSUPPORTED_FORK_CODE,
"Unsupported fork",
EngineApiError::UnsupportedFork,
EngineApiError::AttributesValidationError(AttributesValidationError::UnsupportedFork),
);
ensure_engine_rpc_error(

View File

@ -14,9 +14,6 @@ mod engine_api;
/// The Engine API message type.
mod message;
/// An type representing either an execution payload or payload attributes.
mod payload;
/// Engine API error.
mod error;

View File

@ -81,16 +81,14 @@ struct BeaconPayloadAttributes {
withdrawals: Option<Vec<Withdrawal>>,
#[serde(skip_serializing_if = "Option::is_none")]
parent_beacon_block_root: Option<B256>,
#[cfg(feature = "optimism")]
#[serde(flatten)]
optimism_payload_attributes: BeaconOptimismPayloadAttributes,
}
/// Optimism Payload Attributes
#[cfg(feature = "optimism")]
#[serde_as]
#[derive(Serialize, Deserialize)]
struct BeaconOptimismPayloadAttributes {
#[serde(flatten)]
payload_attributes: BeaconPayloadAttributes,
#[serde(default, skip_serializing_if = "Option::is_none")]
transactions: Option<Vec<Bytes>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -100,6 +98,69 @@ struct BeaconOptimismPayloadAttributes {
gas_limit: Option<u64>,
}
/// A helper module for serializing and deserializing optimism payload attributes for the beacon
/// API.
///
/// See docs for [beacon_api_payload_attributes].
pub mod beacon_api_payload_attributes_optimism {
use super::*;
use crate::engine::{OptimismPayloadAttributes, PayloadAttributes};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Serialize the payload attributes for the beacon API.
pub fn serialize<S>(
payload_attributes: &OptimismPayloadAttributes,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let beacon_api_payload_attributes = BeaconPayloadAttributes {
timestamp: payload_attributes.payload_attributes.timestamp,
prev_randao: payload_attributes.payload_attributes.prev_randao,
suggested_fee_recipient: payload_attributes.payload_attributes.suggested_fee_recipient,
withdrawals: payload_attributes.payload_attributes.withdrawals.clone(),
parent_beacon_block_root: payload_attributes
.payload_attributes
.parent_beacon_block_root,
};
let op_beacon_api_payload_attributes = BeaconOptimismPayloadAttributes {
payload_attributes: beacon_api_payload_attributes,
transactions: payload_attributes.transactions.clone(),
no_tx_pool: payload_attributes.no_tx_pool,
gas_limit: payload_attributes.gas_limit,
};
op_beacon_api_payload_attributes.serialize(serializer)
}
/// Deserialize the payload attributes for the beacon API.
pub fn deserialize<'de, D>(deserializer: D) -> Result<OptimismPayloadAttributes, D::Error>
where
D: Deserializer<'de>,
{
let beacon_api_payload_attributes =
BeaconOptimismPayloadAttributes::deserialize(deserializer)?;
Ok(OptimismPayloadAttributes {
payload_attributes: PayloadAttributes {
timestamp: beacon_api_payload_attributes.payload_attributes.timestamp,
prev_randao: beacon_api_payload_attributes.payload_attributes.prev_randao,
suggested_fee_recipient: beacon_api_payload_attributes
.payload_attributes
.suggested_fee_recipient,
withdrawals: beacon_api_payload_attributes.payload_attributes.withdrawals,
parent_beacon_block_root: beacon_api_payload_attributes
.payload_attributes
.parent_beacon_block_root,
},
transactions: beacon_api_payload_attributes.transactions,
no_tx_pool: beacon_api_payload_attributes.no_tx_pool,
gas_limit: beacon_api_payload_attributes.gas_limit,
})
}
}
/// A helper module for serializing and deserializing the payload attributes for the beacon API.
///
/// The beacon API encoded object has equivalent fields to the
@ -125,12 +186,6 @@ pub mod beacon_api_payload_attributes {
suggested_fee_recipient: payload_attributes.suggested_fee_recipient,
withdrawals: payload_attributes.withdrawals.clone(),
parent_beacon_block_root: payload_attributes.parent_beacon_block_root,
#[cfg(feature = "optimism")]
optimism_payload_attributes: BeaconOptimismPayloadAttributes {
transactions: payload_attributes.optimism_payload_attributes.transactions.clone(),
no_tx_pool: payload_attributes.optimism_payload_attributes.no_tx_pool,
gas_limit: payload_attributes.optimism_payload_attributes.gas_limit,
},
};
beacon_api_payload_attributes.serialize(serializer)
}
@ -147,14 +202,6 @@ pub mod beacon_api_payload_attributes {
suggested_fee_recipient: beacon_api_payload_attributes.suggested_fee_recipient,
withdrawals: beacon_api_payload_attributes.withdrawals,
parent_beacon_block_root: beacon_api_payload_attributes.parent_beacon_block_root,
#[cfg(feature = "optimism")]
optimism_payload_attributes: crate::eth::engine::OptimismPayloadAttributes {
transactions: beacon_api_payload_attributes
.optimism_payload_attributes
.transactions,
no_tx_pool: beacon_api_payload_attributes.optimism_payload_attributes.no_tx_pool,
gas_limit: beacon_api_payload_attributes.optimism_payload_attributes.gas_limit,
},
})
}
}

View File

@ -623,17 +623,15 @@ pub struct PayloadAttributes {
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3>
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<B256>,
/// Optimism Payload Attributes
#[cfg(feature = "optimism")]
#[serde(flatten)]
pub optimism_payload_attributes: OptimismPayloadAttributes,
}
/// Optimism Payload Attributes
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg(feature = "optimism")]
pub struct OptimismPayloadAttributes {
/// The payload attributes
#[serde(flatten)]
pub payload_attributes: PayloadAttributes,
/// Transactions is a field for rollups: the transactions list is forced into the block
#[serde(skip_serializing_if = "Option::is_none")]
pub transactions: Option<Vec<Bytes>>,