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