Skip to main content

tempo_node/rpc/consensus/
types.rs

1//! RPC types for the consensus namespace.
2
3use std::fmt::Display;
4
5use alloy_primitives::B256;
6use futures::Future;
7use reth_primitives_traits::SealedOrRecoveredBlock;
8use serde::{Deserialize, Serialize};
9use tempo_alloy::rpc::TempoHeaderResponse;
10use tempo_primitives::Block;
11use tokio::sync::broadcast;
12
13/// A block with a threshold BLS certificate (notarization or finalization).
14#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
15#[serde(rename_all = "camelCase")]
16pub struct CertifiedBlock {
17    pub epoch: u64,
18    pub view: u64,
19    pub digest: B256,
20
21    /// Hex-encoded full notarization or finalization.
22    pub certificate: String,
23
24    /// The Tempo block.
25    pub block: SealedOrRecoveredBlock<Block>,
26}
27
28impl Display for CertifiedBlock {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match serde_json::to_string(self) {
31            Ok(s) => f.write_str(&s),
32            Err(err) => write!(f, "<failed formatting certified block: {err}"),
33        }
34    }
35}
36
37/// Consensus event emitted.
38#[derive(Clone, Debug, Serialize, Deserialize)]
39#[serde(tag = "type", rename_all = "camelCase")]
40pub enum Event {
41    /// A block was notarized.
42    Notarized {
43        #[serde(flatten)]
44        block: CertifiedBlock,
45        /// Unix timestamp in milliseconds when this event was observed.
46        seen: u64,
47    },
48    /// A block was finalized.
49    Finalized {
50        #[serde(flatten)]
51        block: CertifiedBlock,
52        /// Unix timestamp in milliseconds when this event was observed.
53        seen: u64,
54    },
55    /// A view was nullified.
56    Nullified {
57        epoch: u64,
58        view: u64,
59        /// Unix timestamp in milliseconds when this event was observed.
60        seen: u64,
61    },
62}
63
64/// Query for consensus data.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub enum Query {
68    /// Get the latest item.
69    Latest,
70    /// Get by block height.
71    Height(u64),
72}
73
74impl Display for Query {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match serde_json::to_string(self) {
77            Ok(s) => f.write_str(&s),
78            Err(err) => write!(f, "<failed formatting query: {err}>"),
79        }
80    }
81}
82
83/// Response for get_latest - current consensus state snapshot.
84#[derive(Clone, Debug, Default, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ConsensusState {
87    /// The latest finalized block (if any).
88    pub finalized: Option<CertifiedBlock>,
89    /// The latest notarized block (if any, and not yet finalized).
90    pub notarized: Option<CertifiedBlock>,
91}
92
93/// Error type for identity transition proof requests.
94#[derive(Clone, Debug, thiserror::Error)]
95pub enum IdentityProofError {
96    /// Node is not ready - consensus state not yet initialized.
97    #[error("node not ready")]
98    NotReady,
99    /// Block data has been pruned.
100    #[error("block data pruned at height {0}")]
101    PrunedData(u64),
102    /// Failed to decode DKG outcome from block.
103    #[error("malformed DKG outcome at height {0}")]
104    MalformedData(u64),
105}
106
107/// Response containing identity transition proofs.
108///
109/// Each transition represents a full DKG ceremony where the network's
110/// BLS public key changed. The proof demonstrates that the old network
111/// identity endorsed the new identity.
112#[derive(Clone, Debug, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct IdentityTransitionResponse {
115    /// Network identity of the requested epoch.
116    pub identity: String,
117    /// List of identity transitions, ordered newest to oldest.
118    /// Empty if no full DKG ceremonies have occurred.
119    pub transitions: Vec<IdentityTransition>,
120}
121
122/// A single identity transition (full DKG event).
123///
124/// This proves that the network transitioned from `old_identity` to
125/// `new_identity` at the given epoch, with a certificate signed by
126/// the old network identity.
127///
128/// For genesis (epoch 0), `proof` will be `None` since there is no
129/// finalization certificate for the genesis block.
130#[derive(Clone, Debug, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct IdentityTransition {
133    /// Epoch where the full DKG ceremony occurred.
134    pub transition_epoch: u64,
135    /// Hex-encoded BLS public key before the transition.
136    pub old_identity: String,
137    /// Hex-encoded BLS public key after the transition.
138    pub new_identity: String,
139    /// Proof of the transition. `None` for genesis identity (epoch 0).
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub proof: Option<TransitionProofData>,
142}
143
144/// Cryptographic proof data for an identity transition.
145#[derive(Clone, Debug, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct TransitionProofData {
148    /// The block header containing the new DKG outcome in extra_data.
149    pub header: TempoHeaderResponse,
150    /// Hex-encoded finalization certificate.
151    pub finalization_certificate: String,
152}
153
154#[derive(Debug)]
155pub enum Response<T> {
156    Success(T),
157    NotReady,
158    Missing(&'static str),
159}
160
161impl<T> Response<T>
162where
163    T: std::fmt::Debug,
164{
165    pub fn unwrap(self) -> T {
166        let Self::Success(val) = self else {
167            panic!("not a success: {self:?}")
168        };
169        val
170    }
171}
172
173impl<T> Display for Response<T>
174where
175    T: Display,
176{
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match self {
179            Self::Success(obj) => write!(f, "success: {obj}"),
180            Self::NotReady => write!(f, "service not ready"),
181            Self::Missing(msg) => write!(f, "missing: {msg}"),
182        }
183    }
184}
185
186/// Trait for accessing consensus feed data.
187pub trait ConsensusFeed: Send + Sync + 'static {
188    /// Get a finalization by query (supports `Latest` or `Height`).
189    fn get_finalization(
190        &self,
191        query: Query,
192    ) -> impl Future<Output = Response<CertifiedBlock>> + Send;
193
194    /// Get the current consensus state (latest finalized + latest notarized).
195    fn get_latest(&self) -> impl Future<Output = ConsensusState> + Send;
196
197    /// Subscribe to consensus events.
198    fn subscribe(&self) -> impl Future<Output = Option<broadcast::Receiver<Event>>> + Send;
199
200    /// Get identity transition proofs (full DKG events where network public key changed).
201    ///
202    /// - `from_epoch`: Optional epoch to start searching from (defaults to latest finalized)
203    /// - `full`: If true, return all transitions back to genesis; if false, return only the most recent
204    fn get_identity_transition_proof(
205        &self,
206        from_epoch: Option<u64>,
207        full: bool,
208    ) -> impl Future<Output = Result<IdentityTransitionResponse, IdentityProofError>> + Send;
209}