mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 12:56:12 +00:00
Server sent events (#1920)
## Issue Addressed Resolves #1434 (this is the last major feature in the standard spec. There are only a couple of places we may be off-spec due to recent spec changes or ongoing discussion) Partly addresses #1669 ## Proposed Changes - remove the websocket server - remove the `TeeEventHandler` and `NullEventHandler` - add server sent events according to the eth2 API spec ## Additional Info This is according to the currently unmerged PR here: https://github.com/ethereum/eth2.0-APIs/pull/117 Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
@@ -13,14 +13,16 @@ pub mod lighthouse_vc;
|
||||
pub mod types;
|
||||
|
||||
use self::types::*;
|
||||
use eth2_libp2p::PeerId;
|
||||
use futures::Stream;
|
||||
use futures_util::StreamExt;
|
||||
pub use reqwest;
|
||||
use reqwest::{IntoUrl, Response};
|
||||
pub use reqwest::{StatusCode, Url};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use eth2_libp2p::PeerId;
|
||||
pub use reqwest;
|
||||
pub use reqwest::{StatusCode, Url};
|
||||
use std::iter::Iterator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -42,6 +44,8 @@ pub enum Error {
|
||||
MissingSignatureHeader,
|
||||
/// The server returned an invalid JSON response.
|
||||
InvalidJson(serde_json::Error),
|
||||
/// The server returned an invalid server-sent event.
|
||||
InvalidServerSentEvent(String),
|
||||
/// The server returned an invalid SSZ response.
|
||||
InvalidSsz(ssz::DecodeError),
|
||||
}
|
||||
@@ -59,6 +63,7 @@ impl Error {
|
||||
Error::InvalidSignatureHeader => None,
|
||||
Error::MissingSignatureHeader => None,
|
||||
Error::InvalidJson(_) => None,
|
||||
Error::InvalidServerSentEvent(_) => None,
|
||||
Error::InvalidSsz(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -826,7 +831,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_validator_duties_proposer(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
) -> Result<GenericResponse<Vec<ProposerData>>, Error> {
|
||||
) -> Result<DutiesResponse<Vec<ProposerData>>, Error> {
|
||||
let mut path = self.eth_path()?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -913,7 +918,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
indices: &[u64],
|
||||
) -> Result<GenericResponse<Vec<AttesterData>>, Error> {
|
||||
) -> Result<DutiesResponse<Vec<AttesterData>>, Error> {
|
||||
let mut path = self.eth_path()?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -966,6 +971,36 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `GET events?topics`
|
||||
pub async fn get_events<T: EthSpec>(
|
||||
&self,
|
||||
topic: &[EventTopic],
|
||||
) -> Result<impl Stream<Item = Result<EventKind<T>, Error>>, Error> {
|
||||
let mut path = self.eth_path()?;
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("events");
|
||||
|
||||
let topic_string = topic
|
||||
.iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
path.query_pairs_mut().append_pair("topics", &topic_string);
|
||||
|
||||
Ok(self
|
||||
.client
|
||||
.get(path)
|
||||
.send()
|
||||
.await
|
||||
.map_err(Error::Reqwest)?
|
||||
.bytes_stream()
|
||||
.map(|next| match next {
|
||||
Ok(bytes) => EventKind::from_sse_bytes(bytes.as_ref()),
|
||||
Err(e) => Err(Error::Reqwest(e)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! This module exposes a superset of the `types` crate. It adds additional types that are only
|
||||
//! required for the HTTP API.
|
||||
|
||||
use crate::Error as ServerError;
|
||||
use eth2_libp2p::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use std::str::{from_utf8, FromStr};
|
||||
pub use types::*;
|
||||
|
||||
/// An API error serializable to JSON.
|
||||
@@ -147,6 +147,13 @@ impl fmt::Display for StateId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
|
||||
pub struct DutiesResponse<T: Serialize + serde::de::DeserializeOwned> {
|
||||
pub dependent_root: Hash256,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
|
||||
pub struct GenericResponse<T: Serialize + serde::de::DeserializeOwned> {
|
||||
@@ -638,6 +645,129 @@ pub struct PeerCount {
|
||||
pub disconnecting: u64,
|
||||
}
|
||||
|
||||
// --------- Server Sent Event Types -----------
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SseBlock {
|
||||
pub slot: Slot,
|
||||
pub block: Hash256,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SseFinalizedCheckpoint {
|
||||
pub block: Hash256,
|
||||
pub state: Hash256,
|
||||
pub epoch: Epoch,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SseHead {
|
||||
pub slot: Slot,
|
||||
pub block: Hash256,
|
||||
pub state: Hash256,
|
||||
pub current_duty_dependent_root: Hash256,
|
||||
pub previous_duty_dependent_root: Hash256,
|
||||
pub epoch_transition: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Clone)]
|
||||
#[serde(bound = "T: EthSpec", untagged)]
|
||||
pub enum EventKind<T: EthSpec> {
|
||||
Attestation(Attestation<T>),
|
||||
Block(SseBlock),
|
||||
FinalizedCheckpoint(SseFinalizedCheckpoint),
|
||||
Head(SseHead),
|
||||
VoluntaryExit(SignedVoluntaryExit),
|
||||
}
|
||||
|
||||
impl<T: EthSpec> EventKind<T> {
|
||||
pub fn from_sse_bytes(message: &[u8]) -> Result<Self, ServerError> {
|
||||
let s = from_utf8(message)
|
||||
.map_err(|e| ServerError::InvalidServerSentEvent(format!("{:?}", e)))?;
|
||||
|
||||
let mut split = s.split('\n');
|
||||
let event = split
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
ServerError::InvalidServerSentEvent("Could not parse event tag".to_string())
|
||||
})?
|
||||
.trim_start_matches("event:");
|
||||
let data = split
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
ServerError::InvalidServerSentEvent("Could not parse data tag".to_string())
|
||||
})?
|
||||
.trim_start_matches("data:");
|
||||
|
||||
match event {
|
||||
"attestation" => Ok(EventKind::Attestation(serde_json::from_str(data).map_err(
|
||||
|e| ServerError::InvalidServerSentEvent(format!("Attestation: {:?}", e)),
|
||||
)?)),
|
||||
"block" => Ok(EventKind::Block(serde_json::from_str(data).map_err(
|
||||
|e| ServerError::InvalidServerSentEvent(format!("Block: {:?}", e)),
|
||||
)?)),
|
||||
"finalized_checkpoint" => Ok(EventKind::FinalizedCheckpoint(
|
||||
serde_json::from_str(data).map_err(|e| {
|
||||
ServerError::InvalidServerSentEvent(format!("Finalized Checkpoint: {:?}", e))
|
||||
})?,
|
||||
)),
|
||||
"head" => Ok(EventKind::Head(serde_json::from_str(data).map_err(
|
||||
|e| ServerError::InvalidServerSentEvent(format!("Head: {:?}", e)),
|
||||
)?)),
|
||||
"voluntary_exit" => Ok(EventKind::VoluntaryExit(
|
||||
serde_json::from_str(data).map_err(|e| {
|
||||
ServerError::InvalidServerSentEvent(format!("Voluntary Exit: {:?}", e))
|
||||
})?,
|
||||
)),
|
||||
_ => Err(ServerError::InvalidServerSentEvent(
|
||||
"Could not parse event tag".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct EventQuery {
|
||||
pub topics: QueryVec<EventTopic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EventTopic {
|
||||
Head,
|
||||
Block,
|
||||
Attestation,
|
||||
VoluntaryExit,
|
||||
FinalizedCheckpoint,
|
||||
}
|
||||
|
||||
impl FromStr for EventTopic {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"head" => Ok(EventTopic::Head),
|
||||
"block" => Ok(EventTopic::Block),
|
||||
"attestation" => Ok(EventTopic::Attestation),
|
||||
"voluntary_exit" => Ok(EventTopic::VoluntaryExit),
|
||||
"finalized_checkpoint" => Ok(EventTopic::FinalizedCheckpoint),
|
||||
_ => Err("event topic cannot be parsed.".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EventTopic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EventTopic::Head => write!(f, "head"),
|
||||
EventTopic::Block => write!(f, "block"),
|
||||
EventTopic::Attestation => write!(f, "attestation"),
|
||||
EventTopic::VoluntaryExit => write!(f, "voluntary_exit"),
|
||||
EventTopic::FinalizedCheckpoint => write!(f, "finalized_checkpoint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user