Skip to main content

espresso_api/
render.rs

1//! Consensus and query service types rendered as v2 proto messages.
2
3use std::{borrow::Borrow, collections::HashMap};
4
5use ark_serialize::CanonicalSerialize;
6use espresso_types::{
7    AccountQueryData, BuilderSignature, FeeAccount, FeeAccountProof, FeeInfo, FeeMerkleProof,
8    Header, L1BlockInfo, NamespaceProofQueryData, NsProof, Payload, PubKey, SeqTypes, Transaction,
9    TxProof,
10    config::PublicNetworkConfig,
11    v0_3::{
12        self, AvidMIncorrectEncodingNsProof, AvidMNsProof, RegisteredValidator,
13        ResolvableChainConfig, StakeTableEvent, StateCertQueryDataV1,
14    },
15    v0_4::{
16        RewardAccountProofV2, RewardAccountQueryDataV2, RewardMerkleProofV2, StateCertQueryDataV2,
17    },
18    v0_6::AvidmGf2NsProof,
19};
20use hotshot_contract_adapter::sol_types::{
21    EdOnBN254PointSol, G2PointSol, stake_table_v3::BN254::G1Point as G1PointSol,
22};
23use hotshot_query_service_types::{
24    availability::{
25        BlockQueryData, BlockSummaryQueryData, LeafQueryData, PayloadQueryData,
26        TransactionQueryData, TransactionWithProofQueryData, VidCommonQueryData,
27    },
28    explorer,
29    node::{
30        Limits as NodeLimits, ResourceSyncStatus, SyncStatus, SyncStatusQueryData,
31        TimeWindowQueryData,
32    },
33};
34use hotshot_types::{
35    HotShotConfig, PeerConfig,
36    data::{Leaf2, VidCommon, VidShare, ViewChangeEvidence2},
37    light_client::LCV3StateSignatureRequestBody,
38    network::BuilderType,
39    simple_certificate::{
40        Certificate2, CertificatePair, SimpleCertificate, SuccessThreshold, Threshold,
41        TimeoutCertificate2, TimeoutCertificate3, UpgradeCertificate, ViewSyncFinalizeCertificate2,
42    },
43    simple_vote::{LockView, QuorumData2, Voteable},
44    traits::{EncodeBytes as _, signature_key::SignatureKey},
45    vid::advz::{LargeRangeProofType, SmallRangeProofType},
46};
47use jf_merkle_tree_compat::{
48    Element, Index, NodeValue,
49    prelude::{MerkleNode, MerkleProof},
50};
51use light_client::consensus::{
52    leaf::{FinalityProof, LeafProof},
53    namespace::NamespaceProof,
54    payload::PayloadProof,
55};
56use tagged_base64::TaggedBase64;
57
58use crate::{
59    proto::{
60        self, advz_merkle_node::Node, header_response::Header as Shape,
61        resolvable_chain_config::ChainConfig,
62    },
63    v1::node::StakeTableWithEpochNumber,
64};
65
66impl From<ResolvableChainConfig> for proto::ResolvableChainConfig {
67    fn from(chain_config: ResolvableChainConfig) -> Self {
68        // `commit` would hash a full config too; only `resolve` tells the two apart.
69        let resolved = match chain_config.resolve() {
70            Some(config) => ChainConfig::Full((&config).into()),
71            None => ChainConfig::Commitment(chain_config.commit().to_string()),
72        };
73        proto::ResolvableChainConfig {
74            chain_config: Some(resolved),
75        }
76    }
77}
78
79impl From<&v0_3::ChainConfig> for proto::ChainConfig {
80    fn from(config: &v0_3::ChainConfig) -> Self {
81        proto::ChainConfig {
82            chain_id: config.chain_id.to_string(),
83            max_block_size: *config.max_block_size,
84            base_fee: config.base_fee.to_string(),
85            // v1 renders addresses as lowercase hex through `ethers_core::H160` and
86            // `FixedBytes`; alloy's `Display` prints them EIP-55 checksummed instead.
87            fee_contract: config.fee_contract.map(|address| format!("{address:#x}")),
88            fee_recipient: format!("{:#x}", config.fee_recipient.0),
89            stake_table_contract: config
90                .stake_table_contract
91                .map(|address| format!("{address:#x}")),
92        }
93    }
94}
95
96impl From<L1BlockInfo> for proto::L1BlockInfo {
97    fn from(info: L1BlockInfo) -> Self {
98        proto::L1BlockInfo {
99            number: info.number,
100            timestamp: format!("{:#x}", info.timestamp),
101            hash: format!("{:#x}", info.hash),
102        }
103    }
104}
105
106impl From<&FeeInfo> for proto::FeeInfo {
107    fn from(fee: &FeeInfo) -> Self {
108        proto::FeeInfo {
109            account: format!("{:#x}", fee.account.0),
110            amount: fee.amount.to_string(),
111        }
112    }
113}
114
115impl From<&BuilderSignature> for proto::BuilderSignature {
116    fn from(signature: &BuilderSignature) -> Self {
117        proto::BuilderSignature {
118            r: format!("{:#x}", signature.r()),
119            s: format!("{:#x}", signature.s()),
120            // alloy's parity bool; v1 accepts only 27 or 28.
121            v: if signature.v() { 28 } else { 27 },
122        }
123    }
124}
125
126/// The proto message per protocol version, mirroring the `Header` enum. Versions sharing a shape
127/// share a message, so only the arm distinguishes 0.1 from 0.2 and 0.5 from 0.6 and 0.7.
128impl From<&Header> for proto::HeaderResponse {
129    fn from(header: &Header) -> Self {
130        let shape = match header {
131            Header::V1(_) => Shape::V1(header.into()),
132            Header::V2(_) => Shape::V2(header.into()),
133            Header::V3(_) => Shape::V3(header.into()),
134            Header::V4(_) => Shape::V4(header_v4(header)),
135            Header::V5(_) => Shape::V5(header_v5(header)),
136            Header::V6(_) => Shape::V6(header_v5(header)),
137            Header::V7(_) => Shape::V7(header_v5(header)),
138        };
139        proto::HeaderResponse {
140            header: Some(shape),
141        }
142    }
143}
144
145/// Empty from 0.7, when the header stopped carrying a builder commitment but kept the 0.5 shape.
146fn builder_commitment(header: &Header) -> String {
147    header
148        .builder_commitment()
149        .map(ToString::to_string)
150        .unwrap_or_default()
151}
152
153impl From<&Header> for proto::HeaderV1 {
154    fn from(header: &Header) -> Self {
155        proto::HeaderV1 {
156            chain_config: Some(header.chain_config().into()),
157            height: header.height(),
158            timestamp: header.timestamp_internal(),
159            l1_head: header.l1_head(),
160            l1_finalized: header.l1_finalized().map(Into::into),
161            payload_commitment: header.payload_commitment().to_string(),
162            builder_commitment: builder_commitment(header),
163            ns_table: Some(proto::NsTable {
164                bytes: header.ns_table().encode().to_vec(),
165            }),
166            block_merkle_tree_root: header.block_merkle_tree_root().to_string(),
167            fee_merkle_tree_root: header.fee_merkle_tree_root().to_string(),
168            fee_info: header.fee_info().first().map(Into::into),
169            // The accessor smooths `Option` into a `Vec` across versions; empty means unsigned.
170            builder_signature: header.builder_signature().first().map(Into::into),
171        }
172    }
173}
174
175impl From<&Header> for proto::HeaderV3 {
176    fn from(header: &Header) -> Self {
177        proto::HeaderV3 {
178            chain_config: Some(header.chain_config().into()),
179            height: header.height(),
180            timestamp: header.timestamp_internal(),
181            l1_head: header.l1_head(),
182            l1_finalized: header.l1_finalized().map(Into::into),
183            payload_commitment: header.payload_commitment().to_string(),
184            builder_commitment: builder_commitment(header),
185            ns_table: Some(proto::NsTable {
186                bytes: header.ns_table().encode().to_vec(),
187            }),
188            block_merkle_tree_root: header.block_merkle_tree_root().to_string(),
189            fee_merkle_tree_root: header.fee_merkle_tree_root().to_string(),
190            fee_info: header.fee_info().first().map(Into::into),
191            builder_signature: header.builder_signature().first().map(Into::into),
192            reward_merkle_tree_root: reward_merkle_tree_root(header),
193        }
194    }
195}
196
197/// Private, unlike the 0.1 and 0.3 shapes above, because the two `expect`s are sound only under
198/// the variant match in [`proto::HeaderResponse`]'s impl. A `From<&Header>` would let a 0.1 header
199/// reach them.
200fn header_v4(header: &Header) -> proto::HeaderV4 {
201    proto::HeaderV4 {
202        chain_config: Some(header.chain_config().into()),
203        height: header.height(),
204        timestamp: header.timestamp_internal(),
205        timestamp_millis: header.timestamp_millis_internal(),
206        l1_head: header.l1_head(),
207        l1_finalized: header.l1_finalized().map(Into::into),
208        payload_commitment: header.payload_commitment().to_string(),
209        builder_commitment: builder_commitment(header),
210        ns_table: Some(proto::NsTable {
211            bytes: header.ns_table().encode().to_vec(),
212        }),
213        block_merkle_tree_root: header.block_merkle_tree_root().to_string(),
214        fee_merkle_tree_root: header.fee_merkle_tree_root().to_string(),
215        fee_info: header.fee_info().first().map(Into::into),
216        builder_signature: header.builder_signature().first().map(Into::into),
217        reward_merkle_tree_root: reward_merkle_tree_root(header),
218        total_reward_distributed: header
219            .total_reward_distributed()
220            .expect("0.4 and later headers carry total_reward_distributed")
221            .to_string(),
222        next_stake_table_hash: header.next_stake_table_hash().map(|hash| hash.to_string()),
223    }
224}
225
226/// See [`header_v4`] for why this is not a `From` impl.
227fn header_v5(header: &Header) -> proto::HeaderV5 {
228    proto::HeaderV5 {
229        chain_config: Some(header.chain_config().into()),
230        height: header.height(),
231        timestamp: header.timestamp_internal(),
232        timestamp_millis: header.timestamp_millis_internal(),
233        l1_head: header.l1_head(),
234        l1_finalized: header.l1_finalized().map(Into::into),
235        payload_commitment: header.payload_commitment().to_string(),
236        builder_commitment: builder_commitment(header),
237        ns_table: Some(proto::NsTable {
238            bytes: header.ns_table().encode().to_vec(),
239        }),
240        block_merkle_tree_root: header.block_merkle_tree_root().to_string(),
241        fee_merkle_tree_root: header.fee_merkle_tree_root().to_string(),
242        fee_info: header.fee_info().first().map(Into::into),
243        builder_signature: header.builder_signature().first().map(Into::into),
244        reward_merkle_tree_root: reward_merkle_tree_root(header),
245        total_reward_distributed: header
246            .total_reward_distributed()
247            .expect("0.4 and later headers carry total_reward_distributed")
248            .to_string(),
249        next_stake_table_hash: header.next_stake_table_hash().map(|hash| hash.to_string()),
250        leader_counts: header
251            .leader_counts()
252            .expect("0.5 and later headers carry leader_counts")
253            .iter()
254            .map(|count| *count as u32)
255            .collect(),
256    }
257}
258
259/// 0.3 uses the first reward tree (Left), later versions the second (Right). 0.1 and 0.2 have
260/// none, and the accessor would fabricate an empty tree's commitment for them.
261fn reward_merkle_tree_root(header: &Header) -> String {
262    match header.reward_merkle_tree_root() {
263        either::Either::Left(root) => root.to_string(),
264        either::Either::Right(root) => root.to_string(),
265    }
266}
267
268impl From<ResourceSyncStatus> for proto::ResourceSyncStatus {
269    fn from(status: ResourceSyncStatus) -> Self {
270        proto::ResourceSyncStatus {
271            missing: status.missing as u64,
272            ranges: status
273                .ranges
274                .into_iter()
275                .map(|range| proto::SyncStatusRange {
276                    start: range.start as u64,
277                    end: range.end as u64,
278                    status: match range.status {
279                        SyncStatus::Present => proto::SyncStatus::Present,
280                        SyncStatus::Missing => proto::SyncStatus::Missing,
281                        SyncStatus::Pruned => proto::SyncStatus::Pruned,
282                    }
283                    .into(),
284                })
285                .collect(),
286        }
287    }
288}
289
290impl From<SyncStatusQueryData> for proto::SyncStatusResponse {
291    fn from(status: SyncStatusQueryData) -> Self {
292        proto::SyncStatusResponse {
293            blocks: Some(status.blocks.into()),
294            leaves: Some(status.leaves.into()),
295            vid_common: Some(status.vid_common.into()),
296            pruned_height: status.pruned_height.map(|height| height as u64),
297        }
298    }
299}
300
301impl From<&TimeWindowQueryData<Header>> for proto::HeaderWindowResponse {
302    fn from(window: &TimeWindowQueryData<Header>) -> Self {
303        proto::HeaderWindowResponse {
304            window: window.window.iter().map(Into::into).collect(),
305            prev: window.prev.as_ref().map(Into::into),
306            next: window.next.as_ref().map(Into::into),
307        }
308    }
309}
310
311impl From<NodeLimits> for proto::NodeLimitsResponse {
312    fn from(limits: NodeLimits) -> Self {
313        proto::NodeLimitsResponse {
314            window_limit: limits.window_limit as u64,
315        }
316    }
317}
318
319impl From<PeerConfig<SeqTypes>> for proto::PeerConfig {
320    fn from(peer: PeerConfig<SeqTypes>) -> Self {
321        proto::PeerConfig {
322            stake_table_entry: Some(proto::StakeTableEntry {
323                stake_key: Some(proto::BlsPublicKey {
324                    key: peer.stake_table_entry.stake_key.to_string(),
325                }),
326                stake_amount: format!("{:#x}", peer.stake_table_entry.stake_amount),
327            }),
328            state_ver_key: Some(proto::SchnorrPublicKey {
329                key: peer.state_ver_key.to_string(),
330            }),
331            connect_info: peer.connect_info.map(|info| proto::PeerConnectInfo {
332                p2p_addr: info.p2p_addr.unbracketed_string(),
333                x25519_key: info.x25519_key.to_string(),
334            }),
335        }
336    }
337}
338
339impl From<StakeTableWithEpochNumber<SeqTypes>> for proto::StakeTableResponse {
340    fn from(table: StakeTableWithEpochNumber<SeqTypes>) -> Self {
341        Self {
342            epoch: table.epoch.map(|epoch| *epoch),
343            stake_table: table.stake_table.into_iter().map(Into::into).collect(),
344        }
345    }
346}
347
348impl From<RegisteredValidator<PubKey>> for proto::Validator {
349    fn from(registered: RegisteredValidator<PubKey>) -> Self {
350        let mut delegators: Vec<_> = registered
351            .delegators
352            .into_iter()
353            .map(|(account, amount)| proto::Delegator {
354                account: format!("{account:#x}"),
355                amount: format!("{amount:#x}"),
356            })
357            .collect();
358        // v1 serves a map, so the order is undefined.
359        delegators.sort_by(|a, b| a.account.cmp(&b.account));
360
361        proto::Validator {
362            account: format!("{:#x}", registered.account),
363            stake_table_key: registered.stake_table_key.map(|key| proto::BlsPublicKey {
364                key: key.to_string(),
365            }),
366            state_ver_key: registered.state_ver_key.map(|key| proto::SchnorrPublicKey {
367                key: key.to_string(),
368            }),
369            stake: format!("{:#x}", registered.stake),
370            commission: registered.commission.into(),
371            delegators,
372            authenticated: registered.authenticated,
373            x25519_key: registered.x25519_key.as_ref().map(ToString::to_string),
374            p2p_addr: registered
375                .p2p_addr
376                .as_ref()
377                .map(|addr| addr.unbracketed_string()),
378        }
379    }
380}
381
382impl From<HashMap<PubKey, f64>> for proto::ParticipationResponse {
383    fn from(fractions: HashMap<PubKey, f64>) -> Self {
384        let mut entries: Vec<(String, f64)> = fractions
385            .into_iter()
386            .map(|(key, participation)| (key.to_string(), participation))
387            .collect();
388        // v1 serves a map, so the order is undefined.
389        entries.sort_by(|a, b| a.0.cmp(&b.0));
390        proto::ParticipationResponse {
391            participation: entries
392                .into_iter()
393                .map(|(key, participation)| proto::ParticipationEntry {
394                    key: Some(proto::BlsPublicKey { key }),
395                    participation,
396                })
397                .collect(),
398        }
399    }
400}
401
402/// v1's serialized form is the source for the ADVZ and AvidM arms: jellyfish keeps the ADVZ
403/// share's fields private, and the AvidM payload is ark-serialized into single TaggedBase64
404/// strings that only its serde impl produces. The gf2 arm maps from the share's own accessors,
405/// because its payload is raw bytes that a JSON round trip would inflate into one number per byte.
406impl TryFrom<&VidShare> for proto::VidShareResponse {
407    type Error = tonic::Status;
408
409    fn try_from(share: &VidShare) -> Result<Self, Self::Error> {
410        // Matched on the enum rather than sniffed from the JSON, so a fourth scheme fails to
411        // compile instead of surfacing as a 500.
412        let arm = match share {
413            VidShare::V0(_) => {
414                let json = to_json(share)?;
415                let share = json_entry(&json, "V0")?;
416                let evals_proof = json_entry(share, "evals_proof")?;
417                proto::vid_share_response::Share::V0(proto::AdvzVidShare {
418                    index: json_field(share, "index")?,
419                    aggregate_proofs: json_field(share, "aggregate_proofs")?,
420                    evals: json_field(share, "evals")?,
421                    evals_proof: Some(proto::AdvzMerkleProof {
422                        pos: json_field(evals_proof, "pos")?,
423                        proof: json_array(evals_proof, "proof")?
424                            .iter()
425                            .map(advz_merkle_node)
426                            .collect::<Result<_, _>>()?,
427                    }),
428                })
429            },
430            VidShare::V1(_) => {
431                let json = to_json(share)?;
432                let share = json_entry(&json, "V1")?;
433                proto::vid_share_response::Share::V1(proto::AvidmVidShare {
434                    index: json_field(share, "index")?,
435                    ns_commits: json_field(share, "ns_commits")?,
436                    ns_lens: json_field(share, "ns_lens")?,
437                    content: json_array(share, "content")?
438                        .iter()
439                        .map(|content| {
440                            let range = json_entry(content, "range")?;
441                            Ok(proto::AvidmShareContent {
442                                range: Some(proto::ShardRange {
443                                    start: json_field(range, "start")?,
444                                    end: json_field(range, "end")?,
445                                }),
446                                payload: json_field(content, "payload")?,
447                                mt_proofs: json_field(content, "mt_proofs")?,
448                            })
449                        })
450                        .collect::<Result<_, tonic::Status>>()?,
451                })
452            },
453            VidShare::V2(gf2) => proto::vid_share_response::Share::V2(proto::AvidmGf2VidShare {
454                namespaces: gf2
455                    .ns_shares()
456                    .iter()
457                    .map(|namespace| proto::AvidmGf2Namespace {
458                        range: Some(proto::ShardRange {
459                            start: namespace.range().start as u64,
460                            end: namespace.range().end as u64,
461                        }),
462                        payload: namespace.payload().to_vec(),
463                        mt_proofs: namespace
464                            .mt_proofs()
465                            .iter()
466                            .map(ToString::to_string)
467                            .collect(),
468                    })
469                    .collect(),
470            }),
471        };
472        Ok(proto::VidShareResponse { share: Some(arm) })
473    }
474}
475
476fn advz_merkle_node(value: &serde_json::Value) -> Result<proto::AdvzMerkleNode, tonic::Status> {
477    let node = if let Some(leaf) = value.get("Leaf") {
478        Node::Leaf(proto::AdvzMerkleNodeLeaf {
479            elem: json_field(leaf, "elem")?,
480            pos: json_field(leaf, "pos")?,
481            value: json_field(leaf, "value")?,
482        })
483    } else if let Some(branch) = value.get("Branch") {
484        Node::Branch(proto::AdvzMerkleNodeBranch {
485            children: json_array(branch, "children")?
486                .iter()
487                .map(advz_merkle_node)
488                .collect::<Result<_, _>>()?,
489            value: json_field(branch, "value")?,
490        })
491    } else if let Some(subtree) = value.get("ForgettenSubtree") {
492        // Upstream's spelling, which the proto field name corrects.
493        Node::ForgottenSubtree(proto::AdvzMerkleNodeForgottenSubtree {
494            value: json_field(subtree, "value")?,
495        })
496    } else if value.as_str() == Some("Empty") {
497        // A unit variant, so v1 writes it as a bare string.
498        Node::Empty(proto::AdvzMerkleNodeEmpty {})
499    } else {
500        let arms: Vec<&str> = value
501            .as_object()
502            .map(|node| node.keys().map(String::as_str).collect())
503            .unwrap_or_default();
504        return Err(tonic::Status::internal(format!(
505            "VID share JSON has an unknown Merkle node arm: {arms:?}"
506        )));
507    };
508    Ok(proto::AdvzMerkleNode { node: Some(node) })
509}
510
511impl From<&[Header]> for proto::HeaderRangeResponse {
512    fn from(headers: &[Header]) -> Self {
513        proto::HeaderRangeResponse {
514            headers: headers.iter().map(Into::into).collect(),
515        }
516    }
517}
518
519impl From<&[LeafQueryData<SeqTypes>]> for proto::LeafRangeResponse {
520    fn from(leaves: &[LeafQueryData<SeqTypes>]) -> Self {
521        proto::LeafRangeResponse {
522            leaves: leaves.iter().map(Into::into).collect(),
523        }
524    }
525}
526
527impl From<&[BlockQueryData<SeqTypes>]> for proto::BlockRangeResponse {
528    fn from(blocks: &[BlockQueryData<SeqTypes>]) -> Self {
529        proto::BlockRangeResponse {
530            blocks: blocks.iter().map(Into::into).collect(),
531        }
532    }
533}
534
535impl From<&[PayloadQueryData<SeqTypes>]> for proto::PayloadRangeResponse {
536    fn from(payloads: &[PayloadQueryData<SeqTypes>]) -> Self {
537        proto::PayloadRangeResponse {
538            payloads: payloads.iter().map(Into::into).collect(),
539        }
540    }
541}
542
543impl TryFrom<&[VidCommonQueryData<SeqTypes>]> for proto::VidCommonRangeResponse {
544    type Error = tonic::Status;
545
546    fn try_from(items: &[VidCommonQueryData<SeqTypes>]) -> Result<Self, Self::Error> {
547        Ok(proto::VidCommonRangeResponse {
548            vid_common: items
549                .iter()
550                .map(TryInto::try_into)
551                .collect::<Result<_, _>>()?,
552        })
553    }
554}
555
556impl From<&[BlockSummaryQueryData<SeqTypes>]> for proto::BlockSummaryRangeResponse {
557    fn from(summaries: &[BlockSummaryQueryData<SeqTypes>]) -> Self {
558        proto::BlockSummaryRangeResponse {
559            summaries: summaries.iter().map(Into::into).collect(),
560        }
561    }
562}
563
564impl From<&[Leaf2<SeqTypes>]> for proto::CatchupLeafChainResponse {
565    fn from(leaves: &[Leaf2<SeqTypes>]) -> Self {
566        proto::CatchupLeafChainResponse {
567            leaves: leaves.iter().map(Into::into).collect(),
568        }
569    }
570}
571
572impl TryFrom<&[NamespaceProofQueryData]> for proto::NamespaceProofRangeResponse {
573    type Error = tonic::Status;
574
575    fn try_from(proofs: &[NamespaceProofQueryData]) -> Result<Self, Self::Error> {
576        Ok(proto::NamespaceProofRangeResponse {
577            proofs: proofs
578                .iter()
579                .map(TryInto::try_into)
580                .collect::<Result<_, _>>()?,
581        })
582    }
583}
584
585impl<E, I, T, const ARITY: usize> From<&MerkleProof<E, I, T, ARITY>> for proto::MerklePathResponse
586where
587    E: Element + CanonicalSerialize,
588    I: Index + CanonicalSerialize,
589    T: NodeValue,
590{
591    fn from(proof: &MerkleProof<E, I, T, ARITY>) -> Self {
592        proto::MerklePathResponse {
593            pos: field_tb64(&proof.pos),
594            proof: proof.proof.iter().map(Into::into).collect(),
595        }
596    }
597}
598
599impl<E, I, T> From<&MerkleNode<E, I, T>> for proto::AdvzMerkleNode
600where
601    E: Element + CanonicalSerialize,
602    I: Index + CanonicalSerialize,
603    T: NodeValue,
604{
605    fn from(node: &MerkleNode<E, I, T>) -> Self {
606        let node = match node {
607            MerkleNode::Empty => Node::Empty(proto::AdvzMerkleNodeEmpty {}),
608            MerkleNode::Branch { value, children } => Node::Branch(proto::AdvzMerkleNodeBranch {
609                value: field_tb64(value),
610                children: children
611                    .iter()
612                    .map(|child| proto::AdvzMerkleNode::from(&**child))
613                    .collect(),
614            }),
615            MerkleNode::Leaf { value, pos, elem } => Node::Leaf(proto::AdvzMerkleNodeLeaf {
616                value: field_tb64(value),
617                pos: field_tb64(pos),
618                elem: field_tb64(elem),
619            }),
620            MerkleNode::ForgettenSubtree { value } => {
621                Node::ForgottenSubtree(proto::AdvzMerkleNodeForgottenSubtree {
622                    value: field_tb64(value),
623                })
624            },
625        };
626        proto::AdvzMerkleNode { node: Some(node) }
627    }
628}
629
630impl From<RewardAccountQueryDataV2> for proto::RewardAccountProofResponse {
631    fn from(query: RewardAccountQueryDataV2) -> proto::RewardAccountProofResponse {
632        let RewardAccountQueryDataV2 {
633            balance,
634            proof: RewardAccountProofV2 { account, proof },
635        } = query;
636        let proof = match proof {
637            RewardMerkleProofV2::Presence(proof) => {
638                proto::reward_merkle_proof::Proof::Presence(proto::MerklePathResponse::from(&proof))
639            },
640            RewardMerkleProofV2::Absence(proof) => {
641                proto::reward_merkle_proof::Proof::Absence(proto::MerklePathResponse::from(&proof))
642            },
643        };
644        proto::RewardAccountProofResponse {
645            balance: balance.to_string(),
646            proof: Some(proto::RewardAccountProof {
647                account: format!("{account:#x}"),
648                proof: Some(proto::RewardMerkleProof { proof: Some(proof) }),
649            }),
650        }
651    }
652}
653
654/// As the [`RewardAccountQueryDataV2`] impl, for the v1 tree. The two proof types share no trait,
655/// so the match cannot be written once over both.
656impl From<v0_3::RewardAccountQueryDataV1> for proto::RewardAccountProofResponse {
657    fn from(query: v0_3::RewardAccountQueryDataV1) -> proto::RewardAccountProofResponse {
658        let v0_3::RewardAccountQueryDataV1 {
659            balance,
660            proof: v0_3::RewardAccountProofV1 { account, proof },
661        } = query;
662        let proof = match proof {
663            v0_3::RewardMerkleProofV1::Presence(proof) => {
664                proto::reward_merkle_proof::Proof::Presence(proto::MerklePathResponse::from(&proof))
665            },
666            v0_3::RewardMerkleProofV1::Absence(proof) => {
667                proto::reward_merkle_proof::Proof::Absence(proto::MerklePathResponse::from(&proof))
668            },
669        };
670        proto::RewardAccountProofResponse {
671            balance: balance.to_string(),
672            proof: Some(proto::RewardAccountProof {
673                account: format!("{account:#x}"),
674                proof: Some(proto::RewardMerkleProof { proof: Some(proof) }),
675            }),
676        }
677    }
678}
679
680impl From<AccountQueryData> for proto::FeeAccountProofResponse {
681    fn from(query: AccountQueryData) -> Self {
682        let AccountQueryData {
683            balance,
684            proof: FeeAccountProof { account, proof },
685        } = query;
686        let proof = match proof {
687            FeeMerkleProof::Presence(proof) => {
688                proto::fee_merkle_proof::Proof::Presence(proto::MerklePathResponse::from(&proof))
689            },
690            FeeMerkleProof::Absence(proof) => {
691                proto::fee_merkle_proof::Proof::Absence(proto::MerklePathResponse::from(&proof))
692            },
693        };
694        proto::FeeAccountProofResponse {
695            balance: balance.to_string(),
696            proof: Some(proto::FeeAccountProof {
697                account: format!("{account:#x}"),
698                proof: Some(proto::FeeMerkleProof { proof: Some(proof) }),
699            }),
700        }
701    }
702}
703
704/// The encoding jellyfish's `canonical` serde helper gives every hash, index and element of a
705/// proof: ark-compressed bytes under the `FIELD` tag, whatever the underlying type is.
706/// `block_state_path_mirrors_its_v1_rendering` pins the two to the same bytes.
707fn field_tb64<T>(value: &T) -> String
708where
709    T: CanonicalSerialize,
710{
711    let mut bytes = Vec::new();
712    value
713        .serialize_compressed(&mut bytes)
714        .expect("serializing to a Vec cannot fail");
715    TaggedBase64::new("FIELD", &bytes)
716        .expect("FIELD is a valid tag")
717        .to_string()
718}
719
720/// jellyfish keeps the fields of the VID shares and common, the range proofs and the bad-encoding
721/// proof private, so v1's serde encoding is their one public view.
722fn to_json(value: &impl serde::Serialize) -> Result<serde_json::Value, tonic::Status> {
723    serde_json::to_value(value)
724        .map_err(|err| tonic::Status::internal(format!("v1 encoding failed: {err}")))
725}
726
727/// A missing or mistyped field means the upstream type changed, which is a 500 rather than an
728/// empty value. v1 writes byte fields as integer arrays, which read back as `Vec<u8>`.
729fn json_field<T>(value: &serde_json::Value, field: &str) -> Result<T, tonic::Status>
730where
731    T: serde::de::DeserializeOwned,
732{
733    T::deserialize(json_entry(value, field)?)
734        .map_err(|err| tonic::Status::internal(format!("v1 JSON {field}: {err}")))
735}
736
737fn json_entry<'a>(
738    value: &'a serde_json::Value,
739    field: &str,
740) -> Result<&'a serde_json::Value, tonic::Status> {
741    value
742        .get(field)
743        .ok_or_else(|| tonic::Status::internal(format!("v1 JSON has no {field}")))
744}
745
746fn json_array<'a>(
747    value: &'a serde_json::Value,
748    field: &str,
749) -> Result<&'a [serde_json::Value], tonic::Status> {
750    json_entry(value, field)?
751        .as_array()
752        .map(Vec::as_slice)
753        .ok_or_else(|| tonic::Status::internal(format!("v1 JSON {field}: not an array")))
754}
755
756// Not beside the runtime config in the node crate: both types are foreign there, so the orphan
757// rule refuses the impl. The destructure below reaches only the inner config, because the
758// wrapper's fields are private; a test in espresso-types guards those.
759impl From<PublicNetworkConfig> for proto::HotshotConfigResponse {
760    fn from(public_config: PublicNetworkConfig) -> Self {
761        let config = public_config.hotshot_config().into_hotshot_config();
762        // Destructured without `..` so that a field added to HotShotConfig fails to compile here
763        // instead of becoming a parameter v2 silently never serves.
764        let HotShotConfig {
765            start_threshold: (start_threshold_numerator, start_threshold_denominator),
766            num_nodes_with_stake,
767            known_nodes_with_stake,
768            known_da_nodes,
769            da_committees,
770            da_staked_committee_size,
771            fixed_leader_for_gpuvid,
772            next_view_timeout,
773            view_sync_timeout,
774            num_bootstrap,
775            builder_timeout,
776            data_request_delay,
777            builder_urls,
778            start_proposing_view,
779            stop_proposing_view,
780            start_voting_view,
781            stop_voting_view,
782            start_proposing_time,
783            stop_proposing_time,
784            start_voting_time,
785            stop_voting_time,
786            epoch_height,
787            epoch_start_block,
788            stake_table_capacity,
789            drb_difficulty,
790            drb_upgrade_difficulty,
791        } = config;
792        proto::HotshotConfigResponse {
793            start_threshold_numerator,
794            start_threshold_denominator,
795            num_nodes_with_stake: num_nodes_with_stake.get() as u64,
796            da_staked_committee_size: da_staked_committee_size as u64,
797            next_view_timeout_ms: next_view_timeout,
798            view_sync_timeout_ms: view_sync_timeout.as_millis() as u64,
799            builder_timeout_ms: builder_timeout.as_millis() as u64,
800            data_request_delay_ms: data_request_delay.as_millis() as u64,
801            builder_urls: builder_urls.iter().map(ToString::to_string).collect(),
802            start_proposing_view,
803            stop_proposing_view,
804            start_voting_view,
805            stop_voting_view,
806            start_proposing_time,
807            stop_proposing_time,
808            start_voting_time,
809            stop_voting_time,
810            epoch_height,
811            epoch_start_block,
812            stake_table_capacity: stake_table_capacity as u64,
813            drb_difficulty,
814            drb_upgrade_difficulty,
815            known_nodes_with_stake: known_nodes_with_stake.into_iter().map(Into::into).collect(),
816            known_da_nodes: known_da_nodes.into_iter().map(Into::into).collect(),
817            da_committees: da_committees
818                .into_iter()
819                .map(|da_committee| proto::VersionedDaCommittee {
820                    start_version: da_committee.start_version.to_string(),
821                    start_epoch: da_committee.start_epoch,
822                    committee: da_committee.committee.into_iter().map(Into::into).collect(),
823                })
824                .collect(),
825            fixed_leader_for_gpuvid: fixed_leader_for_gpuvid as u64,
826            num_bootstrap: num_bootstrap as u64,
827            commit_sha: public_config.commit_sha().to_string(),
828            indexed_da: public_config.indexed_da(),
829            builder: match public_config.builder() {
830                BuilderType::External => proto::BuilderType::External,
831                BuilderType::Simple => proto::BuilderType::Simple,
832                BuilderType::Random => proto::BuilderType::Random,
833            }
834            .into(),
835            // The deprecated CDN and libp2p fields stay at their empty defaults.
836            ..Default::default()
837        }
838    }
839}
840
841fn quorum_signatures<V, T>(
842    cert: &SimpleCertificate<SeqTypes, V, T>,
843) -> Option<proto::QuorumSignatures>
844where
845    V: Voteable<SeqTypes>,
846    T: Threshold<SeqTypes>,
847{
848    cert.signatures.as_ref().map(qc_signatures)
849}
850
851/// An aggregate signature and who signed it.
852fn qc_signatures(
853    (signature, signers): &<PubKey as SignatureKey>::QcType,
854) -> proto::QuorumSignatures {
855    // v1 serializes the bitvec crate's memory layout, the API publishes who signed instead.
856    proto::QuorumSignatures {
857        signature: signature.to_string(),
858        signers: signers.iter().by_vals().collect(),
859    }
860}
861
862impl From<&QuorumData2<SeqTypes>> for proto::QuorumData2 {
863    fn from(data: &QuorumData2<SeqTypes>) -> Self {
864        proto::QuorumData2 {
865            leaf_commit: data.leaf_commit.to_string(),
866            epoch: data.epoch.map(|epoch| epoch.u64()),
867            block_number: data.block_number,
868        }
869    }
870}
871
872/// The QC and the next-epoch QC, which vote on the same data.
873impl<V> From<&SimpleCertificate<SeqTypes, V, SuccessThreshold>> for proto::QuorumCertificate2
874where
875    V: Voteable<SeqTypes> + Borrow<QuorumData2<SeqTypes>>,
876{
877    fn from(cert: &SimpleCertificate<SeqTypes, V, SuccessThreshold>) -> Self {
878        proto::QuorumCertificate2 {
879            data: Some(cert.data.borrow().into()),
880            vote_commitment: cert.vote_commitment().to_string(),
881            view_number: cert.view_number.u64(),
882            signatures: quorum_signatures(cert),
883        }
884    }
885}
886
887impl From<&CertificatePair<SeqTypes>> for proto::CertificatePair {
888    fn from(pair: &CertificatePair<SeqTypes>) -> Self {
889        proto::CertificatePair {
890            qc: Some(pair.qc().into()),
891            next_epoch_qc: pair.next_epoch_qc().map(Into::into),
892        }
893    }
894}
895
896impl From<&Certificate2<SeqTypes>> for proto::Certificate2 {
897    fn from(cert: &Certificate2<SeqTypes>) -> Self {
898        proto::Certificate2 {
899            data: Some(proto::Vote2Data {
900                leaf_commit: cert.data.leaf_commit.to_string(),
901                epoch: cert.data.epoch.u64(),
902                block_number: cert.data.block_number,
903            }),
904            vote_commitment: cert.vote_commitment().to_string(),
905            view_number: cert.view_number.u64(),
906            signatures: quorum_signatures(cert),
907        }
908    }
909}
910
911impl From<vbs::version::Version> for proto::ProtocolVersion {
912    fn from(version: vbs::version::Version) -> Self {
913        proto::ProtocolVersion {
914            major: u32::from(version.major),
915            minor: u32::from(version.minor),
916        }
917    }
918}
919
920impl From<&UpgradeCertificate<SeqTypes>> for proto::UpgradeCertificate {
921    fn from(cert: &UpgradeCertificate<SeqTypes>) -> Self {
922        proto::UpgradeCertificate {
923            data: Some(proto::UpgradeProposalData {
924                old_version: Some(cert.data.old_version.into()),
925                new_version: Some(cert.data.new_version.into()),
926                decide_by: cert.data.decide_by.u64(),
927                new_version_hash: cert.data.new_version_hash.clone(),
928                old_version_last_view: cert.data.old_version_last_view.u64(),
929                new_version_first_view: cert.data.new_version_first_view.u64(),
930            }),
931            vote_commitment: cert.vote_commitment().to_string(),
932            view_number: cert.view_number.u64(),
933            signatures: quorum_signatures(cert),
934        }
935    }
936}
937
938impl From<&ViewChangeEvidence2<SeqTypes>> for proto::ViewChangeEvidence2 {
939    fn from(evidence: &ViewChangeEvidence2<SeqTypes>) -> Self {
940        use proto::view_change_evidence2::Evidence;
941
942        let evidence = match evidence {
943            ViewChangeEvidence2::Timeout(cert) => Evidence::Timeout(cert.into()),
944            ViewChangeEvidence2::Timeout3(cert) => Evidence::Timeout3(cert.into()),
945            ViewChangeEvidence2::ViewSync(cert) => Evidence::ViewSync(cert.into()),
946        };
947        proto::ViewChangeEvidence2 {
948            evidence: Some(evidence),
949        }
950    }
951}
952
953impl From<&TimeoutCertificate2<SeqTypes>> for proto::TimeoutCertificate2 {
954    fn from(cert: &TimeoutCertificate2<SeqTypes>) -> Self {
955        proto::TimeoutCertificate2 {
956            data: Some(proto::TimeoutData2 {
957                view: cert.data.view.u64(),
958                epoch: cert.data.epoch.map(|epoch| epoch.u64()),
959            }),
960            vote_commitment: cert.vote_commitment().to_string(),
961            view_number: cert.view_number.u64(),
962            signatures: quorum_signatures(cert),
963        }
964    }
965}
966
967impl From<&TimeoutCertificate3<SeqTypes>> for proto::TimeoutCertificate3 {
968    fn from(cert: &TimeoutCertificate3<SeqTypes>) -> Self {
969        let lock_view = |lock: &LockView| proto::LockView {
970            epoch: lock.epoch.u64(),
971            view: lock.view.u64(),
972        };
973        proto::TimeoutCertificate3 {
974            view_number: cert.view_number.u64(),
975            epoch: cert.epoch.u64(),
976            lock_cert: cert.lock_cert().map(Into::into),
977            signatures: Some(qc_signatures(&cert.latest.signatures)),
978            earlier: cert
979                .earlier
980                .iter()
981                .map(|group| proto::TimeoutSignatures {
982                    lock: group.lock.as_ref().map(lock_view),
983                    signatures: Some(qc_signatures(&group.signatures)),
984                })
985                .collect(),
986        }
987    }
988}
989
990impl From<&ViewSyncFinalizeCertificate2<SeqTypes>> for proto::ViewSyncFinalizeCertificate2 {
991    fn from(cert: &ViewSyncFinalizeCertificate2<SeqTypes>) -> Self {
992        proto::ViewSyncFinalizeCertificate2 {
993            data: Some(proto::ViewSyncFinalizeData2 {
994                relay: cert.data.relay,
995                round: cert.data.round.u64(),
996                epoch: cert.data.epoch.map(|epoch| epoch.u64()),
997            }),
998            vote_commitment: cert.vote_commitment().to_string(),
999            view_number: cert.view_number.u64(),
1000            signatures: quorum_signatures(cert),
1001        }
1002    }
1003}
1004
1005impl From<&Payload> for proto::Payload {
1006    fn from(payload: &Payload) -> Self {
1007        proto::Payload {
1008            raw_payload: payload.raw_payload().to_vec(),
1009            ns_table: Some(proto::NsTable {
1010                bytes: payload.ns_table().encode().to_vec(),
1011            }),
1012        }
1013    }
1014}
1015
1016impl From<&Leaf2<SeqTypes>> for proto::Leaf2 {
1017    fn from(leaf: &Leaf2<SeqTypes>) -> Self {
1018        proto::Leaf2 {
1019            view_number: leaf.view_number().u64(),
1020            justify_qc: Some(leaf.justify_qc().into()),
1021            next_epoch_justify_qc: leaf.next_epoch_justify_qc().map(Into::into),
1022            parent_commitment: leaf.parent_commitment().to_string(),
1023            block_header: Some(leaf.block_header().into()),
1024            upgrade_certificate: leaf.upgrade_certificate().map(Into::into),
1025            block_payload: leaf.block_payload_ref().map(Into::into),
1026            view_change_evidence: leaf.view_change_evidence.as_ref().map(Into::into),
1027            next_drb_result: leaf
1028                .next_drb_result
1029                .map(|result| result.to_vec())
1030                .unwrap_or_default(),
1031            with_epoch: leaf.with_epoch,
1032        }
1033    }
1034}
1035
1036impl From<&LeafQueryData<SeqTypes>> for proto::LeafResponse {
1037    fn from(leaf: &LeafQueryData<SeqTypes>) -> Self {
1038        proto::LeafResponse {
1039            leaf: Some(leaf.leaf().into()),
1040            qc: Some(leaf.qc().into()),
1041        }
1042    }
1043}
1044
1045impl From<&BlockQueryData<SeqTypes>> for proto::BlockResponse {
1046    fn from(block: &BlockQueryData<SeqTypes>) -> Self {
1047        proto::BlockResponse {
1048            header: Some(block.header().into()),
1049            payload: Some(block.payload().into()),
1050            hash: block.hash().to_string(),
1051            size: block.size(),
1052            num_transactions: block.num_transactions(),
1053        }
1054    }
1055}
1056
1057impl From<&PayloadQueryData<SeqTypes>> for proto::PayloadResponse {
1058    fn from(payload: &PayloadQueryData<SeqTypes>) -> Self {
1059        proto::PayloadResponse {
1060            height: payload.height,
1061            block_hash: payload.block_hash().to_string(),
1062            hash: payload.hash().to_string(),
1063            size: payload.size(),
1064            data: Some(payload.data().into()),
1065        }
1066    }
1067}
1068
1069impl TryFrom<&VidCommonQueryData<SeqTypes>> for proto::VidCommonResponse {
1070    type Error = tonic::Status;
1071
1072    fn try_from(common: &VidCommonQueryData<SeqTypes>) -> Result<Self, Self::Error> {
1073        Ok(proto::VidCommonResponse {
1074            height: common.height,
1075            block_hash: common.block_hash().to_string(),
1076            payload_hash: common.payload_hash().to_string(),
1077            common: Some(common.common().try_into()?),
1078        })
1079    }
1080}
1081
1082impl TryFrom<&VidCommon> for proto::vid_common::Common {
1083    type Error = tonic::Status;
1084
1085    fn try_from(common: &VidCommon) -> Result<Self, Self::Error> {
1086        use proto::vid_common::Common;
1087
1088        Ok(match common {
1089            VidCommon::V0(advz) => {
1090                let value = to_json(advz)?;
1091                Common::V0(proto::AdvzCommon {
1092                    poly_commits: json_field(&value, "poly_commits")?,
1093                    all_evals_digest: json_field(&value, "all_evals_digest")?,
1094                    payload_byte_len: json_field(&value, "payload_byte_len")?,
1095                    num_storage_nodes: json_field(&value, "num_storage_nodes")?,
1096                    multiplicity: json_field(&value, "multiplicity")?,
1097                })
1098            },
1099            VidCommon::V1(param) => Common::V1(proto::AvidmCommon {
1100                total_weights: param.total_weights as u64,
1101                recovery_threshold: param.recovery_threshold as u64,
1102            }),
1103            VidCommon::V2(namespaced) => Common::V2(proto::AvidmGf2Common {
1104                param: Some(proto::AvidmGf2Param {
1105                    total_weights: namespaced.param.total_weights as u64,
1106                    recovery_threshold: namespaced.param.recovery_threshold as u64,
1107                }),
1108                ns_commits: namespaced
1109                    .ns_commits
1110                    .iter()
1111                    .map(ToString::to_string)
1112                    .collect(),
1113                ns_lens: namespaced.ns_lens.iter().map(|len| *len as u64).collect(),
1114            }),
1115        })
1116    }
1117}
1118
1119impl TryFrom<&VidCommon> for proto::VidCommon {
1120    type Error = tonic::Status;
1121
1122    fn try_from(common: &VidCommon) -> Result<Self, Self::Error> {
1123        Ok(proto::VidCommon {
1124            common: Some(common.try_into()?),
1125        })
1126    }
1127}
1128
1129impl From<&LeafProof> for proto::LightClientLeafProofResponse {
1130    fn from(proof: &LeafProof) -> Self {
1131        proto::LightClientLeafProofResponse {
1132            leaves: proof.leaves().iter().map(Into::into).collect(),
1133            proof: Some(proof.proof().into()),
1134        }
1135    }
1136}
1137
1138impl From<&FinalityProof> for proto::FinalityProof {
1139    fn from(proof: &FinalityProof) -> Self {
1140        use proto::finality_proof::Proof;
1141
1142        let arm = match proof {
1143            FinalityProof::Assumption => Proof::Assumption(proto::FinalityAssumption {}),
1144            FinalityProof::HotStuff2 {
1145                committing_qc,
1146                deciding_qc,
1147            } => Proof::HotStuff2(proto::HotStuff2Finality {
1148                committing_qc: Some(committing_qc.as_ref().into()),
1149                deciding_qc: Some(deciding_qc.as_ref().into()),
1150            }),
1151            FinalityProof::NewProtocol { cert2, leaf_qc } => {
1152                Proof::NewProtocol(proto::NewProtocolFinality {
1153                    cert2: Some(cert2.as_ref().into()),
1154                    leaf_qc: Some(leaf_qc.as_ref().into()),
1155                })
1156            },
1157            FinalityProof::HotStuff {
1158                precommit_qc,
1159                committing_qc,
1160                deciding_qc,
1161            } => Proof::HotStuff(proto::HotStuffFinality {
1162                precommit_qc: Some(precommit_qc.as_ref().into()),
1163                committing_qc: Some(committing_qc.as_ref().into()),
1164                deciding_qc: Some(deciding_qc.as_ref().into()),
1165            }),
1166        };
1167        proto::FinalityProof { proof: Some(arm) }
1168    }
1169}
1170
1171impl TryFrom<&PayloadProof> for proto::LightClientPayloadProofResponse {
1172    type Error = tonic::Status;
1173
1174    fn try_from(proof: &PayloadProof) -> Result<Self, Self::Error> {
1175        Ok(proto::LightClientPayloadProofResponse {
1176            payload: Some(proof.payload().into()),
1177            vid_common: Some(proof.vid_common().try_into()?),
1178        })
1179    }
1180}
1181
1182impl TryFrom<&NamespaceProof> for proto::LightClientNamespaceProofResponse {
1183    type Error = tonic::Status;
1184
1185    fn try_from(proof: &NamespaceProof) -> Result<Self, Self::Error> {
1186        Ok(proto::LightClientNamespaceProofResponse {
1187            contents: proof.contents().map(TryInto::try_into).transpose()?,
1188        })
1189    }
1190}
1191
1192/// The pair [`NamespaceProof::contents`] returns.
1193impl TryFrom<(&NsProof, &VidCommon)> for proto::LightClientNamespaceProofContents {
1194    type Error = tonic::Status;
1195
1196    fn try_from((proof, common): (&NsProof, &VidCommon)) -> Result<Self, Self::Error> {
1197        Ok(proto::LightClientNamespaceProofContents {
1198            proof: Some(proof.try_into()?),
1199            vid_common: Some(common.try_into()?),
1200        })
1201    }
1202}
1203
1204impl From<&StakeTableEvent> for proto::StakeTableEvent {
1205    fn from(event: &StakeTableEvent) -> Self {
1206        use proto::stake_table_event::Event;
1207
1208        let arm = match event {
1209            StakeTableEvent::Register(event) => Event::Register(proto::ValidatorRegistered {
1210                account: format!("{:#x}", event.account),
1211                bls_vk: Some((&event.blsVk).into()),
1212                schnorr_vk: Some((&event.schnorrVk).into()),
1213                commission: u32::from(event.commission),
1214            }),
1215            StakeTableEvent::RegisterV2(event) => Event::RegisterV2(proto::ValidatorRegisteredV2 {
1216                account: format!("{:#x}", event.account),
1217                bls_vk: Some((&event.blsVK).into()),
1218                schnorr_vk: Some((&event.schnorrVK).into()),
1219                commission: u32::from(event.commission),
1220                bls_sig: Some((&event.blsSig).into()),
1221                schnorr_sig: event.schnorrSig.to_vec(),
1222                metadata_uri: event.metadataUri.clone(),
1223            }),
1224            StakeTableEvent::RegisterV3(event) => Event::RegisterV3(proto::ValidatorRegisteredV3 {
1225                account: format!("{:#x}", event.account),
1226                bls_vk: Some((&event.blsVK).into()),
1227                schnorr_vk: Some((&event.schnorrVK).into()),
1228                commission: u32::from(event.commission),
1229                bls_sig: Some((&event.blsSig).into()),
1230                schnorr_sig: event.schnorrSig.to_vec(),
1231                metadata_uri: event.metadataUri.clone(),
1232                x25519_key: event.x25519Key.to_vec(),
1233                p2p_addr: event.p2pAddr.clone(),
1234            }),
1235            StakeTableEvent::Deregister(event) => Event::Deregister(proto::ValidatorExit {
1236                validator: format!("{:#x}", event.validator),
1237            }),
1238            StakeTableEvent::DeregisterV2(event) => Event::DeregisterV2(proto::ValidatorExitV2 {
1239                validator: format!("{:#x}", event.validator),
1240                unlocks_at: event.unlocksAt.to_string(),
1241            }),
1242            StakeTableEvent::Delegate(event) => Event::Delegate(proto::Delegated {
1243                delegator: format!("{:#x}", event.delegator),
1244                validator: format!("{:#x}", event.validator),
1245                amount: event.amount.to_string(),
1246            }),
1247            StakeTableEvent::Undelegate(event) => Event::Undelegate(proto::Undelegated {
1248                delegator: format!("{:#x}", event.delegator),
1249                validator: format!("{:#x}", event.validator),
1250                amount: event.amount.to_string(),
1251            }),
1252            StakeTableEvent::UndelegateV2(event) => Event::UndelegateV2(proto::UndelegatedV2 {
1253                delegator: format!("{:#x}", event.delegator),
1254                validator: format!("{:#x}", event.validator),
1255                undelegation_id: event.undelegationId,
1256                amount: event.amount.to_string(),
1257                unlocks_at: event.unlocksAt.to_string(),
1258            }),
1259            StakeTableEvent::KeyUpdate(event) => Event::KeyUpdate(proto::ConsensusKeysUpdated {
1260                account: format!("{:#x}", event.account),
1261                bls_vk: Some((&event.blsVK).into()),
1262                schnorr_vk: Some((&event.schnorrVK).into()),
1263            }),
1264            StakeTableEvent::KeyUpdateV2(event) => {
1265                Event::KeyUpdateV2(proto::ConsensusKeysUpdatedV2 {
1266                    account: format!("{:#x}", event.account),
1267                    bls_vk: Some((&event.blsVK).into()),
1268                    schnorr_vk: Some((&event.schnorrVK).into()),
1269                    bls_sig: Some((&event.blsSig).into()),
1270                    schnorr_sig: event.schnorrSig.to_vec(),
1271                })
1272            },
1273            StakeTableEvent::CommissionUpdate(event) => {
1274                Event::CommissionUpdate(proto::CommissionUpdated {
1275                    validator: format!("{:#x}", event.validator),
1276                    timestamp: event.timestamp.to_string(),
1277                    old_commission: u32::from(event.oldCommission),
1278                    new_commission: u32::from(event.newCommission),
1279                })
1280            },
1281            StakeTableEvent::X25519KeyUpdate(event) => {
1282                Event::X25519KeyUpdate(proto::X25519KeyUpdated {
1283                    validator: format!("{:#x}", event.validator),
1284                    x25519_key: event.x25519Key.to_vec(),
1285                })
1286            },
1287            StakeTableEvent::P2pAddrUpdate(event) => Event::P2pAddrUpdate(proto::P2pAddrUpdated {
1288                validator: format!("{:#x}", event.validator),
1289                p2p_addr: event.p2pAddr.clone(),
1290            }),
1291        };
1292        proto::StakeTableEvent { event: Some(arm) }
1293    }
1294}
1295
1296impl From<&G1PointSol> for proto::Bn254G1Point {
1297    fn from(point: &G1PointSol) -> Self {
1298        proto::Bn254G1Point {
1299            x: format!("{:#x}", point.x),
1300            y: format!("{:#x}", point.y),
1301        }
1302    }
1303}
1304
1305impl From<&G2PointSol> for proto::Bn254G2Point {
1306    fn from(point: &G2PointSol) -> Self {
1307        proto::Bn254G2Point {
1308            x0: format!("{:#x}", point.x0),
1309            x1: format!("{:#x}", point.x1),
1310            y0: format!("{:#x}", point.y0),
1311            y1: format!("{:#x}", point.y1),
1312        }
1313    }
1314}
1315
1316impl From<&EdOnBN254PointSol> for proto::EdOnBn254Point {
1317    fn from(point: &EdOnBN254PointSol) -> Self {
1318        proto::EdOnBn254Point {
1319            x: format!("{:#x}", point.x),
1320            y: format!("{:#x}", point.y),
1321        }
1322    }
1323}
1324
1325impl From<&AvidMNsProof> for proto::NsProofPayload {
1326    fn from(proof: &AvidMNsProof) -> Self {
1327        proto::NsProofPayload {
1328            ns_index: proof.0.ns_index as u64,
1329            ns_payload: proof.0.ns_payload.to_vec(),
1330            ns_proof: proof.0.ns_proof.to_string(),
1331        }
1332    }
1333}
1334
1335impl From<&AvidmGf2NsProof> for proto::NsProofPayload {
1336    fn from(proof: &AvidmGf2NsProof) -> Self {
1337        proto::NsProofPayload {
1338            ns_index: proof.0.ns_index as u64,
1339            ns_payload: proof.0.ns_payload.to_vec(),
1340            ns_proof: proof.0.ns_proof.to_string(),
1341        }
1342    }
1343}
1344
1345impl TryFrom<&TxProof> for proto::TxProof {
1346    type Error = tonic::Status;
1347
1348    fn try_from(proof: &TxProof) -> Result<Self, Self::Error> {
1349        use proto::tx_proof::Proof;
1350
1351        let arm = match proof {
1352            TxProof::V0(advz) => Proof::V0(proto::AdvzTxProof {
1353                tx_index: advz.tx_index().to_bytes().to_vec(),
1354                payload_num_txs: advz.payload_num_txs().to_payload_bytes().to_vec(),
1355                payload_proof_num_txs: Some(advz.payload_proof_num_txs().try_into()?),
1356                payload_tx_table_entries: advz.payload_tx_table_entries().to_payload_bytes(),
1357                payload_proof_tx_table_entries: Some(
1358                    advz.payload_proof_tx_table_entries().try_into()?,
1359                ),
1360                payload_proof_tx: advz.payload_proof_tx().map(TryInto::try_into).transpose()?,
1361            }),
1362            TxProof::V1(avidm) => Proof::V1(proto::AvidmTxProof {
1363                tx_index: avidm.tx_index().to_bytes().to_vec(),
1364                ns_proof: Some(avidm.ns_proof().into()),
1365            }),
1366            TxProof::V2(gf2) => Proof::V2(proto::AvidmGf2TxProof {
1367                tx_index: gf2.tx_index().to_bytes().to_vec(),
1368                ns_proof: Some(gf2.ns_proof().into()),
1369            }),
1370        };
1371        Ok(proto::TxProof { proof: Some(arm) })
1372    }
1373}
1374
1375impl TryFrom<&SmallRangeProofType> for proto::SmallRangeProof {
1376    type Error = tonic::Status;
1377
1378    fn try_from(proof: &SmallRangeProofType) -> Result<Self, Self::Error> {
1379        let value = to_json(proof)?;
1380        Ok(proto::SmallRangeProof {
1381            proofs: json_field(&value, "proofs")?,
1382            prefix_bytes: json_field(&value, "prefix_bytes")?,
1383            suffix_bytes: json_field(&value, "suffix_bytes")?,
1384        })
1385    }
1386}
1387
1388impl TryFrom<&LargeRangeProofType> for proto::LargeRangeProof {
1389    type Error = tonic::Status;
1390
1391    fn try_from(proof: &LargeRangeProofType) -> Result<Self, Self::Error> {
1392        let value = to_json(proof)?;
1393        Ok(proto::LargeRangeProof {
1394            prefix_elems: json_field(&value, "prefix_elems")?,
1395            suffix_elems: json_field(&value, "suffix_elems")?,
1396            prefix_bytes: json_field(&value, "prefix_bytes")?,
1397            suffix_bytes: json_field(&value, "suffix_bytes")?,
1398        })
1399    }
1400}
1401
1402impl From<&Transaction> for proto::Transaction {
1403    fn from(tx: &Transaction) -> Self {
1404        proto::Transaction {
1405            namespace: tx.namespace().0,
1406            payload: tx.payload().to_vec(),
1407        }
1408    }
1409}
1410
1411impl From<&TransactionQueryData<SeqTypes>> for proto::TransactionResponse {
1412    fn from(tx: &TransactionQueryData<SeqTypes>) -> Self {
1413        proto::TransactionResponse {
1414            transaction: Some(tx.transaction().into()),
1415            hash: tx.hash().to_string(),
1416            index: tx.index(),
1417            block_hash: tx.block_hash().to_string(),
1418            block_height: tx.block_height(),
1419            namespace: tx.namespace().0,
1420            pos_in_namespace: tx.pos_in_namespace(),
1421        }
1422    }
1423}
1424
1425impl TryFrom<&TransactionWithProofQueryData<SeqTypes>> for proto::TransactionWithProofResponse {
1426    type Error = tonic::Status;
1427
1428    fn try_from(tx: &TransactionWithProofQueryData<SeqTypes>) -> Result<Self, Self::Error> {
1429        Ok(proto::TransactionWithProofResponse {
1430            transaction: Some(tx.transaction().into()),
1431            hash: tx.hash().to_string(),
1432            index: tx.index(),
1433            block_hash: tx.block_hash().to_string(),
1434            block_height: tx.block_height(),
1435            namespace: tx.namespace().0,
1436            pos_in_namespace: tx.pos_in_namespace(),
1437            proof: Some(tx.proof().try_into()?),
1438        })
1439    }
1440}
1441
1442impl From<&BlockSummaryQueryData<SeqTypes>> for proto::BlockSummaryResponse {
1443    fn from(summary: &BlockSummaryQueryData<SeqTypes>) -> Self {
1444        proto::BlockSummaryResponse {
1445            header: Some((&summary.header).into()),
1446            hash: summary.hash.to_string(),
1447            size: summary.size,
1448            num_transactions: summary.num_transactions,
1449            namespaces: summary
1450                .namespaces
1451                .iter()
1452                .map(|(namespace, info)| {
1453                    (
1454                        namespace.0,
1455                        proto::NamespaceInfo {
1456                            num_transactions: info.num_transactions,
1457                            size: info.size,
1458                        },
1459                    )
1460                })
1461                .collect(),
1462        }
1463    }
1464}
1465
1466impl TryFrom<&AvidMIncorrectEncodingNsProof> for proto::AvidmBadEncodingNsProof {
1467    type Error = tonic::Status;
1468
1469    fn try_from(proof: &AvidMIncorrectEncodingNsProof) -> Result<Self, Self::Error> {
1470        let inner = &proof.0;
1471        let value = to_json(&inner.ns_proof)?;
1472        Ok(proto::AvidmBadEncodingNsProof {
1473            ns_index: inner.ns_index as u64,
1474            ns_commit: inner.ns_commit.to_string(),
1475            ns_mt_proof: inner.ns_mt_proof.to_string(),
1476            ns_proof: Some(proto::AvidmBadEncodingProof {
1477                recovered_poly: json_field(&value, "recovered_poly")?,
1478                raw_shares: json_field(&value, "raw_shares")?,
1479            }),
1480        })
1481    }
1482}
1483
1484impl TryFrom<&NsProof> for proto::NsProof {
1485    type Error = tonic::Status;
1486
1487    fn try_from(proof: &NsProof) -> Result<Self, Self::Error> {
1488        use proto::ns_proof::Proof;
1489
1490        let arm = match proof {
1491            NsProof::V0(advz) => Proof::V0(proto::AdvzNsProof {
1492                ns_index: advz.ns_index.to_bytes().to_vec(),
1493                ns_payload: advz.ns_payload.as_bytes_slice().to_vec(),
1494                ns_proof: advz.ns_proof.as_ref().map(TryInto::try_into).transpose()?,
1495            }),
1496            NsProof::V1(avidm) => Proof::V1(avidm.into()),
1497            NsProof::V1IncorrectEncoding(bad) => Proof::V1IncorrectEncoding(bad.try_into()?),
1498            NsProof::V2(gf2) => Proof::V2(gf2.into()),
1499        };
1500        Ok(proto::NsProof { proof: Some(arm) })
1501    }
1502}
1503
1504impl TryFrom<&NamespaceProofQueryData> for proto::NamespaceProofResponse {
1505    type Error = tonic::Status;
1506
1507    fn try_from(data: &NamespaceProofQueryData) -> Result<Self, Self::Error> {
1508        Ok(proto::NamespaceProofResponse {
1509            proof: data.proof.as_ref().map(TryInto::try_into).transpose()?,
1510            transactions: data.transactions.iter().map(Into::into).collect(),
1511        })
1512    }
1513}
1514
1515impl From<&StateCertQueryDataV1<SeqTypes>> for proto::StateCertV1Response {
1516    fn from(cert: &StateCertQueryDataV1<SeqTypes>) -> Self {
1517        let cert = &cert.0;
1518        proto::StateCertV1Response {
1519            epoch: cert.epoch.u64(),
1520            light_client_state: cert.light_client_state.to_string(),
1521            next_stake_table_state: cert.next_stake_table_state.to_string(),
1522            signatures: cert
1523                .signatures
1524                .iter()
1525                .map(|(key, signature)| proto::StateSignatureV1 {
1526                    key: key.to_string(),
1527                    signature: signature.to_string(),
1528                })
1529                .collect(),
1530        }
1531    }
1532}
1533
1534impl From<&StateCertQueryDataV2<SeqTypes>> for proto::StateCertV2Response {
1535    fn from(cert: &StateCertQueryDataV2<SeqTypes>) -> Self {
1536        let cert = &cert.0;
1537        proto::StateCertV2Response {
1538            epoch: cert.epoch.u64(),
1539            light_client_state: cert.light_client_state.to_string(),
1540            next_stake_table_state: cert.next_stake_table_state.to_string(),
1541            signatures: cert
1542                .signatures
1543                .iter()
1544                .map(|(key, lcv3, lcv2)| proto::StateSignatureV2 {
1545                    key: key.to_string(),
1546                    lcv3_signature: lcv3.to_string(),
1547                    lcv2_signature: lcv2.to_string(),
1548                })
1549                .collect(),
1550            auth_root: format!("{:#x}", cert.auth_root),
1551        }
1552    }
1553}
1554
1555impl From<&LCV3StateSignatureRequestBody> for proto::StateSignatureResponse {
1556    fn from(body: &LCV3StateSignatureRequestBody) -> Self {
1557        proto::StateSignatureResponse {
1558            key: body.key.to_string(),
1559            light_client_state: body.state.to_string(),
1560            next_stake_table_state: body.next_stake.to_string(),
1561            auth_root: format!("{:#x}", body.auth_root),
1562            lcv3_signature: body.signature.to_string(),
1563            lcv2_signature: body.v2_signature.to_string(),
1564        }
1565    }
1566}
1567
1568impl From<&explorer::BlockDetail<SeqTypes>> for proto::ExplorerBlockDetail {
1569    fn from(block: &explorer::BlockDetail<SeqTypes>) -> Self {
1570        proto::ExplorerBlockDetail {
1571            hash: block.hash.to_string(),
1572            height: block.height,
1573            time: explorer_time(block.time),
1574            num_transactions: block.num_transactions,
1575            proposer_id: explorer_accounts(&block.proposer_id),
1576            fee_recipient: explorer_accounts(&block.fee_recipient),
1577            size: block.size,
1578            // `MonetaryValue`'s `Serialize` writes its `Display`.
1579            block_reward: block.block_reward.iter().map(ToString::to_string).collect(),
1580        }
1581    }
1582}
1583
1584impl From<&explorer::BlockSummary<SeqTypes>> for proto::ExplorerBlockSummary {
1585    fn from(block: &explorer::BlockSummary<SeqTypes>) -> Self {
1586        proto::ExplorerBlockSummary {
1587            hash: block.hash.to_string(),
1588            height: block.height,
1589            proposer_id: explorer_accounts(&block.proposer_id),
1590            num_transactions: block.num_transactions,
1591            size: block.size,
1592            time: explorer_time(block.time),
1593        }
1594    }
1595}
1596
1597impl From<&explorer::TransactionSummary<SeqTypes>> for proto::ExplorerTransactionSummary {
1598    fn from(transaction: &explorer::TransactionSummary<SeqTypes>) -> Self {
1599        proto::ExplorerTransactionSummary {
1600            hash: transaction.hash.to_string(),
1601            rollups: transaction
1602                .rollups
1603                .iter()
1604                .map(|namespace| u64::from(*namespace))
1605                .collect(),
1606            height: transaction.height,
1607            offset: transaction.offset,
1608            num_transactions: transaction.num_transactions,
1609            time: explorer_time(transaction.time),
1610        }
1611    }
1612}
1613
1614impl From<&explorer::TransactionDetail<SeqTypes>> for proto::ExplorerTransactionDetail {
1615    fn from(details: &explorer::TransactionDetail<SeqTypes>) -> Self {
1616        proto::ExplorerTransactionDetail {
1617            hash: details.hash.to_string(),
1618            height: details.height,
1619            block_confirmed: details.block_confirmed,
1620            offset: details.offset,
1621            num_transactions: details.num_transactions,
1622            size: details.size,
1623            time: explorer_time(details.time),
1624            sequencing_fees: details
1625                .sequencing_fees
1626                .iter()
1627                .map(ToString::to_string)
1628                .collect(),
1629            fee_details: details.fee_details.iter().map(Into::into).collect(),
1630        }
1631    }
1632}
1633
1634impl From<&explorer::FeeAttribution> for proto::ExplorerFeeAttribution {
1635    fn from(attribution: &explorer::FeeAttribution) -> Self {
1636        proto::ExplorerFeeAttribution {
1637            target: attribution.target.clone(),
1638            fees: attribution.fees.iter().map(ToString::to_string).collect(),
1639        }
1640    }
1641}
1642
1643impl From<&explorer::ExplorerSummary<SeqTypes>> for proto::ExplorerSummaryResponse {
1644    fn from(summary: &explorer::ExplorerSummary<SeqTypes>) -> Self {
1645        let explorer::GenesisOverview {
1646            rollups,
1647            transactions,
1648            blocks,
1649        } = summary.genesis_overview;
1650        // v1's four histogram arrays are parallel, built and trimmed together, so they zip into
1651        // one point per block.
1652        let histograms = &summary.histograms;
1653        let histograms = histograms
1654            .block_heights
1655            .iter()
1656            .zip(&histograms.block_time)
1657            .zip(&histograms.block_size)
1658            .zip(&histograms.block_transactions)
1659            .map(|(((height, block_time), block_size), block_transactions)| {
1660                proto::ExplorerHistogramPoint {
1661                    height: *height,
1662                    block_time: *block_time,
1663                    block_size: *block_size,
1664                    block_transactions: *block_transactions,
1665                }
1666            })
1667            .collect();
1668        proto::ExplorerSummaryResponse {
1669            latest_block: Some((&summary.latest_block).into()),
1670            genesis_overview: Some(proto::ExplorerGenesisOverview {
1671                rollups,
1672                transactions,
1673                blocks,
1674            }),
1675            latest_blocks: summary.latest_blocks.iter().map(Into::into).collect(),
1676            latest_transactions: summary.latest_transactions.iter().map(Into::into).collect(),
1677            histograms,
1678        }
1679    }
1680}
1681
1682impl From<&explorer::SearchResult<SeqTypes>> for proto::ExplorerSearchResponse {
1683    fn from(results: &explorer::SearchResult<SeqTypes>) -> Self {
1684        proto::ExplorerSearchResponse {
1685            blocks: results.blocks.iter().map(Into::into).collect(),
1686            transactions: results.transactions.iter().map(Into::into).collect(),
1687        }
1688    }
1689}
1690
1691/// The same `Rfc3339` format `Timestamp`'s own `Serialize` uses, so v1 and v2 agree on the string.
1692fn explorer_time(time: explorer::Timestamp) -> String {
1693    time.0
1694        .format(&time::format_description::well_known::Rfc3339)
1695        .expect("a Timestamp is built from a unix time inside RFC 3339's year range")
1696}
1697
1698/// `FeeAccount`'s `Display` drops the `0x` that its serde writes.
1699fn explorer_accounts(accounts: &[FeeAccount]) -> Vec<String> {
1700    accounts
1701        .iter()
1702        .map(|account| format!("{:#x}", account.0))
1703        .collect()
1704}