mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add sse payload attributes example and beacon api support (#5130)
This commit is contained in:
255
Cargo.lock
generated
255
Cargo.lock
generated
@ -496,6 +496,17 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.4"
|
||||
@ -521,6 +532,20 @@ dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-sse"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e6fa871e4334a622afd6bb2f611635e8083a6f5e2936c0f90f37c7ef9856298"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"futures-lite",
|
||||
"http-types",
|
||||
"log",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.74"
|
||||
@ -661,6 +686,19 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beacon-api-sse"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"eyre",
|
||||
"futures-util",
|
||||
"mev-share-sse",
|
||||
"reth",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.9.1"
|
||||
@ -1376,6 +1414,15 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.5.1"
|
||||
@ -1632,9 +1679,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.3"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124"
|
||||
checksum = "071c0f5945634bc9ba7a452f492377dd6b1993665ddb58f28704119b32f07a9a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
@ -2703,6 +2750,21 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
@ -2772,6 +2834,21 @@ version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
|
||||
dependencies = [
|
||||
"fastrand 1.9.0",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-locks"
|
||||
version = "0.7.1"
|
||||
@ -3167,6 +3244,26 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
|
||||
|
||||
[[package]]
|
||||
name = "http-types"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
"base64 0.13.1",
|
||||
"futures-lite",
|
||||
"infer",
|
||||
"pin-project-lite",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"serde_urlencoded",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
@ -3255,6 +3352,19 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iai"
|
||||
version = "0.1.1"
|
||||
@ -3535,6 +3645,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
|
||||
|
||||
[[package]]
|
||||
name = "inferno"
|
||||
version = "0.11.17"
|
||||
@ -4185,6 +4301,26 @@ dependencies = [
|
||||
"sketches-ddsketch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mev-share-sse"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e59928ecfd8f9dd3211f2eb08bdc260c78c2e2af34d1a652445a6287d3c95942"
|
||||
dependencies = [
|
||||
"async-sse",
|
||||
"bytes",
|
||||
"ethers-core",
|
||||
"futures-util",
|
||||
"http-types",
|
||||
"pin-project-lite",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@ -4282,6 +4418,24 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
@ -4533,12 +4687,50 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@ -4611,6 +4803,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@ -5392,10 +5590,12 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
@ -5404,10 +5604,13 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
@ -6313,6 +6516,7 @@ dependencies = [
|
||||
"reth-primitives",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"similar-asserts",
|
||||
"thiserror",
|
||||
]
|
||||
@ -7032,6 +7236,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
@ -7796,6 +8011,16 @@ dependencies = [
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
@ -8355,6 +8580,7 @@ dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 0.4.0",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8406,6 +8632,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "8.2.5"
|
||||
@ -8432,6 +8664,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
@ -8529,6 +8767,19 @@ version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.64"
|
||||
|
||||
@ -51,6 +51,7 @@ members = [
|
||||
"examples/cli-extension-event-hooks",
|
||||
"examples/rpc-db",
|
||||
"examples/manual-p2p",
|
||||
"examples/beacon-api-sse",
|
||||
"examples/trace-transaction-cli"
|
||||
]
|
||||
default-members = ["bin/reth"]
|
||||
|
||||
@ -21,6 +21,7 @@ alloy-rlp = { workspace = true, features = ["arrayvec"] }
|
||||
thiserror.workspace = true
|
||||
itertools.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_with = "3.3"
|
||||
serde_json.workspace = true
|
||||
jsonrpsee-types = { workspace = true, optional = true }
|
||||
alloy-primitives = { workspace = true, features = ["rand", "rlp"] }
|
||||
|
||||
71
crates/rpc/rpc-types/src/eth/engine/beacon_api/events.rs
Normal file
71
crates/rpc/rpc-types/src/eth/engine/beacon_api/events.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! Support for the Beacon API events
|
||||
//!
|
||||
//! See also [ethereum-beacon-API eventstream](https://ethereum.github.io/beacon-APIs/#/Events/eventstream)
|
||||
|
||||
use crate::engine::PayloadAttributes;
|
||||
use alloy_primitives::B256;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, DisplayFromStr};
|
||||
|
||||
/// Event for the `payload_attributes` topic of the beacon API node event stream.
|
||||
///
|
||||
/// This event gives block builders and relays sufficient information to construct or verify a block
|
||||
/// at `proposal_slot`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PayloadAttributesEvent {
|
||||
/// the identifier of the beacon hard fork at `proposal_slot`, e.g `"bellatrix"`, `"capella"`.
|
||||
pub version: String,
|
||||
/// Wrapped data of the event.
|
||||
pub data: PayloadAttributesData,
|
||||
}
|
||||
|
||||
impl PayloadAttributesEvent {
|
||||
/// Returns the payload attributes
|
||||
pub fn attributes(&self) -> &PayloadAttributes {
|
||||
&self.data.payload_attributes
|
||||
}
|
||||
}
|
||||
|
||||
/// Data of the event that contains the payload attributes
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PayloadAttributesData {
|
||||
/// The slot at which a block using these payload attributes may be built
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub proposal_slot: u64,
|
||||
/// the beacon block root of the parent block to be built upon.
|
||||
pub parent_block_root: B256,
|
||||
/// he execution block number of the parent block.
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub parent_block_number: u64,
|
||||
/// the execution block hash of the parent block.
|
||||
pub parent_block_hash: B256,
|
||||
/// The execution block number of the parent block.
|
||||
/// the validator index of the proposer at `proposal_slot` on the chain identified by
|
||||
/// `parent_block_root`.
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub proposer_index: u64,
|
||||
/// Beacon API encoding of `PayloadAttributesV<N>` as defined by the `execution-apis`
|
||||
/// specification
|
||||
///
|
||||
/// Note: this uses the beacon API format which uses snake-case and quoted decimals rather than
|
||||
/// big-endian hex.
|
||||
#[serde(with = "crate::eth::engine::payload::beacon_api_payload_attributes")]
|
||||
pub payload_attributes: PayloadAttributes,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde_payload_attributes_event() {
|
||||
let s = r#"{"version":"capella","data":{"proposal_slot":"173332","proposer_index":"649112","parent_block_root":"0x5a49069647f6bf8f25d76b55ce920947654ade4ba1c6ab826d16712dd62b42bf","parent_block_number":"161093","parent_block_hash":"0x608b3d140ecb5bbcd0019711ac3704ece7be8e6d100816a55db440c1bcbb0251","payload_attributes":{"timestamp":"1697982384","prev_randao":"0x3142abd98055871ebf78f0f8e758fd3a04df3b6e34d12d09114f37a737f8f01e","suggested_fee_recipient":"0x0000000000000000000000000000000000000001","withdrawals":[{"index":"2461612","validator_index":"853570","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"45016211"},{"index":"2461613","validator_index":"853571","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5269785"},{"index":"2461614","validator_index":"853572","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5275106"},{"index":"2461615","validator_index":"853573","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5235962"},{"index":"2461616","validator_index":"853574","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5252171"},{"index":"2461617","validator_index":"853575","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5221319"},{"index":"2461618","validator_index":"853576","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5260879"},{"index":"2461619","validator_index":"853577","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5285244"},{"index":"2461620","validator_index":"853578","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5266681"},{"index":"2461621","validator_index":"853579","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5271322"},{"index":"2461622","validator_index":"853580","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5231327"},{"index":"2461623","validator_index":"853581","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5276761"},{"index":"2461624","validator_index":"853582","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5246244"},{"index":"2461625","validator_index":"853583","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5261011"},{"index":"2461626","validator_index":"853584","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5276477"},{"index":"2461627","validator_index":"853585","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5275319"}]}}}"#;
|
||||
|
||||
let event = serde_json::from_str::<PayloadAttributesEvent>(s).unwrap();
|
||||
let input = serde_json::from_str::<serde_json::Value>(s).unwrap();
|
||||
let json = serde_json::to_value(event).unwrap();
|
||||
assert_eq!(input, json);
|
||||
}
|
||||
}
|
||||
2
crates/rpc/rpc-types/src/eth/engine/beacon_api/mod.rs
Normal file
2
crates/rpc/rpc-types/src/eth/engine/beacon_api/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
/// Beacon API events support.
|
||||
pub mod events;
|
||||
@ -8,6 +8,9 @@ pub mod payload;
|
||||
mod transition;
|
||||
pub use self::{cancun::*, forkchoice::*, payload::*, transition::*};
|
||||
|
||||
/// Beacon API types
|
||||
pub mod beacon_api;
|
||||
|
||||
/// The list of all supported Engine capabilities available over the engine endpoint.
|
||||
pub const CAPABILITIES: [&str; 12] = [
|
||||
"engine_forkchoiceUpdatedV1",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::eth::withdrawal::BeaconAPIWithdrawal;
|
||||
pub use crate::Withdrawal;
|
||||
use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256, U64};
|
||||
use reth_primitives::{
|
||||
@ -5,6 +6,7 @@ use reth_primitives::{
|
||||
BlobTransactionSidecar, SealedBlock,
|
||||
};
|
||||
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
|
||||
use serde_with::{serde_as, DisplayFromStr};
|
||||
|
||||
/// The execution payload body response that allows for `null` values.
|
||||
pub type ExecutionPayloadBodiesV1 = Vec<Option<ExecutionPayloadBodyV1>>;
|
||||
@ -411,6 +413,63 @@ pub struct PayloadAttributes {
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct BeaconAPIPayloadAttributes {
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
timestamp: u64,
|
||||
prev_randao: B256,
|
||||
suggested_fee_recipient: Address,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde_as(as = "Option<Vec<BeaconAPIWithdrawal>>")]
|
||||
withdrawals: Option<Vec<Withdrawal>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parent_beacon_block_root: Option<B256>,
|
||||
}
|
||||
/// A helper module for serializing and deserializing the payload attributes for the beacon API.
|
||||
///
|
||||
/// The beacon API encoded object has equivalent fields to the [PayloadAttributes] with two
|
||||
/// differences:
|
||||
/// 1) `snake_case` identifiers must be used rather than `camelCase`;
|
||||
/// 2) integers must be encoded as quoted decimals rather than big-endian hex.
|
||||
pub mod beacon_api_payload_attributes {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Serialize the payload attributes for the beacon API.
|
||||
pub fn serialize<S>(
|
||||
payload_attributes: &PayloadAttributes,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let beacon_api_payload_attributes = BeaconAPIPayloadAttributes {
|
||||
timestamp: payload_attributes.timestamp.to(),
|
||||
prev_randao: payload_attributes.prev_randao,
|
||||
suggested_fee_recipient: payload_attributes.suggested_fee_recipient,
|
||||
withdrawals: payload_attributes.withdrawals.clone(),
|
||||
parent_beacon_block_root: payload_attributes.parent_beacon_block_root,
|
||||
};
|
||||
beacon_api_payload_attributes.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserialize the payload attributes for the beacon API.
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<PayloadAttributes, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let beacon_api_payload_attributes = BeaconAPIPayloadAttributes::deserialize(deserializer)?;
|
||||
Ok(PayloadAttributes {
|
||||
timestamp: U64::from(beacon_api_payload_attributes.timestamp),
|
||||
prev_randao: beacon_api_payload_attributes.prev_randao,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure contains the result of processing a payload or fork choice update.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -862,4 +921,21 @@ mod tests {
|
||||
serde_json::from_str(input);
|
||||
assert!(payload_res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_api_payload_serde() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct Event {
|
||||
#[serde(with = "beacon_api_payload_attributes")]
|
||||
payload: PayloadAttributes,
|
||||
}
|
||||
|
||||
let s = r#"{"timestamp":"1697981664","prev_randao":"0x739947d9f0aed15e32ed05a978e53b55cdcfe3db4a26165890fa45a80a06c996","suggested_fee_recipient":"0x0000000000000000000000000000000000000001","withdrawals":[{"index":"2460700","validator_index":"852657","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5268915"},{"index":"2460701","validator_index":"852658","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5253066"},{"index":"2460702","validator_index":"852659","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5266666"},{"index":"2460703","validator_index":"852660","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5239026"},{"index":"2460704","validator_index":"852661","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5273516"},{"index":"2460705","validator_index":"852662","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5260842"},{"index":"2460706","validator_index":"852663","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5238925"},{"index":"2460707","validator_index":"852664","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5253956"},{"index":"2460708","validator_index":"852665","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5284374"},{"index":"2460709","validator_index":"852666","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5276798"},{"index":"2460710","validator_index":"852667","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5239682"},{"index":"2460711","validator_index":"852668","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5261544"},{"index":"2460712","validator_index":"852669","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5247034"},{"index":"2460713","validator_index":"852670","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5256750"},{"index":"2460714","validator_index":"852671","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5261929"},{"index":"2460715","validator_index":"852672","address":"0x778f5f13c4be78a3a4d7141bcb26999702f407cf","amount":"5243188"}]}"#;
|
||||
|
||||
let event: Event = serde_json::from_str(s).unwrap();
|
||||
let input = serde_json::from_str::<serde_json::Value>(s).unwrap();
|
||||
let json = serde_json::to_value(event).unwrap();
|
||||
assert_eq!(input, json);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ mod syncing;
|
||||
pub mod trace;
|
||||
mod transaction;
|
||||
pub mod txpool;
|
||||
mod withdrawal;
|
||||
pub mod withdrawal;
|
||||
mod work;
|
||||
|
||||
pub use account::*;
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
//! Withdrawal type and serde helpers.
|
||||
|
||||
use alloy_primitives::{Address, U256};
|
||||
use alloy_rlp::RlpEncodable;
|
||||
use reth_primitives::{constants::GWEI_TO_WEI, serde_helper::u64_hex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs};
|
||||
|
||||
/// Withdrawal represents a validator withdrawal from the consensus layer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, Serialize, Deserialize)]
|
||||
pub struct Withdrawal {
|
||||
@ -25,6 +29,73 @@ impl Withdrawal {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted
|
||||
/// decimals.
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct BeaconAPIWithdrawal {
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
index: u64,
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
validator_index: u64,
|
||||
address: Address,
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
impl SerializeAs<Withdrawal> for BeaconAPIWithdrawal {
|
||||
fn serialize_as<S>(source: &Withdrawal, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
beacon_api_withdrawals::serialize(source, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, Withdrawal> for BeaconAPIWithdrawal {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Withdrawal, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
beacon_api_withdrawals::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than
|
||||
/// big-endian hex.
|
||||
pub mod beacon_api_withdrawals {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Serialize the payload attributes for the beacon API.
|
||||
pub fn serialize<S>(payload_attributes: &Withdrawal, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let withdrawal = BeaconAPIWithdrawal {
|
||||
index: payload_attributes.index,
|
||||
validator_index: payload_attributes.validator_index,
|
||||
address: payload_attributes.address,
|
||||
amount: payload_attributes.amount,
|
||||
};
|
||||
withdrawal.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserialize the payload attributes for the beacon API.
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Withdrawal, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let withdrawal = BeaconAPIWithdrawal::deserialize(deserializer)?;
|
||||
Ok(Withdrawal {
|
||||
index: withdrawal.index,
|
||||
validator_index: withdrawal.validator_index,
|
||||
address: withdrawal.address,
|
||||
amount: withdrawal.amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
16
examples/beacon-api-sse/Cargo.toml
Normal file
16
examples/beacon-api-sse/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "beacon-api-sse"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
eyre.workspace = true
|
||||
clap.workspace = true
|
||||
tracing.workspace = true
|
||||
futures-util.workspace = true
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
|
||||
mev-share-sse = "0.1.5"
|
||||
113
examples/beacon-api-sse/src/main.rs
Normal file
113
examples/beacon-api-sse/src/main.rs
Normal file
@ -0,0 +1,113 @@
|
||||
//! Example of how to subscribe to beacon chain events via SSE.
|
||||
//!
|
||||
//! See also [ethereum-beacon-API eventstream](https://ethereum.github.io/beacon-APIs/#/Events/eventstream)
|
||||
//!
|
||||
//! Run with
|
||||
//!
|
||||
//! ```not_rust
|
||||
//! cargo run -p beacon-api-sse -- node
|
||||
//! ```
|
||||
//!
|
||||
//! This launches a regular reth instance and subscribes to payload attributes event stream.
|
||||
//!
|
||||
//! **NOTE**: This expects that the CL client is running an http server on `localhost:5052` and is
|
||||
//! configured to emit payload attributes events.
|
||||
//!
|
||||
//! See lighthouse beacon Node API: <https://lighthouse-book.sigmaprime.io/api-bn.html#beacon-node-api>
|
||||
use clap::Parser;
|
||||
use futures_util::stream::StreamExt;
|
||||
use mev_share_sse::{client::EventStream, EventClient};
|
||||
use reth::{
|
||||
cli::{
|
||||
components::RethNodeComponents,
|
||||
ext::{RethCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
rpc::types::engine::beacon_api::events::PayloadAttributesEvent,
|
||||
tasks::TaskSpawner,
|
||||
};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use tracing::{info, warn};
|
||||
|
||||
fn main() {
|
||||
Cli::<BeaconEventsExt>::parse().run().unwrap();
|
||||
}
|
||||
|
||||
/// The type that tells the reth CLI what extensions to use
|
||||
#[derive(Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
struct BeaconEventsExt;
|
||||
|
||||
impl RethCliExt for BeaconEventsExt {
|
||||
/// This tells the reth CLI to install additional CLI arguments
|
||||
type Node = BeaconEventsConfig;
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
struct BeaconEventsConfig {
|
||||
/// Beacon Node http server address
|
||||
#[arg(long = "cl.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
|
||||
pub cl_addr: IpAddr,
|
||||
/// Beacon Node http server port to listen on
|
||||
#[arg(long = "cl.port", default_value_t = 5052)]
|
||||
pub cl_port: u16,
|
||||
}
|
||||
|
||||
impl BeaconEventsConfig {
|
||||
/// Returns the http url of the beacon node
|
||||
pub fn http_base_url(&self) -> String {
|
||||
format!("http://{}:{}", self.cl_addr, self.cl_port)
|
||||
}
|
||||
|
||||
/// Returns the URL to the events endpoint
|
||||
pub fn events_url(&self) -> String {
|
||||
format!("{}/eth/v1/events", self.http_base_url())
|
||||
}
|
||||
|
||||
/// Service that subscribes to beacon chain payload attributes events
|
||||
async fn run(self) {
|
||||
let client = EventClient::default();
|
||||
|
||||
let mut subscription = self.new_payload_attributes_subscription(&client).await;
|
||||
|
||||
while let Some(event) = subscription.next().await {
|
||||
info!("Received payload attributes: {:?}", event);
|
||||
}
|
||||
}
|
||||
|
||||
// It can take a bit until the CL endpoint is live so we retry a few times
|
||||
async fn new_payload_attributes_subscription(
|
||||
&self,
|
||||
client: &EventClient,
|
||||
) -> EventStream<PayloadAttributesEvent> {
|
||||
let payloads_url = format!("{}?topics=payload_attributes", self.events_url());
|
||||
loop {
|
||||
match client.subscribe(&payloads_url).await {
|
||||
Ok(subscription) => return subscription,
|
||||
Err(err) => {
|
||||
warn!("Failed to subscribe to payload attributes events: {:?}\nRetrying in 5 seconds...", err);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RethNodeCommandConfig for BeaconEventsConfig {
|
||||
fn on_node_started<Reth: RethNodeComponents>(&mut self, components: &Reth) -> eyre::Result<()> {
|
||||
components.task_executor().spawn(Box::pin(self.clone().run()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_config() {
|
||||
let args = BeaconEventsConfig::try_parse_from(["reth"]);
|
||||
assert!(args.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user