diff --git a/crates/blockchain/src/events.rs b/crates/blockchain/src/events.rs index 66386ffd..0f3367ec 100644 --- a/crates/blockchain/src/events.rs +++ b/crates/blockchain/src/events.rs @@ -22,16 +22,18 @@ use tracing::warn; /// /// These are the names consumers address events by (the SSE `event:` line and, /// later, `?topics=` filtering), kept separate from [`ChainEvent`] so the -/// payload stays flat with the topic travelling out-of-band. The names match -/// the Ethereum beacon-API eventstream topics (`head`, `block`, -/// `finalized_checkpoint`); `justified_checkpoint` is an ethlambda extension -/// with no beacon analog. +/// payload stays flat with the topic travelling out-of-band. Names match the +/// Ethereum beacon-API eventstream topics where an analog exists (`head`, +/// `block`, `finalized_checkpoint`, `block_gossip`); `justified_checkpoint` is +/// an ethlambda extension with no direct beacon topic. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Topic { Head, Block, JustifiedCheckpoint, FinalizedCheckpoint, + /// A block seen on gossip, before import (analog of beacon `block_gossip`). + BlockGossip, } impl Topic { @@ -41,6 +43,7 @@ impl Topic { Topic::Block => "block", Topic::JustifiedCheckpoint => "justified_checkpoint", Topic::FinalizedCheckpoint => "finalized_checkpoint", + Topic::BlockGossip => "block_gossip", } } } @@ -60,6 +63,7 @@ impl FromStr for Topic { "block" => Ok(Topic::Block), "justified_checkpoint" => Ok(Topic::JustifiedCheckpoint), "finalized_checkpoint" => Ok(Topic::FinalizedCheckpoint), + "block_gossip" => Ok(Topic::BlockGossip), other => Err(UnknownTopic(other.to_string())), } } @@ -90,6 +94,9 @@ pub enum ChainEvent { JustifiedCheckpoint { slot: u64, block: H256, state: H256 }, /// The finalized checkpoint advanced. FinalizedCheckpoint { slot: u64, block: H256, state: H256 }, + /// A block seen on gossip, before import. Analog of beacon `block_gossip`; + /// `block` is imported later once its parent chain is available. + BlockGossip { slot: u64, block: H256 }, } impl ChainEvent { @@ -99,6 +106,7 @@ impl ChainEvent { ChainEvent::Block { .. } => Topic::Block, ChainEvent::JustifiedCheckpoint { .. } => Topic::JustifiedCheckpoint, ChainEvent::FinalizedCheckpoint { .. } => Topic::FinalizedCheckpoint, + ChainEvent::BlockGossip { .. } => Topic::BlockGossip, } } } @@ -297,11 +305,12 @@ mod tests { } } - const ALL_TOPICS: [Topic; 4] = [ + const ALL_TOPICS: [Topic; 5] = [ Topic::Head, Topic::Block, Topic::JustifiedCheckpoint, Topic::FinalizedCheckpoint, + Topic::BlockGossip, ]; #[tokio::test] @@ -356,6 +365,10 @@ mod tests { }, Topic::FinalizedCheckpoint, ), + ( + ChainEvent::BlockGossip { slot: 1, block }, + Topic::BlockGossip, + ), ]; for (event, topic) in cases { assert_eq!(event.topic(), topic); diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 73d3a9cd..3a3ed503 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1322,6 +1322,10 @@ impl Handler for BlockChainServer { impl Handler for BlockChainServer { async fn handle(&mut self, msg: NewBlock, _ctx: &Context) { + self.events.emit(ChainEvent::BlockGossip { + slot: msg.block.message.slot, + block: msg.block.message.hash_tree_root(), + }); self.on_block(msg.block); } } diff --git a/docs/rpc.md b/docs/rpc.md index f560692d..e838af82 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -86,7 +86,7 @@ SSZ-encoded `SignedBlock` at the latest finalized checkpoint. The genesis/anchor ### `GET /lean/v0/events` -Server-Sent Events stream (`Content-Type: text/event-stream`) of live chain events published by the blockchain actor. Four event types: +Server-Sent Events stream (`Content-Type: text/event-stream`) of live chain events published by the blockchain actor. Five event types: Payload fields mirror the Ethereum beacon-API eventstream: `block` is the block root, `state` the state root, and `slot` stands in for the beacon `epoch`. @@ -96,6 +96,7 @@ Payload fields mirror the Ethereum beacon-API eventstream: `block` is the block | `block` | `{ "slot": 128, "block": "0x…" }` | A block is imported into the store | | `justified_checkpoint` | `{ "slot": 120, "block": "0x…", "state": "0x…" }` | The justified checkpoint advances | | `finalized_checkpoint` | `{ "slot": 96, "block": "0x…", "state": "0x…" }` | The finalized checkpoint advances | +| `block_gossip` | `{ "slot": 128, "block": "0x…" }` | A block is seen on the network, before import | The topic name travels only on the SSE `event:` line; the `data:` line carries the flat JSON payload. Example frame: @@ -112,7 +113,7 @@ A **required** comma-separated list of event names selects which events to strea curl -N 'http://127.0.0.1:5052/lean/v0/events?topics=head,finalized_checkpoint' ``` -Valid values are exactly the event names above: `head`, `block`, `justified_checkpoint`, `finalized_checkpoint`. As in the Beacon API `eventstream` endpoint, `topics` is mandatory: there is no "subscribe to everything" default; list the topics you want. +Valid values are exactly the event names above: `head`, `block`, `justified_checkpoint`, `finalized_checkpoint`, `block_gossip`. As in the Beacon API `eventstream` endpoint, `topics` is mandatory: there is no "subscribe to everything" default; list the topics you want. | Status | Condition | |--------|-----------|