Skip to main content

espresso_node/api/
state.rs

1//! RewardApi trait implementations for espresso-node
2//!
3//! This module provides implementations for both v1::RewardApi (internal types)
4//! and v2::RewardApi (proto types), backed by the same data source.
5
6use std::time::Duration;
7
8use alloy::primitives::U256;
9use async_trait::async_trait;
10use espresso_api::{error::AvailabilityError, v1::HotShotAvailabilityApi};
11use espresso_types::{
12    NamespaceId, NamespaceProofQueryData, NsProof, SeqTypes,
13    v0::sparse_mt::KeccakNode,
14    v0_3::RewardAmount as InternalRewardAmount,
15    v0_4::{
16        RewardAccountProofV2 as InternalRewardAccountProofV2,
17        RewardAccountQueryDataV2 as InternalRewardAccountQueryData, RewardAccountV2,
18        RewardMerkleProofV2 as InternalRewardMerkleProofV2,
19    },
20    v0_6::RewardClaimError,
21};
22use futures::{StreamExt as _, join, stream::BoxStream};
23use hotshot_contract_adapter::reward::RewardClaimInput as InternalRewardClaimInput;
24use hotshot_new_protocol::message::Certificate2;
25use hotshot_query_service::{
26    Header as HsHeader,
27    availability::{
28        AvailabilityDataSource, BlockId as HsBlockId, BlockQueryData, BlockSummaryQueryData,
29        LeafId as HsLeafId, LeafQueryData, Limits as HsLimits, PayloadQueryData,
30        QueryablePayload as _, TransactionQueryData, TransactionWithProofQueryData,
31        VidCommonQueryData,
32    },
33    node::NodeDataSource as _,
34    types::HeightIndexed as _,
35};
36use hotshot_types::{
37    data::{EpochNumber, VidShare},
38    vid::avidm::AvidMShare,
39};
40use jf_merkle_tree_compat::prelude::{
41    MerkleNode as InternalMerkleNode, MerkleProof as InternalMerkleProof,
42};
43use serde_json;
44use serialization_api::v2::{
45    self, RewardAccountProofV2, RewardAccountQueryDataV2, RewardBalance, RewardBalances,
46    RewardClaimInput, RewardMerkleProofV2, RewardMerkleTreeV2Data, merkle_node,
47    reward_merkle_proof_v2::ProofType,
48};
49use tagged_base64::TaggedBase64;
50
51use super::{
52    RewardMerkleTreeDataSource, RewardMerkleTreeV2Data as InternalRewardTreeData,
53    data_source::{
54        RequestResponseDataSource as _, StakeTableDataSource, StateCertDataSource,
55        StateCertFetchingDataSource, StateSignatureDataSource,
56    },
57};
58
59/// Node API state implementation
60///
61/// This struct implements both v1::RewardApi (internal types) and v2::RewardApi (proto types).
62#[derive(Clone)]
63pub struct NodeApiStateImpl<D> {
64    data_source: D,
65    env_vars: std::sync::Arc<Vec<String>>,
66    public_node_config: Option<std::sync::Arc<crate::options::PublicNodeConfig>>,
67}
68
69impl<D> NodeApiStateImpl<D> {
70    pub fn new(data_source: D) -> Self {
71        Self {
72            data_source,
73            env_vars: std::sync::Arc::new(Vec::new()),
74            public_node_config: None,
75        }
76    }
77
78    pub fn with_env_vars(mut self, env_vars: Vec<String>) -> Self {
79        self.env_vars = std::sync::Arc::new(env_vars);
80        self
81    }
82
83    pub fn with_public_node_config(
84        mut self,
85        config: Option<crate::options::PublicNodeConfig>,
86    ) -> Self {
87        self.public_node_config = config.map(std::sync::Arc::new);
88        self
89    }
90
91    /// Convert RewardAccountProofV2 to proto
92    fn convert_reward_account_proof_v2(
93        &self,
94        proof: &InternalRewardAccountProofV2,
95    ) -> anyhow::Result<RewardAccountProofV2> {
96        Ok(RewardAccountProofV2 {
97            account: format!("{:#x}", proof.account),
98            proof: Some(self.convert_reward_merkle_proof_v2(&proof.proof)?),
99        })
100    }
101
102    /// Convert RewardMerkleProofV2 enum to proto
103    fn convert_reward_merkle_proof_v2(
104        &self,
105        proof: &InternalRewardMerkleProofV2,
106    ) -> anyhow::Result<RewardMerkleProofV2> {
107        let proof_type = match proof {
108            InternalRewardMerkleProofV2::Presence(p) => {
109                ProofType::Presence(self.convert_merkle_proof(p)?)
110            },
111            InternalRewardMerkleProofV2::Absence(p) => {
112                ProofType::Absence(self.convert_merkle_proof(p)?)
113            },
114        };
115
116        Ok(RewardMerkleProofV2 {
117            proof_type: Some(proof_type),
118        })
119    }
120
121    /// Convert MerkleProof to proto
122    fn convert_merkle_proof(
123        &self,
124        proof: &InternalMerkleProof<InternalRewardAmount, RewardAccountV2, KeccakNode, 2>,
125    ) -> anyhow::Result<v2::MerkleProof> {
126        let proof_nodes: Result<Vec<v2::MerkleNode>, _> = proof
127            .proof
128            .iter()
129            .map(|node| self.convert_merkle_node(node))
130            .collect();
131
132        Ok(v2::MerkleProof {
133            pos: TaggedBase64::new("FIELD", proof.pos.0.as_slice())
134                .map_err(|e| anyhow::anyhow!("failed to encode proof pos: {}", e))?
135                .to_string(),
136            proof: proof_nodes?,
137        })
138    }
139
140    /// Convert MerkleNode to proto (recursive)
141    fn convert_merkle_node(
142        &self,
143        node: &InternalMerkleNode<InternalRewardAmount, RewardAccountV2, KeccakNode>,
144    ) -> anyhow::Result<v2::MerkleNode> {
145        let node_type = match node {
146            InternalMerkleNode::Empty => merkle_node::NodeType::Empty(v2::Empty {
147                dummy: Some(v2::EmptyData {}),
148            }),
149            InternalMerkleNode::Leaf { pos, elem, value } => {
150                merkle_node::NodeType::Leaf(v2::Leaf {
151                    pos: TaggedBase64::new("FIELD", pos.0.as_slice())
152                        .map_err(|e| anyhow::anyhow!("failed to encode leaf pos: {}", e))?
153                        .to_string(),
154                    elem: TaggedBase64::new("FIELD", &elem.0.to_le_bytes::<32>())
155                        .map_err(|e| anyhow::anyhow!("failed to encode leaf elem: {}", e))?
156                        .to_string(),
157                    value: TaggedBase64::new("FIELD", &value.0)
158                        .map_err(|e| anyhow::anyhow!("failed to encode leaf value: {}", e))?
159                        .to_string(),
160                })
161            },
162            InternalMerkleNode::Branch { value, children } => {
163                let proto_children: Result<Vec<v2::MerkleNode>, _> = children
164                    .iter()
165                    .map(|child| self.convert_merkle_node(child))
166                    .collect();
167
168                merkle_node::NodeType::Branch(v2::Branch {
169                    value: TaggedBase64::new("FIELD", &value.0)
170                        .map_err(|e| anyhow::anyhow!("failed to encode branch value: {}", e))?
171                        .to_string(),
172                    children: proto_children?,
173                })
174            },
175            InternalMerkleNode::ForgettenSubtree { value } => {
176                merkle_node::NodeType::ForgottenSubtree(v2::ForgottenSubtree {
177                    value: TaggedBase64::new("FIELD", &value.0)
178                        .map_err(|e| {
179                            anyhow::anyhow!("failed to encode forgotten subtree value: {}", e)
180                        })?
181                        .to_string(),
182                })
183            },
184        };
185
186        Ok(v2::MerkleNode {
187            node_type: Some(node_type),
188        })
189    }
190}
191
192// ============================================================================
193// ApiSerializations implementation (conversion layer)
194// ============================================================================
195
196impl<D> serialization_api::ApiSerializations for NodeApiStateImpl<D>
197where
198    D: std::ops::Deref + Send + Sync + 'static,
199    D::Target: RewardMerkleTreeDataSource + Send + Sync,
200{
201    // Request types
202    type Address = alloy::primitives::Address;
203
204    // Response types (internal types)
205    type RewardClaimInput = InternalRewardClaimInput;
206    type RewardBalance = U256;
207    type RewardAccountQueryData = InternalRewardAccountQueryData;
208    type RewardBalances = (Vec<(RewardAccountV2, InternalRewardAmount)>, u64); // (amounts, total)
209    type RewardMerkleTreeData = InternalRewardTreeData;
210
211    // Data API types
212    type NamespaceProof = espresso_types::NamespaceProofQueryData;
213    type IncorrectEncodingProof = espresso_types::v0_3::AvidMIncorrectEncodingNsProof;
214
215    // Consensus API types
216    type StateCertificate = espresso_types::StateCertQueryDataV2<espresso_types::SeqTypes>;
217    type StakeTable = Vec<hotshot_types::PeerConfig<espresso_types::SeqTypes>>;
218
219    // Helper conversion types
220    type PeerConfig = hotshot_types::PeerConfig<espresso_types::SeqTypes>;
221    type LightClientCert = hotshot_types::simple_certificate::LightClientStateUpdateCertificateV2<
222        espresso_types::SeqTypes,
223    >;
224    type NsProof = espresso_types::NsProof;
225
226    fn deserialize_address(&self, s: &str) -> anyhow::Result<Self::Address> {
227        s.parse()
228            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", s))
229    }
230
231    // Serialize internal types → proto types
232    fn serialize_reward_claim_input(
233        &self,
234        address: &str,
235        value: &Self::RewardClaimInput,
236    ) -> anyhow::Result<RewardClaimInput> {
237        // Serialize auth_data directly - it serializes to a hex string via serde
238        let auth_data = serde_json::to_string(&value.auth_data)
239            .map_err(|e| anyhow::anyhow!("failed to serialize auth_data: {}", e))?
240            // Remove quotes added by JSON string serialization
241            .trim_matches('"')
242            .to_string();
243
244        Ok(RewardClaimInput {
245            address: address.to_string(),
246            lifetime_rewards: format!("{:#x}", value.lifetime_rewards), // Hex for contract
247            auth_data,
248        })
249    }
250
251    fn serialize_reward_balance(
252        &self,
253        value: &Self::RewardBalance,
254    ) -> anyhow::Result<RewardBalance> {
255        Ok(RewardBalance {
256            amount: value.to_string(), // Decimal string
257        })
258    }
259
260    fn serialize_reward_account_query_data(
261        &self,
262        value: &Self::RewardAccountQueryData,
263    ) -> anyhow::Result<RewardAccountQueryDataV2> {
264        // Convert balance to decimal string
265        let balance = value.balance.to_string();
266
267        // Convert the proof
268        let proof = Some(self.convert_reward_account_proof_v2(&value.proof)?);
269
270        Ok(RewardAccountQueryDataV2 { balance, proof })
271    }
272
273    fn serialize_reward_balances(
274        &self,
275        value: &Self::RewardBalances,
276    ) -> anyhow::Result<RewardBalances> {
277        let (amounts_vec, total) = value;
278
279        // Convert each account/amount pair to proto format
280        let amounts = amounts_vec
281            .iter()
282            .map(|(account, amount)| serialization_api::v2::RewardAmount {
283                address: format!("{:#x}", account.0),
284                amount: amount.0.to_string(), // Decimal string
285            })
286            .collect();
287
288        Ok(RewardBalances {
289            amounts,
290            total: *total,
291        })
292    }
293
294    fn serialize_reward_merkle_tree_data(
295        &self,
296        value: &Self::RewardMerkleTreeData,
297    ) -> anyhow::Result<RewardMerkleTreeV2Data> {
298        let bytes = bincode::serialize(value)
299            .map_err(|e| anyhow::anyhow!("failed to serialize RewardMerkleTreeV2Data: {}", e))?;
300        Ok(RewardMerkleTreeV2Data { data: bytes })
301    }
302
303    // Data API serialization methods
304
305    fn serialize_namespace_proof(
306        &self,
307        value: &Self::NamespaceProof,
308    ) -> anyhow::Result<v2::NamespaceProofResponse> {
309        // Serialize each transaction field explicitly using base64_bytes
310        let transactions: Vec<v2::Transaction> = value
311            .transactions
312            .iter()
313            .map(|tx| -> anyhow::Result<v2::Transaction> {
314                let mut payload_bytes = Vec::new();
315                base64_bytes::serialize(
316                    &tx.payload,
317                    &mut serde_json::Serializer::new(&mut payload_bytes),
318                )
319                .map_err(|e| anyhow::anyhow!("failed to serialize payload: {}", e))?;
320                // Convert to string and remove quotes added by JSON serializer
321                let payload_str = String::from_utf8(payload_bytes)?
322                    .trim_matches('"')
323                    .to_string();
324
325                Ok(v2::Transaction {
326                    namespace: tx.namespace.0,
327                    payload: payload_str,
328                })
329            })
330            .collect::<anyhow::Result<Vec<_>>>()?;
331
332        let proof = value
333            .proof
334            .as_ref()
335            .map(|p| self.serialize_ns_proof(p))
336            .transpose()?;
337
338        Ok(serialization_api::v2::NamespaceProofResponse {
339            transactions,
340            proof,
341        })
342    }
343
344    fn serialize_incorrect_encoding_proof(
345        &self,
346        value: &Self::IncorrectEncodingProof,
347    ) -> anyhow::Result<v2::IncorrectEncodingProofResponse> {
348        // Serialize the VID proof to JSON string
349        let proof_data = serde_json::to_string(&value.0)?;
350        Ok(serialization_api::v2::IncorrectEncodingProofResponse {
351            proof: Some(v2::AvidMIncorrectEncodingNsProof { proof_data }),
352        })
353    }
354
355    // Consensus API serialization methods
356
357    fn serialize_state_certificate(
358        &self,
359        value: &Self::StateCertificate,
360    ) -> anyhow::Result<v2::StateCertificateResponse> {
361        let certificate = self.serialize_light_client_cert(&value.0)?;
362
363        Ok(serialization_api::v2::StateCertificateResponse {
364            certificate: Some(certificate),
365        })
366    }
367
368    fn serialize_stake_table(
369        &self,
370        value: &Self::StakeTable,
371    ) -> anyhow::Result<v2::StakeTableResponse> {
372        let peers: Result<Vec<_>, _> = value
373            .iter()
374            .map(|peer| self.serialize_peer_config(peer))
375            .collect();
376
377        Ok(serialization_api::v2::StakeTableResponse { peers: peers? })
378    }
379
380    fn serialize_peer_config(&self, peer: &Self::PeerConfig) -> anyhow::Result<v2::PeerConfig> {
381        let stake_table_entry = v2::StakeTableEntry {
382            stake_key: Some(v2::BlsPublicKey {
383                key: peer.stake_table_entry.stake_key.to_string(),
384            }),
385            stake_amount: peer.stake_table_entry.stake_amount.to_string(),
386        };
387
388        let state_ver_key = v2::SchnorrPublicKey {
389            key: peer.state_ver_key.to_string(),
390        };
391
392        let connect_info = peer.connect_info.as_ref().map(|info| {
393            let p2p_addr = match &info.p2p_addr {
394                hotshot_types::addr::NetAddr::Inet(ip, port) => v2::NetAddr {
395                    addr_type: Some(v2::net_addr::AddrType::Inet(v2::InetAddr {
396                        host: match ip {
397                            std::net::IpAddr::V4(_) => ip.to_string(),
398                            std::net::IpAddr::V6(_) => format!("[{ip}]"),
399                        },
400                        port: *port as u32,
401                    })),
402                },
403                hotshot_types::addr::NetAddr::Name(name, port) => v2::NetAddr {
404                    addr_type: Some(v2::net_addr::AddrType::Name(v2::NameAddr {
405                        name: name.to_string(),
406                        port: *port as u32,
407                    })),
408                },
409            };
410
411            v2::PeerConnectInfo {
412                x25519_key: info.x25519_key.to_string(),
413                p2p_addr: Some(p2p_addr),
414            }
415        });
416
417        Ok(v2::PeerConfig {
418            stake_table_entry: Some(stake_table_entry),
419            state_ver_key: Some(state_ver_key),
420            connect_info,
421        })
422    }
423
424    fn serialize_light_client_cert(
425        &self,
426        cert: &Self::LightClientCert,
427    ) -> anyhow::Result<v2::LightClientStateUpdateCertificateV2> {
428        let signatures: Result<Vec<_>, anyhow::Error> = cert
429            .signatures
430            .iter()
431            .map(
432                |(key, lcv3_sig, lcv2_sig)| -> anyhow::Result<v2::StateSignatureTuple> {
433                    Ok(v2::StateSignatureTuple {
434                        state_signature_key: Some(v2::SchnorrPublicKey {
435                            key: key.to_string(),
436                        }),
437                        lcv3_signature: lcv3_sig.to_string(),
438                        lcv2_signature: lcv2_sig.to_string(),
439                    })
440                },
441            )
442            .collect();
443
444        Ok(v2::LightClientStateUpdateCertificateV2 {
445            epoch: cert.epoch.u64(),
446            light_client_state: cert.light_client_state.to_string(),
447            next_stake_table_state: cert.next_stake_table_state.to_string(),
448            signatures: signatures?,
449            auth_root: cert.auth_root.to_string(),
450        })
451    }
452
453    fn serialize_ns_proof(&self, proof: &Self::NsProof) -> anyhow::Result<v2::NsProof> {
454        let proof_version = match proof {
455            NsProof::V0(advz_proof) => {
456                // Serialize the inner fields directly
457                let json = serde_json::json!({
458                    "ns_index": advz_proof.ns_index,
459                    "ns_payload": advz_proof.ns_payload,
460                    "ns_proof": advz_proof.ns_proof,
461                });
462                v2::ns_proof::ProofVersion::V0(serde_json::from_value(json)?)
463            },
464            NsProof::V1(avidm_proof) => {
465                // Serialize ns_payload using base64_bytes
466                let mut ns_payload_bytes = Vec::new();
467                base64_bytes::serialize(
468                    &avidm_proof.0.ns_payload,
469                    &mut serde_json::Serializer::new(&mut ns_payload_bytes),
470                )
471                .map_err(|e| anyhow::anyhow!("failed to serialize ns_payload: {}", e))?;
472                let ns_payload_str = String::from_utf8(ns_payload_bytes)?
473                    .trim_matches('"')
474                    .to_string();
475
476                v2::ns_proof::ProofVersion::V1(v2::AvidMNsProof {
477                    ns_index: avidm_proof.0.ns_index as u64,
478                    ns_payload: ns_payload_str,
479                    ns_proof: avidm_proof.0.ns_proof.to_string(),
480                })
481            },
482            NsProof::V1IncorrectEncoding(incorrect_proof) => {
483                // Serialize the whole proof to JSON string
484                v2::ns_proof::ProofVersion::V1IncorrectEncoding(v2::AvidMIncorrectEncodingNsProof {
485                    proof_data: serde_json::to_string(&incorrect_proof.0)?,
486                })
487            },
488            NsProof::V2(gf2_proof) => {
489                // Serialize ns_payload using base64_bytes
490                let mut ns_payload_bytes = Vec::new();
491                base64_bytes::serialize(
492                    &gf2_proof.0.ns_payload,
493                    &mut serde_json::Serializer::new(&mut ns_payload_bytes),
494                )
495                .map_err(|e| anyhow::anyhow!("failed to serialize ns_payload: {}", e))?;
496                let ns_payload_str = String::from_utf8(ns_payload_bytes)?
497                    .trim_matches('"')
498                    .to_string();
499
500                v2::ns_proof::ProofVersion::V2(v2::AvidmGf2NsProof {
501                    ns_index: gf2_proof.0.ns_index as u64,
502                    ns_payload: ns_payload_str,
503                    ns_proof: gf2_proof.0.ns_proof.to_string(),
504                })
505            },
506        };
507
508        Ok(v2::NsProof {
509            proof_version: Some(proof_version),
510        })
511    }
512}
513
514// ============================================================================
515// RewardApiV2 implementation (business logic)
516// ============================================================================
517
518#[async_trait]
519impl<D> espresso_api::v2::RewardApi for NodeApiStateImpl<D>
520where
521    D: std::ops::Deref + Clone + Send + Sync + 'static,
522    D::Target: RewardMerkleTreeDataSource + Send + Sync,
523{
524    async fn get_reward_claim_input(
525        &self,
526        address: Self::Address,
527    ) -> anyhow::Result<Self::RewardClaimInput> {
528        // Load the latest reward account proof from the data source
529        let proof = self
530            .data_source
531            .load_latest_reward_account_proof_v2(address.into())
532            .await
533            .map_err(|err| {
534                anyhow::anyhow!(
535                    "failed to load latest reward account {:?}: {}",
536                    address,
537                    err
538                )
539            })?;
540
541        // Convert the proof to reward claim input and return internal type
542        proof.to_reward_claim_input().map_err(|err| match err {
543            RewardClaimError::ZeroRewardError => {
544                anyhow::anyhow!("zero reward balance for {:?}", address)
545            },
546            RewardClaimError::ProofConversionError(e) => {
547                anyhow::anyhow!("failed to create solidity proof for {:?}: {}", address, e)
548            },
549        })
550    }
551
552    async fn get_reward_balance(
553        &self,
554        address: Self::Address,
555    ) -> anyhow::Result<Self::RewardBalance> {
556        // Load the latest reward account proof from the data source
557        let proof = self
558            .data_source
559            .load_latest_reward_account_proof_v2(address.into())
560            .await
561            .map_err(|err| {
562                anyhow::anyhow!(
563                    "failed to load latest reward account {:?}: {}",
564                    address,
565                    err
566                )
567            })?;
568
569        // Return internal balance type
570        Ok(proof.balance)
571    }
572
573    async fn get_reward_account_proof(
574        &self,
575        address: Self::Address,
576    ) -> anyhow::Result<Self::RewardAccountQueryData> {
577        // Load the latest reward account proof from the data source and return internal type
578        self.data_source
579            .load_latest_reward_account_proof_v2(address.into())
580            .await
581            .map_err(|err| {
582                anyhow::anyhow!(
583                    "failed to load latest reward account proof for {:?}: {}",
584                    address,
585                    err
586                )
587            })
588    }
589
590    async fn get_reward_balances(
591        &self,
592        height: u64,
593        offset: u64,
594        limit: u64,
595    ) -> anyhow::Result<Self::RewardBalances> {
596        // Validate limit (from reward.toml: limit <= 10000)
597        if limit > 10000 {
598            return Err(anyhow::anyhow!(
599                "limit {} exceeds maximum allowed value of 10000",
600                limit
601            ));
602        }
603
604        // Load the merkle tree at the given height
605        let tree_bytes = self.data_source.load_tree(height).await.map_err(|err| {
606            anyhow::anyhow!("failed to load reward tree at height {}: {}", height, err)
607        })?;
608
609        // Deserialize the tree into internal format
610        let tree_data: InternalRewardTreeData =
611            bincode::deserialize(&tree_bytes).map_err(|err| {
612                anyhow::anyhow!(
613                    "failed to deserialize RewardMerkleTreeV2Data at height {}: {}",
614                    height,
615                    err
616                )
617            })?;
618
619        let offset_usize = offset as usize;
620        let limit_usize = limit as usize;
621        let total = tree_data.balances.len() as u64;
622
623        // Validate offset is within bounds
624        if offset_usize > tree_data.balances.len() {
625            return Err(anyhow::anyhow!("offset {} out of bounds", offset));
626        }
627
628        let end = std::cmp::min(offset_usize + limit_usize, tree_data.balances.len());
629        let slice = &tree_data.balances[offset_usize..end];
630
631        // Reverse order (matching Tide implementation) and return internal type with total
632        let reversed: Vec<_> = slice.iter().rev().copied().collect();
633        Ok((reversed, total))
634    }
635
636    async fn get_reward_merkle_tree_v2(
637        &self,
638        height: u64,
639    ) -> anyhow::Result<Self::RewardMerkleTreeData> {
640        // Load the raw merkle tree bytes
641        let tree_bytes = self.data_source.load_tree(height).await.map_err(|err| {
642            anyhow::anyhow!("failed to load reward tree at height {}: {}", height, err)
643        })?;
644
645        // Deserialize and return internal type
646        bincode::deserialize(&tree_bytes).map_err(|err| {
647            anyhow::anyhow!(
648                "failed to deserialize RewardMerkleTreeV2Data at height {}: {}",
649                height,
650                err
651            )
652        })
653    }
654}
655
656// ============================================================================
657// RewardApiV1 implementation (internal types, no proto conversion)
658// ============================================================================
659
660#[async_trait]
661impl<D> espresso_api::v1::RewardApi for NodeApiStateImpl<D>
662where
663    D: RewardMerkleTreeDataSource,
664{
665    type RewardClaimInput = InternalRewardClaimInput;
666    type RewardBalance = InternalRewardAmount;
667    type RewardAccountQueryData = InternalRewardAccountQueryData;
668    type RewardAmounts = Vec<(alloy::primitives::Address, InternalRewardAmount)>;
669    type RewardMerkleTreeData = Vec<u8>;
670
671    async fn get_reward_claim_input(
672        &self,
673        block_height: u64,
674        address: String,
675    ) -> anyhow::Result<Self::RewardClaimInput> {
676        // Parse the Ethereum address
677        let addr: alloy::primitives::Address = address
678            .parse()
679            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
680
681        // Load the reward account proof from the data source
682        let proof = self
683            .data_source
684            .load_reward_account_proof_v2(block_height, addr.into())
685            .await
686            .map_err(|err| {
687                anyhow::anyhow!(
688                    "failed to load reward account {} at height {}: {}",
689                    address,
690                    block_height,
691                    err
692                )
693            })?;
694
695        // Convert the proof to reward claim input (internal type)
696        let claim_input = proof.to_reward_claim_input().map_err(|err| match err {
697            RewardClaimError::ZeroRewardError => {
698                anyhow::anyhow!(
699                    "zero reward balance for {} at height {}",
700                    address,
701                    block_height
702                )
703            },
704            RewardClaimError::ProofConversionError(e) => {
705                anyhow::anyhow!(
706                    "failed to create solidity proof for {} at height {}: {}",
707                    address,
708                    block_height,
709                    e
710                )
711            },
712        })?;
713
714        Ok(claim_input)
715    }
716
717    async fn get_reward_balance(
718        &self,
719        height: u64,
720        address: String,
721    ) -> anyhow::Result<Self::RewardBalance> {
722        // Parse the Ethereum address
723        let addr: alloy::primitives::Address = address
724            .parse()
725            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
726
727        // Load the reward account proof from the data source
728        let proof = self
729            .data_source
730            .load_reward_account_proof_v2(height, addr.into())
731            .await
732            .map_err(|err| {
733                anyhow::anyhow!(
734                    "failed to load reward account {} at height {}: {}",
735                    address,
736                    height,
737                    err
738                )
739            })?;
740
741        Ok(InternalRewardAmount(proof.balance))
742    }
743
744    async fn get_latest_reward_balance(
745        &self,
746        address: String,
747    ) -> anyhow::Result<Self::RewardBalance> {
748        let addr: alloy::primitives::Address = address
749            .parse()
750            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
751
752        let proof = self
753            .data_source
754            .load_latest_reward_account_proof_v2(addr.into())
755            .await
756            .map_err(|err| {
757                anyhow::anyhow!("failed to load latest reward account {}: {}", address, err)
758            })?;
759
760        Ok(InternalRewardAmount(proof.balance))
761    }
762
763    async fn get_reward_account_proof(
764        &self,
765        height: u64,
766        address: String,
767    ) -> anyhow::Result<Self::RewardAccountQueryData> {
768        // Parse the Ethereum address
769        let addr: alloy::primitives::Address = address
770            .parse()
771            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
772
773        // Load and return the reward account proof directly (internal type)
774        let proof = self
775            .data_source
776            .load_reward_account_proof_v2(height, addr.into())
777            .await
778            .map_err(|err| {
779                anyhow::anyhow!(
780                    "failed to load reward account {} at height {}: {}",
781                    address,
782                    height,
783                    err
784                )
785            })?;
786
787        Ok(proof)
788    }
789
790    async fn get_latest_reward_account_proof(
791        &self,
792        address: String,
793    ) -> anyhow::Result<Self::RewardAccountQueryData> {
794        // Parse the Ethereum address
795        let addr: alloy::primitives::Address = address
796            .parse()
797            .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
798
799        // Load and return the latest reward account proof directly (internal type)
800        let proof = self
801            .data_source
802            .load_latest_reward_account_proof_v2(addr.into())
803            .await
804            .map_err(|err| {
805                anyhow::anyhow!("failed to load latest reward account {}: {}", address, err)
806            })?;
807
808        Ok(proof)
809    }
810
811    async fn get_reward_amounts(
812        &self,
813        height: u64,
814        offset: u64,
815        limit: u64,
816    ) -> anyhow::Result<Self::RewardAmounts> {
817        // Validate limit (from reward.toml: limit <= 10000)
818        if limit > 10000 {
819            return Err(anyhow::anyhow!(
820                "limit {} exceeds maximum allowed value of 10000",
821                limit
822            ));
823        }
824
825        // Load the merkle tree at the given height
826        let tree_bytes = self.data_source.load_tree(height).await.map_err(|err| {
827            anyhow::anyhow!("failed to load reward tree at height {}: {}", height, err)
828        })?;
829
830        // Deserialize the tree into internal format
831        let tree_data: InternalRewardTreeData =
832            bincode::deserialize(&tree_bytes).map_err(|err| {
833                anyhow::anyhow!(
834                    "failed to deserialize RewardMerkleTreeV2Data at height {}: {}",
835                    height,
836                    err
837                )
838            })?;
839
840        let offset_usize = offset as usize;
841        let limit_usize = limit as usize;
842
843        // Validate offset is within bounds
844        if offset_usize > tree_data.balances.len() {
845            return Err(anyhow::anyhow!("offset {} out of bounds", offset));
846        }
847
848        let end = std::cmp::min(offset_usize + limit_usize, tree_data.balances.len());
849        let slice = &tree_data.balances[offset_usize..end];
850
851        let result: Vec<(alloy::primitives::Address, InternalRewardAmount)> = slice
852            .iter()
853            .rev()
854            .map(|(account, amount)| (account.0, *amount))
855            .collect();
856
857        Ok(result)
858    }
859
860    async fn get_reward_merkle_tree_v2(
861        &self,
862        height: u64,
863    ) -> anyhow::Result<Self::RewardMerkleTreeData> {
864        self.data_source.load_tree(height).await.map_err(|err| {
865            anyhow::anyhow!("failed to load reward tree at height {}: {}", height, err)
866        })
867    }
868}
869
870// ============================================================================
871// v2::DataApi implementation
872// ============================================================================
873
874#[async_trait]
875impl<D> espresso_api::v2::DataApi for NodeApiStateImpl<D>
876where
877    D: std::ops::Deref + Clone + Send + Sync + 'static,
878    D::Target: RewardMerkleTreeDataSource
879        + hotshot_query_service::availability::AvailabilityDataSource<espresso_types::SeqTypes>
880        + hotshot_query_service::node::NodeDataSource<espresso_types::SeqTypes>
881        + super::data_source::RequestResponseDataSource<espresso_types::SeqTypes>
882        + Sync
883        + Send,
884{
885    async fn get_namespace_proof(
886        &self,
887        namespace_id: u64,
888        block_height: u64,
889    ) -> anyhow::Result<Self::NamespaceProof> {
890        let ns_id = NamespaceId(namespace_id);
891        let block_id = HsBlockId::Number(block_height as usize);
892
893        // Fetch block and VID common data concurrently
894        let ds = &*self.data_source;
895        let timeout = std::time::Duration::from_millis(500);
896        let (block_fetch, vid_fetch) = join!(ds.get_block(block_id), ds.get_vid_common(block_id));
897        let (block_opt, vid_opt) = join!(
898            block_fetch.with_timeout(timeout),
899            vid_fetch.with_timeout(timeout)
900        );
901
902        let block = block_opt.ok_or_else(|| anyhow::anyhow!("block {} not found", block_height))?;
903        let vid_common = vid_opt.ok_or_else(|| {
904            anyhow::anyhow!("VID common data for block {} not found", block_height)
905        })?;
906
907        // Generate namespace proof
908        let ns_table = block.payload().ns_table();
909        let ns_index = ns_table.find_ns_id(&ns_id).ok_or_else(|| {
910            anyhow::anyhow!(
911                "namespace {} not present in block {}",
912                namespace_id,
913                block_height
914            )
915        })?;
916
917        let proof =
918            NsProof::new(block.payload(), &ns_index, vid_common.common()).ok_or_else(|| {
919                anyhow::anyhow!(
920                    "failed to generate namespace proof for block {}",
921                    block_height
922                )
923            })?;
924
925        let transactions = proof.export_all_txs(&ns_id);
926
927        Ok(espresso_types::NamespaceProofQueryData {
928            transactions,
929            proof: Some(proof),
930        })
931    }
932
933    async fn get_namespace_proof_range(
934        &self,
935        namespace_id: u64,
936        from: u64,
937        until: u64,
938    ) -> anyhow::Result<Vec<Self::NamespaceProof>> {
939        let ns_id = NamespaceId(namespace_id);
940
941        // Validate range
942        if until <= from {
943            return Err(anyhow::anyhow!(
944                "invalid range: until ({}) must be greater than from ({})",
945                until,
946                from
947            ));
948        }
949
950        let range_size = until - from;
951        const MAX_RANGE: u64 = 100; // Match limit from availability.toml
952        if range_size > MAX_RANGE {
953            return Err(anyhow::anyhow!(
954                "range too large: {} blocks (max {})",
955                range_size,
956                MAX_RANGE
957            ));
958        }
959
960        // Fetch blocks and VID common data for the range
961        let (blocks_stream, vids_stream) = join!(
962            self.data_source
963                .get_block_range(from as usize..until as usize),
964            self.data_source
965                .get_vid_common_range(from as usize..until as usize)
966        );
967
968        let blocks: Vec<_> = blocks_stream
969            .then(|block| async move { block.resolve().await })
970            .collect()
971            .await;
972        let vids: Vec<_> = vids_stream
973            .then(|vid| async move { vid.resolve().await })
974            .collect()
975            .await;
976
977        if blocks.len() != vids.len() {
978            return Err(anyhow::anyhow!(
979                "mismatch between blocks and VID common data"
980            ));
981        }
982
983        // Generate proofs for each block
984        let mut proofs = Vec::new();
985
986        for (block, vid) in blocks.into_iter().zip(vids) {
987            let ns_table = block.payload().ns_table();
988
989            // Check if namespace exists in this block
990            if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
991                if let Some(proof) = NsProof::new(block.payload(), &ns_index, vid.common()) {
992                    let transactions = proof.export_all_txs(&ns_id);
993                    proofs.push(espresso_types::NamespaceProofQueryData {
994                        transactions,
995                        proof: Some(proof),
996                    });
997                } else {
998                    // Failed to generate proof - return empty result for this block
999                    proofs.push(espresso_types::NamespaceProofQueryData {
1000                        transactions: vec![],
1001                        proof: None,
1002                    });
1003                }
1004            } else {
1005                // Namespace not present in this block
1006                proofs.push(espresso_types::NamespaceProofQueryData {
1007                    transactions: vec![],
1008                    proof: None,
1009                });
1010            }
1011        }
1012
1013        Ok(proofs)
1014    }
1015
1016    async fn get_incorrect_encoding_proof(
1017        &self,
1018        namespace_id: u64,
1019        block_height: u64,
1020    ) -> anyhow::Result<Self::IncorrectEncodingProof> {
1021        let ns_id = NamespaceId(namespace_id);
1022        let block_id = HsBlockId::Number(block_height as usize);
1023
1024        let ds = &*self.data_source;
1025        let timeout = std::time::Duration::from_millis(500);
1026        let (block_fetch, vid_fetch) = join!(ds.get_block(block_id), ds.get_vid_common(block_id));
1027        let (block, vid_common) = join!(
1028            block_fetch.with_timeout(timeout),
1029            vid_fetch.with_timeout(timeout)
1030        );
1031
1032        let block = block.ok_or_else(|| anyhow::anyhow!("block {} not found", block_height))?;
1033        let vid_common = vid_common.ok_or_else(|| {
1034            anyhow::anyhow!("VID common data for block {} not found", block_height)
1035        })?;
1036
1037        let ns_table = block.payload().ns_table();
1038        let ns_index = ns_table.find_ns_id(&ns_id).ok_or_else(|| {
1039            anyhow::anyhow!(
1040                "namespace {} not present in block {}",
1041                namespace_id,
1042                block_height
1043            )
1044        })?;
1045
1046        if NsProof::new(block.payload(), &ns_index, vid_common.common()).is_some() {
1047            return Err(anyhow::anyhow!(
1048                "block {} was correctly encoded",
1049                block_height
1050            ));
1051        }
1052
1053        // Block has incorrect encoding — fetch VID shares to construct the proof.
1054        let vid_shares_future = ds
1055            .request_vid_shares(block_height, vid_common.clone(), Duration::from_secs(40))
1056            .await;
1057        let mut vid_shares = vid_shares_future
1058            .await
1059            .map_err(|e| anyhow::anyhow!("failed to fetch VID shares: {e:#}"))?;
1060
1061        if let Ok(local_share) = ds.vid_share(block_height as usize).await {
1062            vid_shares.push(local_share);
1063        }
1064
1065        let avidm_shares: Vec<AvidMShare> = vid_shares
1066            .into_iter()
1067            .filter_map(|s| {
1068                if let VidShare::V1(s) = s {
1069                    Some(s)
1070                } else {
1071                    None
1072                }
1073            })
1074            .collect();
1075
1076        match NsProof::v1_1_new_with_incorrect_encoding(
1077            &avidm_shares,
1078            ns_table,
1079            &ns_index,
1080            &vid_common.payload_hash(),
1081            vid_common.common(),
1082        ) {
1083            Some(NsProof::V1IncorrectEncoding(proof)) => Ok(proof),
1084            _ => Err(anyhow::anyhow!(
1085                "failed to generate incorrect encoding proof"
1086            )),
1087        }
1088    }
1089}
1090
1091// ============================================================================
1092// v2::ConsensusApi implementation
1093// ============================================================================
1094
1095#[async_trait]
1096impl<D> espresso_api::v2::ConsensusApi for NodeApiStateImpl<D>
1097where
1098    D: std::ops::Deref + Clone + Send + Sync + 'static,
1099    D::Target: RewardMerkleTreeDataSource
1100        + super::data_source::StateCertDataSource
1101        + super::data_source::StateCertFetchingDataSource<espresso_types::SeqTypes>
1102        + super::data_source::StakeTableDataSource<espresso_types::SeqTypes>
1103        + Send
1104        + Sync,
1105{
1106    async fn get_state_certificate(&self, epoch: u64) -> anyhow::Result<Self::StateCertificate> {
1107        let ds = &*self.data_source;
1108
1109        // Try to get from local storage first
1110        let state_cert = ds.get_state_cert_by_epoch(epoch).await?;
1111
1112        let cert = match state_cert {
1113            Some(cert) => cert,
1114            None => {
1115                // Not found locally, try to fetch from peers
1116                const TIMEOUT: Duration = Duration::from_secs(40);
1117                let cert = ds.request_state_cert(epoch, TIMEOUT).await.map_err(|e| {
1118                    anyhow::anyhow!("failed to fetch state cert for epoch {}: {}", epoch, e)
1119                })?;
1120
1121                // Store the fetched certificate
1122                ds.insert_state_cert(epoch, cert.clone()).await?;
1123
1124                cert
1125            },
1126        };
1127
1128        Ok(espresso_types::StateCertQueryDataV2(cert))
1129    }
1130
1131    async fn get_stake_table(&self, epoch: u64) -> anyhow::Result<Self::StakeTable> {
1132        let ds = &*self.data_source;
1133        ds.get_stake_table(Some(EpochNumber::new(epoch))).await
1134    }
1135}
1136
1137// ============================================================================
1138// v1::AvailabilityApi implementation
1139// ============================================================================
1140
1141#[async_trait]
1142impl<D> espresso_api::v1::AvailabilityApi for NodeApiStateImpl<D>
1143where
1144    D: std::ops::Deref + Clone + Send + Sync + 'static,
1145    D::Target: RewardMerkleTreeDataSource
1146        + hotshot_query_service::availability::AvailabilityDataSource<espresso_types::SeqTypes>
1147        + hotshot_query_service::node::NodeDataSource<espresso_types::SeqTypes>
1148        + super::data_source::RequestResponseDataSource<espresso_types::SeqTypes>
1149        + super::data_source::StateCertDataSource
1150        + super::data_source::StateCertFetchingDataSource<espresso_types::SeqTypes>
1151        + Send
1152        + Sync,
1153{
1154    type NamespaceProofQueryData = espresso_types::NamespaceProofQueryData;
1155    type IncorrectEncodingProof = espresso_types::v0_3::AvidMIncorrectEncodingNsProof;
1156    type StateCertQueryDataV1 = espresso_types::StateCertQueryDataV1<espresso_types::SeqTypes>;
1157    type StateCertQueryDataV2 = espresso_types::StateCertQueryDataV2<espresso_types::SeqTypes>;
1158
1159    async fn get_namespace_proof(
1160        &self,
1161        block_id: espresso_api::v1::availability::BlockId,
1162        namespace: u32,
1163    ) -> anyhow::Result<Option<Self::NamespaceProofQueryData>> {
1164        let ns_id = NamespaceId::from(namespace);
1165
1166        // Convert v1 BlockId to hotshot BlockId
1167        let hs_block_id = match block_id {
1168            espresso_api::v1::availability::BlockId::Height(h) => HsBlockId::Number(h as usize),
1169            espresso_api::v1::availability::BlockId::Hash(h) => {
1170                let hash = h
1171                    .parse()
1172                    .map_err(|_| anyhow::anyhow!("invalid block hash: {}", h))?;
1173                HsBlockId::Hash(hash)
1174            },
1175            espresso_api::v1::availability::BlockId::PayloadHash(h) => {
1176                let payload_hash = h
1177                    .parse()
1178                    .map_err(|_| anyhow::anyhow!("invalid payload hash: {}", h))?;
1179                HsBlockId::PayloadHash(payload_hash)
1180            },
1181        };
1182
1183        // Fetch block and VID common data
1184        let ds = &*self.data_source;
1185        let timeout = std::time::Duration::from_millis(500);
1186        let (block_fetch, vid_fetch) =
1187            join!(ds.get_block(hs_block_id), ds.get_vid_common(hs_block_id));
1188        let (block, vid_common) = join!(
1189            block_fetch.with_timeout(timeout),
1190            vid_fetch.with_timeout(timeout)
1191        );
1192
1193        let Some(block) = block else {
1194            return Ok(None);
1195        };
1196        let Some(vid_common) = vid_common else {
1197            return Ok(None);
1198        };
1199
1200        // Check if namespace is present
1201        let ns_table = block.payload().ns_table();
1202        let Some(ns_index) = ns_table.find_ns_id(&ns_id) else {
1203            return Ok(None);
1204        };
1205
1206        // Generate namespace proof
1207        let Some(proof) = NsProof::new(block.payload(), &ns_index, vid_common.common()) else {
1208            // Failed to generate proof - namespace exists but proof generation failed
1209            return Ok(Some(espresso_types::NamespaceProofQueryData {
1210                transactions: vec![],
1211                proof: None,
1212            }));
1213        };
1214
1215        let transactions = proof.export_all_txs(&ns_id);
1216
1217        Ok(Some(espresso_types::NamespaceProofQueryData {
1218            transactions,
1219            proof: Some(proof),
1220        }))
1221    }
1222
1223    async fn get_namespace_proof_range(
1224        &self,
1225        from: u64,
1226        until: u64,
1227        namespace: u32,
1228    ) -> anyhow::Result<Vec<Self::NamespaceProofQueryData>> {
1229        let ns_id = NamespaceId::from(namespace);
1230
1231        // Validate range
1232        if until <= from {
1233            return Err(bad_request(format!(
1234                "invalid range: until ({}) must be greater than from ({})",
1235                until, from
1236            )));
1237        }
1238
1239        let range_size = until - from;
1240        const MAX_RANGE: u64 = 100;
1241        if range_size > MAX_RANGE {
1242            return Err(range_exceeded(format!(
1243                "range too large: {} blocks (max {})",
1244                range_size, MAX_RANGE
1245            )));
1246        }
1247
1248        // Fetch blocks and VID common data for the range
1249        let (blocks_stream, vids_stream) = join!(
1250            self.data_source
1251                .get_block_range(from as usize..until as usize),
1252            self.data_source
1253                .get_vid_common_range(from as usize..until as usize)
1254        );
1255
1256        let blocks: Vec<_> = blocks_stream
1257            .then(|block| async move { block.resolve().await })
1258            .collect()
1259            .await;
1260        let vids: Vec<_> = vids_stream
1261            .then(|vid| async move { vid.resolve().await })
1262            .collect()
1263            .await;
1264
1265        if blocks.len() != vids.len() {
1266            return Err(anyhow::anyhow!(
1267                "mismatch between blocks and VID common data"
1268            ));
1269        }
1270
1271        // Generate proofs for each block
1272        let mut proofs = Vec::new();
1273
1274        for (block, vid) in blocks.into_iter().zip(vids) {
1275            let ns_table = block.payload().ns_table();
1276
1277            // Check if namespace exists in this block
1278            if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
1279                if let Some(proof) = NsProof::new(block.payload(), &ns_index, vid.common()) {
1280                    let transactions = proof.export_all_txs(&ns_id);
1281                    proofs.push(espresso_types::NamespaceProofQueryData {
1282                        transactions,
1283                        proof: Some(proof),
1284                    });
1285                } else {
1286                    // Failed to generate proof - return empty result for this block
1287                    proofs.push(espresso_types::NamespaceProofQueryData {
1288                        transactions: vec![],
1289                        proof: None,
1290                    });
1291                }
1292            } else {
1293                // Namespace not present in this block
1294                proofs.push(espresso_types::NamespaceProofQueryData {
1295                    transactions: vec![],
1296                    proof: None,
1297                });
1298            }
1299        }
1300
1301        Ok(proofs)
1302    }
1303
1304    async fn stream_namespace_proofs(
1305        &self,
1306        from: usize,
1307        namespace: u32,
1308    ) -> anyhow::Result<BoxStream<'static, Self::NamespaceProofQueryData>> {
1309        let ns_id = NamespaceId::from(namespace);
1310        let ds = self.data_source.clone();
1311        let blocks = (*ds).subscribe_blocks(from).await;
1312        let vids = (*ds).subscribe_vid_common(from).await;
1313
1314        let stream = blocks
1315            .zip(vids)
1316            .map(move |(block, vid)| {
1317                let ns_table = block.payload().ns_table();
1318                if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
1319                    if let Some(proof) = NsProof::new(block.payload(), &ns_index, vid.common()) {
1320                        let transactions = proof.export_all_txs(&ns_id);
1321                        NamespaceProofQueryData {
1322                            transactions,
1323                            proof: Some(proof),
1324                        }
1325                    } else {
1326                        NamespaceProofQueryData {
1327                            transactions: vec![],
1328                            proof: None,
1329                        }
1330                    }
1331                } else {
1332                    NamespaceProofQueryData {
1333                        transactions: vec![],
1334                        proof: None,
1335                    }
1336                }
1337            })
1338            .boxed();
1339
1340        Ok(stream)
1341    }
1342
1343    async fn get_incorrect_encoding_proof(
1344        &self,
1345        block_id: espresso_api::v1::availability::BlockId,
1346        namespace: u32,
1347    ) -> anyhow::Result<Self::IncorrectEncodingProof> {
1348        let ns_id = NamespaceId::from(namespace);
1349
1350        let hs_block_id = match block_id {
1351            espresso_api::v1::availability::BlockId::Height(h) => HsBlockId::Number(h as usize),
1352            espresso_api::v1::availability::BlockId::Hash(h) => {
1353                let hash = h
1354                    .parse()
1355                    .map_err(|_| anyhow::anyhow!("invalid block hash: {}", h))?;
1356                HsBlockId::Hash(hash)
1357            },
1358            espresso_api::v1::availability::BlockId::PayloadHash(h) => {
1359                let payload_hash = h
1360                    .parse()
1361                    .map_err(|_| anyhow::anyhow!("invalid payload hash: {}", h))?;
1362                HsBlockId::PayloadHash(payload_hash)
1363            },
1364        };
1365
1366        let ds = &*self.data_source;
1367        let timeout = std::time::Duration::from_millis(500);
1368        let (block_fetch, vid_fetch) =
1369            join!(ds.get_block(hs_block_id), ds.get_vid_common(hs_block_id));
1370        let (block, vid_common) = join!(
1371            block_fetch.with_timeout(timeout),
1372            vid_fetch.with_timeout(timeout)
1373        );
1374
1375        let block = block.ok_or_else(|| anyhow::anyhow!("block not found"))?;
1376        let vid_common = vid_common.ok_or_else(|| anyhow::anyhow!("VID common data not found"))?;
1377
1378        let ns_table = block.payload().ns_table();
1379        let ns_index = ns_table
1380            .find_ns_id(&ns_id)
1381            .ok_or_else(|| anyhow::anyhow!("namespace {} not present in block", namespace))?;
1382
1383        if NsProof::new(block.payload(), &ns_index, vid_common.common()).is_some() {
1384            return Err(anyhow::anyhow!("block was correctly encoded"));
1385        }
1386
1387        // Block has incorrect encoding — fetch VID shares to construct the proof.
1388        let vid_shares_future = ds
1389            .request_vid_shares(block.height(), vid_common.clone(), Duration::from_secs(40))
1390            .await;
1391        let mut vid_shares = vid_shares_future
1392            .await
1393            .map_err(|e| anyhow::anyhow!("failed to fetch VID shares: {e:#}"))?;
1394
1395        if let Ok(local_share) = ds.vid_share(block.height() as usize).await {
1396            vid_shares.push(local_share);
1397        }
1398
1399        let avidm_shares: Vec<AvidMShare> = vid_shares
1400            .into_iter()
1401            .filter_map(|s| {
1402                if let VidShare::V1(s) = s {
1403                    Some(s)
1404                } else {
1405                    None
1406                }
1407            })
1408            .collect();
1409
1410        match NsProof::v1_1_new_with_incorrect_encoding(
1411            &avidm_shares,
1412            ns_table,
1413            &ns_index,
1414            &vid_common.payload_hash(),
1415            vid_common.common(),
1416        ) {
1417            Some(NsProof::V1IncorrectEncoding(proof)) => Ok(proof),
1418            _ => Err(anyhow::anyhow!(
1419                "failed to generate incorrect encoding proof"
1420            )),
1421        }
1422    }
1423
1424    async fn get_state_cert(&self, epoch: u64) -> anyhow::Result<Self::StateCertQueryDataV1> {
1425        // Try to get from local storage first
1426        let state_cert = self.data_source.get_state_cert_by_epoch(epoch).await?;
1427
1428        let cert = match state_cert {
1429            Some(cert) => cert,
1430            None => {
1431                // Not found locally, try to fetch from peers
1432                const TIMEOUT: Duration = Duration::from_secs(40);
1433                let cert = self
1434                    .data_source
1435                    .request_state_cert(epoch, TIMEOUT)
1436                    .await
1437                    .map_err(|e| {
1438                        anyhow::anyhow!("failed to fetch state cert for epoch {}: {}", epoch, e)
1439                    })?;
1440
1441                // Store the fetched certificate
1442                self.data_source
1443                    .insert_state_cert(epoch, cert.clone())
1444                    .await?;
1445
1446                cert
1447            },
1448        };
1449
1450        Ok(espresso_types::StateCertQueryDataV1::from(
1451            espresso_types::StateCertQueryDataV2(cert),
1452        ))
1453    }
1454
1455    async fn get_state_cert_v2(&self, epoch: u64) -> anyhow::Result<Self::StateCertQueryDataV2> {
1456        // Try to get from local storage first
1457        let state_cert = self.data_source.get_state_cert_by_epoch(epoch).await?;
1458
1459        let cert = match state_cert {
1460            Some(cert) => cert,
1461            None => {
1462                // Not found locally, try to fetch from peers
1463                const TIMEOUT: Duration = Duration::from_secs(40);
1464                let cert = self
1465                    .data_source
1466                    .request_state_cert(epoch, TIMEOUT)
1467                    .await
1468                    .map_err(|e| {
1469                        anyhow::anyhow!("failed to fetch state cert for epoch {}: {}", epoch, e)
1470                    })?;
1471
1472                // Store the fetched certificate
1473                self.data_source
1474                    .insert_state_cert(epoch, cert.clone())
1475                    .await?;
1476
1477                cert
1478            },
1479        };
1480
1481        Ok(espresso_types::StateCertQueryDataV2(cert))
1482    }
1483}
1484
1485// ============================================================================
1486// v1::HotShotAvailabilityApi implementation
1487// ============================================================================
1488
1489fn not_found(msg: impl Into<String>) -> anyhow::Error {
1490    AvailabilityError::NotFound(msg.into()).into()
1491}
1492
1493fn bad_request(msg: impl Into<String>) -> anyhow::Error {
1494    AvailabilityError::BadRequest(msg.into()).into()
1495}
1496
1497fn range_exceeded(msg: impl Into<String>) -> anyhow::Error {
1498    AvailabilityError::RangeExceeded(msg.into()).into()
1499}
1500
1501fn enforce_range(from: usize, until: usize, limit: usize) -> anyhow::Result<()> {
1502    if until.saturating_sub(from) > limit {
1503        return Err(range_exceeded(format!(
1504            "range {from}..{until} exceeds limit {limit}"
1505        )));
1506    }
1507    Ok(())
1508}
1509
1510#[async_trait]
1511impl<D> HotShotAvailabilityApi for NodeApiStateImpl<D>
1512where
1513    D: std::ops::Deref + Clone + Send + Sync + 'static,
1514    D::Target: AvailabilityDataSource<espresso_types::SeqTypes> + Send + Sync,
1515{
1516    type Leaf = LeafQueryData<espresso_types::SeqTypes>;
1517    type Block = BlockQueryData<espresso_types::SeqTypes>;
1518    type Header = HsHeader<espresso_types::SeqTypes>;
1519    type Payload = PayloadQueryData<espresso_types::SeqTypes>;
1520    type VidCommon = VidCommonQueryData<espresso_types::SeqTypes>;
1521    type Transaction = TransactionQueryData<espresso_types::SeqTypes>;
1522    type TransactionWithProof = TransactionWithProofQueryData<espresso_types::SeqTypes>;
1523    type BlockSummary = BlockSummaryQueryData<espresso_types::SeqTypes>;
1524    type Limits = HsLimits;
1525    type Cert2 = Certificate2<espresso_types::SeqTypes>;
1526
1527    async fn get_leaf(
1528        &self,
1529        id: espresso_api::v1::availability::LeafId,
1530    ) -> anyhow::Result<Self::Leaf> {
1531        let hs_id = match id {
1532            espresso_api::v1::availability::LeafId::Height(h) => HsLeafId::Number(h as usize),
1533            espresso_api::v1::availability::LeafId::Hash(h) => {
1534                HsLeafId::Hash(h.parse().map_err(|_| bad_request("invalid leaf hash"))?)
1535            },
1536        };
1537        let ds = &*self.data_source;
1538        ds.get_leaf(hs_id)
1539            .await
1540            .with_timeout(Duration::from_millis(500))
1541            .await
1542            .ok_or_else(|| not_found("leaf not found"))
1543    }
1544
1545    async fn get_leaf_range(&self, from: usize, until: usize) -> anyhow::Result<Vec<Self::Leaf>> {
1546        enforce_range(from, until, 500)?;
1547        let timeout = Duration::from_millis(500);
1548        let ds = &*self.data_source;
1549        let stream = ds.get_leaf_range(from..until).await;
1550        let mut results = Vec::new();
1551        futures::pin_mut!(stream);
1552        let mut i = from;
1553        while let Some(fetch) = stream.next().await {
1554            let item = fetch
1555                .with_timeout(timeout)
1556                .await
1557                .ok_or_else(|| not_found(format!("leaf {} not found", i)))?;
1558            results.push(item);
1559            i += 1;
1560        }
1561        Ok(results)
1562    }
1563
1564    async fn get_header(
1565        &self,
1566        id: espresso_api::v1::availability::BlockId,
1567    ) -> anyhow::Result<Self::Header> {
1568        let hs_id = block_id_to_hs(id)?;
1569        let ds = &*self.data_source;
1570        ds.get_header(hs_id)
1571            .await
1572            .with_timeout(Duration::from_millis(500))
1573            .await
1574            .ok_or_else(|| not_found(format!("header not found for {}", hs_id)))
1575    }
1576
1577    async fn get_header_range(
1578        &self,
1579        from: usize,
1580        until: usize,
1581    ) -> anyhow::Result<Vec<Self::Header>> {
1582        enforce_range(from, until, 100)?;
1583        let timeout = Duration::from_millis(500);
1584        let ds = &*self.data_source;
1585        let stream = ds.get_header_range(from..until).await;
1586        let mut results = Vec::new();
1587        futures::pin_mut!(stream);
1588        let mut i = from;
1589        while let Some(fetch) = stream.next().await {
1590            let item = fetch
1591                .with_timeout(timeout)
1592                .await
1593                .ok_or_else(|| not_found(format!("header {} not found", i)))?;
1594            results.push(item);
1595            i += 1;
1596        }
1597        Ok(results)
1598    }
1599
1600    async fn get_block(
1601        &self,
1602        id: espresso_api::v1::availability::BlockId,
1603    ) -> anyhow::Result<Self::Block> {
1604        let hs_id = block_id_to_hs(id)?;
1605        let ds = &*self.data_source;
1606        ds.get_block(hs_id)
1607            .await
1608            .with_timeout(Duration::from_millis(500))
1609            .await
1610            .ok_or_else(|| not_found(format!("block not found for {}", hs_id)))
1611    }
1612
1613    async fn get_block_range(&self, from: usize, until: usize) -> anyhow::Result<Vec<Self::Block>> {
1614        enforce_range(from, until, 100)?;
1615        let timeout = Duration::from_millis(500);
1616        let ds = &*self.data_source;
1617        let stream = ds.get_block_range(from..until).await;
1618        let mut results = Vec::new();
1619        futures::pin_mut!(stream);
1620        let mut i = from;
1621        while let Some(fetch) = stream.next().await {
1622            let item = fetch
1623                .with_timeout(timeout)
1624                .await
1625                .ok_or_else(|| not_found(format!("block {} not found", i)))?;
1626            results.push(item);
1627            i += 1;
1628        }
1629        Ok(results)
1630    }
1631
1632    async fn get_payload(
1633        &self,
1634        id: espresso_api::v1::availability::PayloadId,
1635    ) -> anyhow::Result<Self::Payload> {
1636        let hs_id = payload_id_to_hs(id)?;
1637        let ds = &*self.data_source;
1638        ds.get_payload(hs_id)
1639            .await
1640            .with_timeout(Duration::from_millis(500))
1641            .await
1642            .ok_or_else(|| not_found(format!("payload not found for {}", hs_id)))
1643    }
1644
1645    async fn get_payload_range(
1646        &self,
1647        from: usize,
1648        until: usize,
1649    ) -> anyhow::Result<Vec<Self::Payload>> {
1650        enforce_range(from, until, 100)?;
1651        let timeout = Duration::from_millis(500);
1652        let ds = &*self.data_source;
1653        let stream = ds.get_payload_range(from..until).await;
1654        let mut results = Vec::new();
1655        futures::pin_mut!(stream);
1656        let mut i = from;
1657        while let Some(fetch) = stream.next().await {
1658            let item = fetch
1659                .with_timeout(timeout)
1660                .await
1661                .ok_or_else(|| not_found(format!("payload {} not found", i)))?;
1662            results.push(item);
1663            i += 1;
1664        }
1665        Ok(results)
1666    }
1667
1668    async fn get_vid_common(
1669        &self,
1670        id: espresso_api::v1::availability::BlockId,
1671    ) -> anyhow::Result<Self::VidCommon> {
1672        let hs_id = block_id_to_hs(id)?;
1673        let ds = &*self.data_source;
1674        ds.get_vid_common(hs_id)
1675            .await
1676            .with_timeout(Duration::from_millis(500))
1677            .await
1678            .ok_or_else(|| not_found(format!("VID common not found for {}", hs_id)))
1679    }
1680
1681    async fn get_vid_common_range(
1682        &self,
1683        from: usize,
1684        until: usize,
1685    ) -> anyhow::Result<Vec<Self::VidCommon>> {
1686        enforce_range(from, until, 500)?;
1687        let timeout = Duration::from_millis(500);
1688        let ds = &*self.data_source;
1689        let stream = ds.get_vid_common_range(from..until).await;
1690        let mut results = Vec::new();
1691        futures::pin_mut!(stream);
1692        let mut i = from;
1693        while let Some(fetch) = stream.next().await {
1694            let item = fetch
1695                .with_timeout(timeout)
1696                .await
1697                .ok_or_else(|| not_found(format!("VID common {} not found", i)))?;
1698            results.push(item);
1699            i += 1;
1700        }
1701        Ok(results)
1702    }
1703
1704    async fn get_transaction_by_position(
1705        &self,
1706        height: u64,
1707        index: u64,
1708    ) -> anyhow::Result<Self::Transaction> {
1709        let ds = &*self.data_source;
1710        let block = ds
1711            .get_block(HsBlockId::Number(height as usize))
1712            .await
1713            .with_timeout(Duration::from_millis(500))
1714            .await
1715            .ok_or_else(|| not_found(format!("block {} not found", height)))?;
1716
1717        let idx = block
1718            .payload()
1719            .nth(block.metadata(), index as usize)
1720            .ok_or_else(|| {
1721                not_found(format!(
1722                    "transaction index {} out of bounds in block {}",
1723                    index, height
1724                ))
1725            })?;
1726        let tx = block
1727            .transaction(&idx)
1728            .ok_or_else(|| not_found(format!("transaction not found at index {}", index)))?;
1729        TransactionQueryData::new(tx, &block, &idx, index)
1730            .ok_or_else(|| anyhow::anyhow!("failed to build transaction query data"))
1731    }
1732
1733    async fn get_transaction_by_hash(&self, hash: String) -> anyhow::Result<Self::Transaction> {
1734        let ds = &*self.data_source;
1735        let tx_hash: hotshot_query_service::availability::TransactionHash<
1736            espresso_types::SeqTypes,
1737        > = hash
1738            .parse()
1739            .map_err(|_| bad_request(format!("invalid transaction hash: {}", hash)))?;
1740        let bwt = ds
1741            .get_block_containing_transaction(tx_hash)
1742            .await
1743            .with_timeout(Duration::from_millis(500))
1744            .await
1745            .ok_or_else(|| not_found("transaction not found"))?;
1746        Ok(bwt.transaction)
1747    }
1748
1749    async fn get_transaction_proof_by_position(
1750        &self,
1751        height: u64,
1752        index: u64,
1753    ) -> anyhow::Result<Self::TransactionWithProof> {
1754        let ds = &*self.data_source;
1755        let timeout = Duration::from_millis(500);
1756
1757        let (block_fetch, vid_fetch) = futures::join!(
1758            ds.get_block(HsBlockId::Number(height as usize)),
1759            ds.get_vid_common(HsBlockId::Number(height as usize))
1760        );
1761        let (block, vid) = futures::join!(
1762            block_fetch.with_timeout(timeout),
1763            vid_fetch.with_timeout(timeout)
1764        );
1765
1766        let block = block.ok_or_else(|| not_found(format!("block {} not found", height)))?;
1767        let vid =
1768            vid.ok_or_else(|| not_found(format!("VID common not found for block {}", height)))?;
1769
1770        let idx = block
1771            .payload()
1772            .nth(block.metadata(), index as usize)
1773            .ok_or_else(|| {
1774                not_found(format!(
1775                    "transaction index {} out of bounds in block {}",
1776                    index, height
1777                ))
1778            })?;
1779        let tx = block
1780            .transaction(&idx)
1781            .ok_or_else(|| not_found(format!("transaction not found at index {}", index)))?;
1782        let tx_data = TransactionQueryData::new(tx, &block, &idx, index)
1783            .ok_or_else(|| anyhow::anyhow!("failed to build transaction query data"))?;
1784        let proof = block
1785            .transaction_proof(&vid, &idx)
1786            .ok_or_else(|| anyhow::anyhow!("failed to build transaction proof"))?;
1787        Ok(TransactionWithProofQueryData::new(tx_data, proof))
1788    }
1789
1790    async fn get_transaction_proof_by_hash(
1791        &self,
1792        hash: String,
1793    ) -> anyhow::Result<Self::TransactionWithProof> {
1794        let ds = &*self.data_source;
1795        let timeout = Duration::from_millis(500);
1796
1797        let tx_hash: hotshot_query_service::availability::TransactionHash<
1798            espresso_types::SeqTypes,
1799        > = hash
1800            .parse()
1801            .map_err(|_| bad_request(format!("invalid transaction hash: {}", hash)))?;
1802        let bwt = ds
1803            .get_block_containing_transaction(tx_hash)
1804            .await
1805            .with_timeout(timeout)
1806            .await
1807            .ok_or_else(|| not_found("transaction not found"))?;
1808
1809        let vid = ds
1810            .get_vid_common(HsBlockId::Number(bwt.block.height() as usize))
1811            .await
1812            .with_timeout(timeout)
1813            .await
1814            .ok_or_else(|| {
1815                not_found(format!(
1816                    "VID common not found for block {}",
1817                    bwt.block.height()
1818                ))
1819            })?;
1820
1821        let proof = bwt
1822            .block
1823            .transaction_proof(&vid, &bwt.index)
1824            .ok_or_else(|| anyhow::anyhow!("failed to build transaction proof"))?;
1825        Ok(TransactionWithProofQueryData::new(bwt.transaction, proof))
1826    }
1827
1828    async fn get_block_summary(&self, height: usize) -> anyhow::Result<Self::BlockSummary> {
1829        let ds = &*self.data_source;
1830        let block = ds
1831            .get_block(HsBlockId::Number(height))
1832            .await
1833            .with_timeout(Duration::from_millis(500))
1834            .await
1835            .ok_or_else(|| not_found(format!("block {} not found", height)))?;
1836        Ok(BlockSummaryQueryData::from(block))
1837    }
1838
1839    async fn get_block_summary_range(
1840        &self,
1841        from: usize,
1842        until: usize,
1843    ) -> anyhow::Result<Vec<Self::BlockSummary>> {
1844        enforce_range(from, until, 100)?;
1845        let timeout = Duration::from_millis(500);
1846        let ds = &*self.data_source;
1847        let stream = ds.get_block_range(from..until).await;
1848        let mut results = Vec::new();
1849        futures::pin_mut!(stream);
1850        let mut i = from;
1851        while let Some(fetch) = stream.next().await {
1852            let block = fetch
1853                .with_timeout(timeout)
1854                .await
1855                .ok_or_else(|| not_found(format!("block {} not found", i)))?;
1856            results.push(BlockSummaryQueryData::from(block));
1857            i += 1;
1858        }
1859        Ok(results)
1860    }
1861
1862    async fn get_limits(&self) -> anyhow::Result<Self::Limits> {
1863        Ok(HsLimits {
1864            small_object_range_limit: 500,
1865            large_object_range_limit: 100,
1866        })
1867    }
1868
1869    async fn get_cert2(&self, height: u64) -> anyhow::Result<Option<Self::Cert2>> {
1870        self.data_source
1871            .get_cert2(height)
1872            .await
1873            .map_err(|e| anyhow::anyhow!("{}", e))
1874    }
1875
1876    async fn stream_leaves(&self, from: usize) -> anyhow::Result<BoxStream<'static, Self::Leaf>> {
1877        let ds = self.data_source.clone();
1878        Ok((*ds).subscribe_leaves(from).await.boxed())
1879    }
1880
1881    async fn stream_headers(
1882        &self,
1883        from: usize,
1884    ) -> anyhow::Result<BoxStream<'static, Self::Header>> {
1885        let ds = self.data_source.clone();
1886        Ok((*ds).subscribe_headers(from).await.boxed())
1887    }
1888
1889    async fn stream_blocks(&self, from: usize) -> anyhow::Result<BoxStream<'static, Self::Block>> {
1890        let ds = self.data_source.clone();
1891        Ok((*ds).subscribe_blocks(from).await.boxed())
1892    }
1893
1894    async fn stream_payloads(
1895        &self,
1896        from: usize,
1897    ) -> anyhow::Result<BoxStream<'static, Self::Payload>> {
1898        let ds = self.data_source.clone();
1899        Ok((*ds).subscribe_payloads(from).await.boxed())
1900    }
1901
1902    async fn stream_vid_common(
1903        &self,
1904        from: usize,
1905    ) -> anyhow::Result<BoxStream<'static, Self::VidCommon>> {
1906        let ds = self.data_source.clone();
1907        Ok((*ds).subscribe_vid_common(from).await.boxed())
1908    }
1909
1910    async fn stream_transactions(
1911        &self,
1912        from: usize,
1913        namespace: Option<u32>,
1914    ) -> anyhow::Result<BoxStream<'static, Self::Transaction>> {
1915        let ds = self.data_source.clone();
1916        let stream = (*ds)
1917            .subscribe_blocks(from)
1918            .await
1919            .flat_map(move |block| {
1920                let ns_filter = namespace.map(NamespaceId::from);
1921                let txs: Vec<Self::Transaction> = block
1922                    .enumerate()
1923                    .enumerate()
1924                    .filter_map(|(position_in_block, (tx_index, _tx))| {
1925                        let tx = block.transaction(&tx_index)?;
1926                        if let Some(ns) = ns_filter
1927                            && tx.namespace() != ns
1928                        {
1929                            return None;
1930                        }
1931                        TransactionQueryData::new(tx, &block, &tx_index, position_in_block as u64)
1932                    })
1933                    .collect();
1934                futures::stream::iter(txs)
1935            })
1936            .boxed();
1937        Ok(stream)
1938    }
1939}
1940
1941fn block_id_to_hs(
1942    id: espresso_api::v1::availability::BlockId,
1943) -> anyhow::Result<HsBlockId<SeqTypes>> {
1944    match id {
1945        espresso_api::v1::availability::BlockId::Height(h) => Ok(HsBlockId::Number(h as usize)),
1946        espresso_api::v1::availability::BlockId::Hash(h) => {
1947            let hash = h
1948                .parse()
1949                .map_err(|_| bad_request(format!("invalid block hash: {}", h)))?;
1950            Ok(HsBlockId::Hash(hash))
1951        },
1952        espresso_api::v1::availability::BlockId::PayloadHash(h) => {
1953            let payload_hash = h
1954                .parse()
1955                .map_err(|_| bad_request(format!("invalid payload hash: {}", h)))?;
1956            Ok(HsBlockId::PayloadHash(payload_hash))
1957        },
1958    }
1959}
1960
1961fn payload_id_to_hs(
1962    id: espresso_api::v1::availability::PayloadId,
1963) -> anyhow::Result<HsBlockId<SeqTypes>> {
1964    match id {
1965        espresso_api::v1::availability::PayloadId::Height(h) => Ok(HsBlockId::Number(h as usize)),
1966        espresso_api::v1::availability::PayloadId::Hash(h) => {
1967            let payload_hash = h
1968                .parse()
1969                .map_err(|_| bad_request(format!("invalid payload hash: {}", h)))?;
1970            Ok(HsBlockId::PayloadHash(payload_hash))
1971        },
1972        espresso_api::v1::availability::PayloadId::BlockHash(h) => {
1973            let hash = h
1974                .parse()
1975                .map_err(|_| bad_request(format!("invalid block hash: {}", h)))?;
1976            Ok(HsBlockId::Hash(hash))
1977        },
1978    }
1979}
1980
1981fn classify_query_error(err: hotshot_query_service::QueryError) -> anyhow::Error {
1982    use hotshot_query_service::QueryError;
1983    match err {
1984        QueryError::NotFound | QueryError::Missing => not_found(err.to_string()),
1985        QueryError::Error { .. } => anyhow::anyhow!(err.to_string()),
1986    }
1987}
1988
1989#[async_trait]
1990impl<D> espresso_api::v1::BlockStateApi for NodeApiStateImpl<D>
1991where
1992    D: std::ops::Deref + Clone + Send + Sync + 'static,
1993    D::Target: hotshot_query_service::merklized_state::MerklizedStateDataSource<
1994            espresso_types::SeqTypes,
1995            espresso_types::BlockMerkleTree,
1996            { <espresso_types::BlockMerkleTree as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY },
1997        > + hotshot_query_service::merklized_state::MerklizedStateHeightPersistence
1998        + Send
1999        + Sync,
2000{
2001    type MerkleProof = InternalMerkleProof<
2002        committable::Commitment<espresso_types::Header>,
2003        u64,
2004        jf_merkle_tree_compat::prelude::Sha3Node,
2005        3,
2006    >;
2007
2008    async fn get_block_state_path(
2009        &self,
2010        snapshot: espresso_api::v1::Snapshot,
2011        key: String,
2012    ) -> anyhow::Result<Self::MerkleProof> {
2013        use hotshot_query_service::merklized_state::{
2014            MerklizedStateDataSource, Snapshot as HsSnapshot,
2015        };
2016
2017        let hs_snapshot = match snapshot {
2018            espresso_api::v1::Snapshot::Height(h) => HsSnapshot::Index(h),
2019            espresso_api::v1::Snapshot::Commit(c) => {
2020                let tb64: TaggedBase64 = c
2021                    .parse()
2022                    .map_err(|_| bad_request("failed to parse commit param"))?;
2023                let commit = (&tb64)
2024                    .try_into()
2025                    .map_err(|_| bad_request("failed to parse commit param"))?;
2026                HsSnapshot::Commit(commit)
2027            },
2028        };
2029        let key: u64 = key
2030            .parse()
2031            .map_err(|_| bad_request("failed to parse Key param"))?;
2032        let ds = &*self.data_source;
2033        MerklizedStateDataSource::<
2034            espresso_types::SeqTypes,
2035            espresso_types::BlockMerkleTree,
2036            _,
2037        >::get_path(ds, hs_snapshot, key)
2038        .await
2039        .map_err(classify_query_error)
2040    }
2041
2042    async fn get_block_state_height(&self) -> anyhow::Result<u64> {
2043        use hotshot_query_service::merklized_state::MerklizedStateHeightPersistence;
2044
2045        let ds = &*self.data_source;
2046        ds.get_last_state_height()
2047            .await
2048            .map(|h| h as u64)
2049            .map_err(classify_query_error)
2050    }
2051}
2052
2053#[async_trait]
2054impl<D> espresso_api::v1::FeeStateApi for NodeApiStateImpl<D>
2055where
2056    D: std::ops::Deref + Clone + Send + Sync + 'static,
2057    D::Target: hotshot_query_service::merklized_state::MerklizedStateDataSource<
2058            espresso_types::SeqTypes,
2059            espresso_types::FeeMerkleTree,
2060            { <espresso_types::FeeMerkleTree as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY },
2061        > + hotshot_query_service::merklized_state::MerklizedStateHeightPersistence
2062        + Send
2063        + Sync,
2064{
2065    type MerkleProof = InternalMerkleProof<
2066        espresso_types::FeeAmount,
2067        espresso_types::FeeAccount,
2068        jf_merkle_tree_compat::prelude::Sha3Node,
2069        256,
2070    >;
2071    type FeeAmount = espresso_types::FeeAmount;
2072
2073    async fn get_fee_state_path(
2074        &self,
2075        snapshot: espresso_api::v1::Snapshot,
2076        key: String,
2077    ) -> anyhow::Result<Self::MerkleProof> {
2078        use hotshot_query_service::merklized_state::{
2079            MerklizedStateDataSource, Snapshot as HsSnapshot,
2080        };
2081
2082        let hs_snapshot = match snapshot {
2083            espresso_api::v1::Snapshot::Height(h) => HsSnapshot::Index(h),
2084            espresso_api::v1::Snapshot::Commit(c) => {
2085                let tb64: TaggedBase64 = c
2086                    .parse()
2087                    .map_err(|_| bad_request("failed to parse commit param"))?;
2088                let commit = (&tb64)
2089                    .try_into()
2090                    .map_err(|_| bad_request("failed to parse commit param"))?;
2091                HsSnapshot::Commit(commit)
2092            },
2093        };
2094        let key: espresso_types::FeeAccount = key
2095            .parse()
2096            .map_err(|_| bad_request("failed to parse Key param"))?;
2097        let ds = &*self.data_source;
2098        MerklizedStateDataSource::<
2099            espresso_types::SeqTypes,
2100            espresso_types::FeeMerkleTree,
2101            _,
2102        >::get_path(ds, hs_snapshot, key)
2103        .await
2104        .map_err(classify_query_error)
2105    }
2106
2107    async fn get_fee_state_height(&self) -> anyhow::Result<u64> {
2108        use hotshot_query_service::merklized_state::MerklizedStateHeightPersistence;
2109
2110        let ds = &*self.data_source;
2111        ds.get_last_state_height()
2112            .await
2113            .map(|h| h as u64)
2114            .map_err(classify_query_error)
2115    }
2116
2117    async fn get_fee_balance_latest(
2118        &self,
2119        address: String,
2120    ) -> anyhow::Result<Option<Self::FeeAmount>> {
2121        use hotshot_query_service::merklized_state::{
2122            MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot as HsSnapshot,
2123        };
2124        use jf_merkle_tree_compat::prelude::MerkleProof as JfMerkleProof;
2125
2126        let key: espresso_types::FeeAccount = address
2127            .parse()
2128            .map_err(|_| bad_request("failed to parse address"))?;
2129        let ds = &*self.data_source;
2130        let height = ds
2131            .get_last_state_height()
2132            .await
2133            .map_err(classify_query_error)?;
2134        let path: JfMerkleProof<
2135            espresso_types::FeeAmount,
2136            espresso_types::FeeAccount,
2137            jf_merkle_tree_compat::prelude::Sha3Node,
2138            256,
2139        > = MerklizedStateDataSource::<
2140            espresso_types::SeqTypes,
2141            espresso_types::FeeMerkleTree,
2142            _,
2143        >::get_path(ds, HsSnapshot::Index(height as u64), key)
2144        .await
2145        .map_err(classify_query_error)?;
2146        Ok(path.elem().copied())
2147    }
2148}
2149
2150// ============================================================================
2151// v1::StatusApi implementation
2152// ============================================================================
2153
2154#[async_trait]
2155impl<D> espresso_api::v1::StatusApi for NodeApiStateImpl<D>
2156where
2157    D: std::ops::Deref + Clone + Send + Sync + 'static,
2158    D::Target: hotshot_query_service::status::StatusDataSource + Send + Sync,
2159{
2160    async fn block_height(&self) -> anyhow::Result<u64> {
2161        let ds = &*self.data_source;
2162        let h = hotshot_query_service::status::StatusDataSource::block_height(ds)
2163            .await
2164            .map_err(|e| anyhow::anyhow!("{e}"))?;
2165        Ok(h as u64)
2166    }
2167
2168    async fn success_rate(&self) -> anyhow::Result<f64> {
2169        let ds = &*self.data_source;
2170        hotshot_query_service::status::StatusDataSource::success_rate(ds)
2171            .await
2172            .map_err(|e| anyhow::anyhow!("{e}"))
2173    }
2174
2175    async fn time_since_last_decide(&self) -> anyhow::Result<u64> {
2176        let ds = &*self.data_source;
2177        hotshot_query_service::status::StatusDataSource::elapsed_time_since_last_decide(ds)
2178            .await
2179            .map_err(|e| anyhow::anyhow!("{e}"))
2180    }
2181
2182    async fn metrics(&self) -> anyhow::Result<String> {
2183        use hotshot_query_service::status::HasMetrics;
2184        use tide_disco::metrics::Metrics as _;
2185        let ds = &*self.data_source;
2186        ds.metrics().export().map_err(|e| anyhow::anyhow!("{e}"))
2187    }
2188}
2189
2190// ============================================================================
2191// v1::ConfigApi implementation
2192// ============================================================================
2193
2194#[async_trait]
2195impl<D> espresso_api::v1::ConfigApi for NodeApiStateImpl<D>
2196where
2197    D: std::ops::Deref + Clone + Send + Sync + 'static,
2198    D::Target: super::data_source::HotShotConfigDataSource + Send + Sync,
2199{
2200    type HotShotConfig = espresso_types::config::PublicNetworkConfig;
2201    type RuntimeConfig = crate::options::PublicNodeConfig;
2202
2203    async fn hotshot_config(&self) -> anyhow::Result<Self::HotShotConfig> {
2204        use super::data_source::HotShotConfigDataSource as _;
2205        let ds = &*self.data_source;
2206        Ok(ds.get_config().await)
2207    }
2208
2209    async fn env(&self) -> anyhow::Result<Vec<String>> {
2210        Ok((*self.env_vars).clone())
2211    }
2212
2213    async fn runtime_config(&self) -> anyhow::Result<Self::RuntimeConfig> {
2214        self.public_node_config.as_deref().cloned().ok_or_else(|| {
2215            espresso_api::error::AvailabilityError::NotFound(
2216                "runtime config not available".to_string(),
2217            )
2218            .into()
2219        })
2220    }
2221}
2222
2223// ============================================================================
2224// v1::NodeApi implementation
2225// ============================================================================
2226
2227#[async_trait]
2228impl<D> espresso_api::v1::NodeApi for NodeApiStateImpl<D>
2229where
2230    D: std::ops::Deref + Clone + Send + Sync + 'static,
2231    D::Target: hotshot_query_service::node::NodeDataSource<espresso_types::SeqTypes>
2232        + super::data_source::StakeTableDataSource<espresso_types::SeqTypes>
2233        + super::data_source::PruningDataSource
2234        + Send
2235        + Sync,
2236{
2237    type VidShare = hotshot_types::data::VidShare;
2238    type SyncStatus = hotshot_query_service::node::SyncStatusQueryData;
2239    type HeaderWindow = hotshot_query_service::node::TimeWindowQueryData<
2240        hotshot_query_service::Header<espresso_types::SeqTypes>,
2241    >;
2242    type Limits = hotshot_query_service::node::Limits;
2243    type StakeTable = Vec<hotshot_types::PeerConfig<espresso_types::SeqTypes>>;
2244    type StakeTableCurrent =
2245        super::data_source::StakeTableWithEpochNumber<espresso_types::SeqTypes>;
2246    type Validators = indexmap::IndexMap<
2247        alloy::primitives::Address,
2248        espresso_types::v0_3::AuthenticatedValidator<espresso_types::PubKey>,
2249    >;
2250    type AllValidators = Vec<espresso_types::v0_3::RegisteredValidator<espresso_types::PubKey>>;
2251    type Participation = std::collections::HashMap<espresso_types::PubKey, f64>;
2252    type BlockReward = Option<espresso_types::v0_3::RewardAmount>;
2253    type Block = hotshot_query_service::availability::BlockQueryData<espresso_types::SeqTypes>;
2254    type Leaf = hotshot_query_service::availability::LeafQueryData<espresso_types::SeqTypes>;
2255
2256    async fn block_height(&self) -> anyhow::Result<u64> {
2257        let ds = &*self.data_source;
2258        let h = hotshot_query_service::node::NodeDataSource::block_height(ds)
2259            .await
2260            .map_err(classify_query_error)?;
2261        Ok(h as u64)
2262    }
2263
2264    async fn count_transactions(
2265        &self,
2266        from: Option<u64>,
2267        to: Option<u64>,
2268        namespace: Option<u64>,
2269    ) -> anyhow::Result<u64> {
2270        use std::ops::Bound;
2271        let ds = &*self.data_source;
2272        let from = match from {
2273            Some(f) => Bound::Included(f as usize),
2274            None => Bound::Unbounded,
2275        };
2276        let to = match to {
2277            Some(t) => Bound::Included(t as usize),
2278            None => Bound::Unbounded,
2279        };
2280        let ns = namespace.map(espresso_types::NamespaceId::from);
2281        let count = ds
2282            .count_transactions_in_range((from, to), ns)
2283            .await
2284            .map_err(classify_query_error)?;
2285        Ok(count as u64)
2286    }
2287
2288    async fn payload_size(
2289        &self,
2290        from: Option<u64>,
2291        to: Option<u64>,
2292        namespace: Option<u64>,
2293    ) -> anyhow::Result<u64> {
2294        use std::ops::Bound;
2295        let ds = &*self.data_source;
2296        let from = match from {
2297            Some(f) => Bound::Included(f as usize),
2298            None => Bound::Unbounded,
2299        };
2300        let to = match to {
2301            Some(t) => Bound::Included(t as usize),
2302            None => Bound::Unbounded,
2303        };
2304        let ns = namespace.map(espresso_types::NamespaceId::from);
2305        let size = ds
2306            .payload_size_in_range((from, to), ns)
2307            .await
2308            .map_err(classify_query_error)?;
2309        Ok(size as u64)
2310    }
2311
2312    async fn get_vid_share(
2313        &self,
2314        id: espresso_api::v1::VidShareId,
2315    ) -> anyhow::Result<Self::VidShare> {
2316        let ds = &*self.data_source;
2317        let node_id: HsBlockId<espresso_types::SeqTypes> = match id {
2318            espresso_api::v1::VidShareId::Height(h) => HsBlockId::Number(h as usize),
2319            espresso_api::v1::VidShareId::Hash(h) => HsBlockId::Hash(
2320                h.parse()
2321                    .map_err(|_| bad_request(format!("invalid block hash: {h}")))?,
2322            ),
2323            espresso_api::v1::VidShareId::PayloadHash(h) => HsBlockId::PayloadHash(
2324                h.parse()
2325                    .map_err(|_| bad_request(format!("invalid payload hash: {h}")))?,
2326            ),
2327        };
2328        hotshot_query_service::node::NodeDataSource::vid_share(ds, node_id)
2329            .await
2330            .map_err(classify_query_error)
2331    }
2332
2333    async fn sync_status(&self) -> anyhow::Result<Self::SyncStatus> {
2334        let ds = &*self.data_source;
2335        hotshot_query_service::node::NodeDataSource::sync_status(ds)
2336            .await
2337            .map_err(classify_query_error)
2338    }
2339
2340    async fn get_header_window(
2341        &self,
2342        start: espresso_api::v1::HeaderWindowStart,
2343        end: u64,
2344    ) -> anyhow::Result<Self::HeaderWindow> {
2345        use hotshot_query_service::node::WindowStart;
2346        let ds = &*self.data_source;
2347        let start: WindowStart<espresso_types::SeqTypes> = match start {
2348            espresso_api::v1::HeaderWindowStart::Time(t) => WindowStart::Time(t),
2349            espresso_api::v1::HeaderWindowStart::Height(h) => WindowStart::Height(h),
2350            espresso_api::v1::HeaderWindowStart::Hash(h) => WindowStart::Hash(
2351                h.parse()
2352                    .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2353            ),
2354        };
2355        ds.get_header_window(start, end, node_window_limit())
2356            .await
2357            .map_err(classify_query_error)
2358    }
2359
2360    async fn limits(&self) -> anyhow::Result<Self::Limits> {
2361        Ok(hotshot_query_service::node::Limits {
2362            window_limit: node_window_limit(),
2363        })
2364    }
2365
2366    async fn stake_table(&self, epoch: u64) -> anyhow::Result<Self::StakeTable> {
2367        let ds = &*self.data_source;
2368        ds.get_stake_table(Some(hotshot_types::data::EpochNumber::new(epoch)))
2369            .await
2370    }
2371
2372    async fn stake_table_current(&self) -> anyhow::Result<Self::StakeTableCurrent> {
2373        let ds = &*self.data_source;
2374        ds.get_stake_table_current().await
2375    }
2376
2377    async fn da_stake_table(&self, epoch: u64) -> anyhow::Result<Self::StakeTable> {
2378        let ds = &*self.data_source;
2379        ds.get_da_stake_table(Some(hotshot_types::data::EpochNumber::new(epoch)))
2380            .await
2381    }
2382
2383    async fn da_stake_table_current(&self) -> anyhow::Result<Self::StakeTableCurrent> {
2384        let ds = &*self.data_source;
2385        ds.get_da_stake_table_current().await
2386    }
2387
2388    async fn get_validators(&self, epoch: u64) -> anyhow::Result<Self::Validators> {
2389        let ds = &*self.data_source;
2390        ds.get_validators(hotshot_types::data::EpochNumber::new(epoch))
2391            .await
2392    }
2393
2394    async fn get_all_validators(
2395        &self,
2396        epoch: u64,
2397        offset: u64,
2398        limit: u64,
2399    ) -> anyhow::Result<Self::AllValidators> {
2400        if limit > 1000 {
2401            return Err(anyhow::anyhow!("Limit cannot be greater than 1000"));
2402        }
2403        let ds = &*self.data_source;
2404        ds.get_all_validators(hotshot_types::data::EpochNumber::new(epoch), offset, limit)
2405            .await
2406    }
2407
2408    async fn current_proposal_participation(&self) -> anyhow::Result<Self::Participation> {
2409        let ds = &*self.data_source;
2410        Ok(ds.current_proposal_participation().await)
2411    }
2412
2413    async fn proposal_participation(&self, epoch: u64) -> anyhow::Result<Self::Participation> {
2414        let ds = &*self.data_source;
2415        Ok(ds
2416            .proposal_participation(hotshot_types::data::EpochNumber::new(epoch))
2417            .await)
2418    }
2419
2420    async fn current_vote_participation(&self) -> anyhow::Result<Self::Participation> {
2421        let ds = &*self.data_source;
2422        Ok(ds.current_vote_participation().await)
2423    }
2424
2425    async fn vote_participation(&self, epoch: u64) -> anyhow::Result<Self::Participation> {
2426        let ds = &*self.data_source;
2427        Ok(ds
2428            .vote_participation(hotshot_types::data::EpochNumber::new(epoch))
2429            .await)
2430    }
2431
2432    async fn get_block_reward(&self, epoch: Option<u64>) -> anyhow::Result<Self::BlockReward> {
2433        let ds = &*self.data_source;
2434        ds.get_block_reward(epoch.map(hotshot_types::data::EpochNumber::new))
2435            .await
2436    }
2437
2438    async fn get_oldest_block(&self) -> anyhow::Result<Option<Self::Block>> {
2439        use super::data_source::PruningDataSource as _;
2440        let ds = &*self.data_source;
2441        ds.get_oldest_block().await
2442    }
2443
2444    async fn get_oldest_leaf(&self) -> anyhow::Result<Option<Self::Leaf>> {
2445        use super::data_source::PruningDataSource as _;
2446        let ds = &*self.data_source;
2447        ds.get_oldest_leaf().await
2448    }
2449}
2450
2451fn node_window_limit() -> usize {
2452    hotshot_query_service::node::Options::default().window_limit
2453}
2454
2455// ============================================================================
2456// v1::CatchupApi implementation
2457// ============================================================================
2458
2459#[async_trait]
2460impl<D> espresso_api::v1::CatchupApi for NodeApiStateImpl<D>
2461where
2462    D: std::ops::Deref + Clone + Send + Sync + 'static,
2463    D::Target: super::data_source::CatchupDataSource
2464        + super::data_source::NodeStateDataSource
2465        + Send
2466        + Sync,
2467{
2468    type FeeAccount = espresso_types::FeeAccount;
2469    type RewardAccountV1 = espresso_types::v0_3::RewardAccountV1;
2470    type RewardAccountV2 = espresso_types::v0_4::RewardAccountV2;
2471
2472    type AccountQueryData = espresso_types::AccountQueryData;
2473    type FeeMerkleTree = espresso_types::FeeMerkleTree;
2474    type BlocksFrontier = super::BlocksFrontier;
2475    type ChainConfig = espresso_types::v0_3::ChainConfig;
2476    type LeafChain = Vec<espresso_types::Leaf2>;
2477    type Cert2 = espresso_types::Certificate2<espresso_types::SeqTypes>;
2478    type RewardAccountQueryDataV1 = espresso_types::v0_3::RewardAccountQueryDataV1;
2479    type RewardMerkleTreeV1 = espresso_types::v0_3::RewardMerkleTreeV1;
2480    type RewardAccountQueryDataV2 = espresso_types::v0_4::RewardAccountQueryDataV2;
2481    type RewardMerkleTreeV2Data = serde_json::Value;
2482    type StateCert = hotshot_types::simple_certificate::LightClientStateUpdateCertificateV2<
2483        espresso_types::SeqTypes,
2484    >;
2485
2486    async fn get_account(
2487        &self,
2488        height: u64,
2489        view: u64,
2490        address: String,
2491    ) -> anyhow::Result<Self::AccountQueryData> {
2492        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2493        let ds = &*self.data_source;
2494        let view = hotshot_types::data::ViewNumber::new(view);
2495        let account: espresso_types::FeeAccount = address
2496            .parse()
2497            .map_err(|err| bad_request(format!("malformed fee account {address}: {err}")))?;
2498        let instance = ds.node_state().await;
2499        ds.get_account(&instance, height, view, account)
2500            .await
2501            .map_err(|err| not_found(format!("{err:#}")))
2502    }
2503
2504    async fn get_accounts(
2505        &self,
2506        height: u64,
2507        view: u64,
2508        accounts: Vec<Self::FeeAccount>,
2509    ) -> anyhow::Result<Self::FeeMerkleTree> {
2510        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2511        let ds = &*self.data_source;
2512        let view = hotshot_types::data::ViewNumber::new(view);
2513        let instance = ds.node_state().await;
2514        ds.get_accounts(&instance, height, view, &accounts)
2515            .await
2516            .map_err(|err| not_found(format!("{err:#}")))
2517    }
2518
2519    async fn get_blocks_frontier(
2520        &self,
2521        height: u64,
2522        view: u64,
2523    ) -> anyhow::Result<Self::BlocksFrontier> {
2524        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2525        let ds = &*self.data_source;
2526        let view = hotshot_types::data::ViewNumber::new(view);
2527        let instance = ds.node_state().await;
2528        ds.get_frontier(&instance, height, view)
2529            .await
2530            .map_err(|err| not_found(format!("{err:#}")))
2531    }
2532
2533    async fn get_chain_config(&self, commitment: String) -> anyhow::Result<Self::ChainConfig> {
2534        use super::data_source::CatchupDataSource as _;
2535        let ds = &*self.data_source;
2536        let parsed: committable::Commitment<espresso_types::v0_3::ChainConfig> = commitment
2537            .parse()
2538            .map_err(|err| bad_request(format!("malformed chain config commitment: {err}")))?;
2539        ds.get_chain_config(parsed)
2540            .await
2541            .map_err(|err| not_found(format!("{err:#}")))
2542    }
2543
2544    async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Self::LeafChain> {
2545        use super::data_source::CatchupDataSource as _;
2546        let ds = &*self.data_source;
2547        ds.get_leaf_chain(height)
2548            .await
2549            .map_err(|err| not_found(format!("{err:#}")))
2550    }
2551
2552    async fn get_cert2(&self, height: u64) -> anyhow::Result<Self::Cert2> {
2553        use super::data_source::CatchupDataSource as _;
2554        let ds = &*self.data_source;
2555        let response = ds
2556            .get_cert2(height)
2557            .await
2558            .map_err(|err| not_found(format!("{err:#}")))?;
2559        response.ok_or_else(|| not_found(format!("no cert2 available for height {height}")))
2560    }
2561
2562    async fn get_reward_account_v1(
2563        &self,
2564        height: u64,
2565        view: u64,
2566        address: String,
2567    ) -> anyhow::Result<Self::RewardAccountQueryDataV1> {
2568        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2569        let ds = &*self.data_source;
2570        let view = hotshot_types::data::ViewNumber::new(view);
2571        let account: espresso_types::v0_4::RewardAccountV2 = address
2572            .parse()
2573            .map_err(|err| bad_request(format!("malformed reward account {address}: {err}")))?;
2574        let instance = ds.node_state().await;
2575        ds.get_reward_account_v1(&instance, height, view, account.into())
2576            .await
2577            .map_err(|err| not_found(format!("{err:#}")))
2578    }
2579
2580    async fn get_reward_accounts_v1(
2581        &self,
2582        height: u64,
2583        view: u64,
2584        accounts: Vec<Self::RewardAccountV1>,
2585    ) -> anyhow::Result<Self::RewardMerkleTreeV1> {
2586        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2587        let ds = &*self.data_source;
2588        let view = hotshot_types::data::ViewNumber::new(view);
2589        let instance = ds.node_state().await;
2590        ds.get_reward_accounts_v1(&instance, height, view, &accounts)
2591            .await
2592            .map_err(|err| not_found(format!("{err:#}")))
2593    }
2594
2595    async fn get_reward_account_v2(
2596        &self,
2597        height: u64,
2598        view: u64,
2599        address: String,
2600    ) -> anyhow::Result<Self::RewardAccountQueryDataV2> {
2601        use super::data_source::{CatchupDataSource as _, NodeStateDataSource as _};
2602        let ds = &*self.data_source;
2603        let view = hotshot_types::data::ViewNumber::new(view);
2604        let account: espresso_types::v0_4::RewardAccountV2 = address
2605            .parse()
2606            .map_err(|err| bad_request(format!("malformed reward account {address}: {err}")))?;
2607        let instance = ds.node_state().await;
2608        ds.get_reward_account_v2(&instance, height, view, account)
2609            .await
2610            .map_err(|err| not_found(format!("{err:#}")))
2611    }
2612
2613    async fn get_reward_merkle_tree_v2(
2614        &self,
2615        height: u64,
2616        view: u64,
2617    ) -> anyhow::Result<Self::RewardMerkleTreeV2Data> {
2618        use super::data_source::CatchupDataSource as _;
2619        let ds = &*self.data_source;
2620        let view = hotshot_types::data::ViewNumber::new(view);
2621        let bytes = ds
2622            .get_reward_merkle_tree_v2(height, view)
2623            .await
2624            .map_err(|err| not_found(format!("{err:#}")))?;
2625        // tide-disco returns the raw Vec<u8> from `get_reward_merkle_tree_v2`. To preserve
2626        // identical wire output, re-encode the bytes themselves as the JSON body.
2627        Ok(serde_json::to_value(bytes)?)
2628    }
2629
2630    async fn get_state_cert(&self, epoch: u64) -> anyhow::Result<Self::StateCert> {
2631        use super::data_source::CatchupDataSource as _;
2632        let ds = &*self.data_source;
2633        ds.get_state_cert(epoch)
2634            .await
2635            .map_err(|err| not_found(format!("{err:#}")))
2636    }
2637}
2638
2639// ============================================================================
2640// v1::SubmitApi implementation
2641// ============================================================================
2642
2643#[async_trait]
2644impl<D> espresso_api::v1::SubmitApi for NodeApiStateImpl<D>
2645where
2646    D: std::ops::Deref + Clone + Send + Sync + 'static,
2647    D::Target: SubmitDataSourceErased + Send + Sync,
2648{
2649    type Transaction = espresso_types::Transaction;
2650    type TxHash = committable::Commitment<espresso_types::Transaction>;
2651
2652    async fn submit(&self, tx: Self::Transaction) -> anyhow::Result<Self::TxHash> {
2653        use committable::Committable as _;
2654        let hash = tx.commit();
2655        let ds = &*self.data_source;
2656        ds.submit_erased(tx)
2657            .await
2658            .map_err(|err| anyhow::anyhow!("{err:#}"))?;
2659        Ok(hash)
2660    }
2661}
2662
2663/// Network-agnostic submit hook used by the axum wrapper. The original
2664/// `SubmitDataSource<N, P>` trait is parameterized by the network type; this
2665/// erased trait lets `NodeApiStateImpl` avoid carrying those parameters.
2666#[async_trait]
2667pub(crate) trait SubmitDataSourceErased {
2668    async fn submit_erased(&self, tx: espresso_types::Transaction) -> anyhow::Result<()>;
2669}
2670
2671#[async_trait]
2672impl<N, P, D> SubmitDataSourceErased
2673    for hotshot_query_service::data_source::ExtensibleDataSource<D, crate::api::ApiState<N, P>>
2674where
2675    N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
2676    P: espresso_types::v0::traits::SequencerPersistence,
2677    D: Send + Sync,
2678{
2679    async fn submit_erased(&self, tx: espresso_types::Transaction) -> anyhow::Result<()> {
2680        <Self as super::data_source::SubmitDataSource<N, P>>::submit(self, tx).await
2681    }
2682}
2683
2684// ============================================================================
2685// v1::StateSignatureApi implementation
2686// ============================================================================
2687
2688#[async_trait]
2689impl<D> espresso_api::v1::StateSignatureApi for NodeApiStateImpl<D>
2690where
2691    D: std::ops::Deref + Clone + Send + Sync + 'static,
2692    D::Target: StateSignatureDataSourceErased + Send + Sync,
2693{
2694    type Signature = hotshot_types::light_client::LCV3StateSignatureRequestBody;
2695
2696    async fn get_state_signature(&self, height: u64) -> anyhow::Result<Self::Signature> {
2697        let ds = &*self.data_source;
2698        ds.get_state_signature_erased(height)
2699            .await
2700            .ok_or_else(|| not_found("Signature not found."))
2701    }
2702}
2703
2704#[async_trait]
2705pub(crate) trait StateSignatureDataSourceErased {
2706    async fn get_state_signature_erased(
2707        &self,
2708        height: u64,
2709    ) -> Option<hotshot_types::light_client::LCV3StateSignatureRequestBody>;
2710}
2711
2712#[async_trait]
2713impl<N, P, D> StateSignatureDataSourceErased
2714    for hotshot_query_service::data_source::ExtensibleDataSource<D, crate::api::ApiState<N, P>>
2715where
2716    N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
2717    P: espresso_types::v0::traits::SequencerPersistence,
2718    D: Send + Sync,
2719{
2720    async fn get_state_signature_erased(
2721        &self,
2722        height: u64,
2723    ) -> Option<hotshot_types::light_client::LCV3StateSignatureRequestBody> {
2724        <Self as StateSignatureDataSource<N>>::get_state_signature(self, height).await
2725    }
2726}
2727
2728// ============================================================================
2729// v1::ExplorerApi implementation
2730// ============================================================================
2731
2732#[async_trait]
2733impl<D> espresso_api::v1::ExplorerApi for NodeApiStateImpl<D>
2734where
2735    D: std::ops::Deref + Clone + Send + Sync + 'static,
2736    D::Target:
2737        hotshot_query_service::explorer::ExplorerDataSource<espresso_types::SeqTypes> + Send + Sync,
2738{
2739    type BlockDetail =
2740        hotshot_query_service::explorer::BlockDetailResponse<espresso_types::SeqTypes>;
2741    type BlockSummaries =
2742        hotshot_query_service::explorer::BlockSummaryResponse<espresso_types::SeqTypes>;
2743    type TransactionDetail =
2744        hotshot_query_service::explorer::TransactionDetailResponse<espresso_types::SeqTypes>;
2745    type TransactionSummaries =
2746        hotshot_query_service::explorer::TransactionSummariesResponse<espresso_types::SeqTypes>;
2747    type ExplorerSummary =
2748        hotshot_query_service::explorer::ExplorerSummaryResponse<espresso_types::SeqTypes>;
2749    type SearchResult =
2750        hotshot_query_service::explorer::SearchResultResponse<espresso_types::SeqTypes>;
2751
2752    async fn get_block_detail(
2753        &self,
2754        ident: espresso_api::v1::BlockIdent,
2755    ) -> anyhow::Result<Self::BlockDetail> {
2756        use hotshot_query_service::explorer::{BlockIdentifier, ExplorerDataSource as _};
2757        let ds = &*self.data_source;
2758        let target = match ident {
2759            espresso_api::v1::BlockIdent::Height(h) => BlockIdentifier::Height(h as usize),
2760            espresso_api::v1::BlockIdent::Hash(h) => BlockIdentifier::Hash(
2761                h.parse()
2762                    .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2763            ),
2764            espresso_api::v1::BlockIdent::Latest => BlockIdentifier::Latest,
2765        };
2766        ds.get_block_detail(target)
2767            .await
2768            .map(Into::into)
2769            .map_err(|err| anyhow::anyhow!("{err}"))
2770    }
2771
2772    async fn get_block_summaries(
2773        &self,
2774        target: espresso_api::v1::BlockIdent,
2775        limit: u64,
2776    ) -> anyhow::Result<Self::BlockSummaries> {
2777        use hotshot_query_service::explorer::{
2778            BlockIdentifier, BlockRange, ExplorerDataSource as _, GetBlockSummariesRequest,
2779        };
2780        let ds = &*self.data_source;
2781        let num_blocks = std::num::NonZeroUsize::new(limit as usize)
2782            .ok_or_else(|| bad_request("limit must be greater than 0"))?;
2783        if num_blocks.get() > 100 {
2784            return Err(bad_request("limit must be <= 100"));
2785        }
2786        let target = match target {
2787            espresso_api::v1::BlockIdent::Height(h) => BlockIdentifier::Height(h as usize),
2788            espresso_api::v1::BlockIdent::Hash(h) => BlockIdentifier::Hash(
2789                h.parse()
2790                    .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2791            ),
2792            espresso_api::v1::BlockIdent::Latest => BlockIdentifier::Latest,
2793        };
2794        ds.get_block_summaries(GetBlockSummariesRequest(BlockRange { target, num_blocks }))
2795            .await
2796            .map(Into::into)
2797            .map_err(|err| anyhow::anyhow!("{err}"))
2798    }
2799
2800    async fn get_transaction_detail(
2801        &self,
2802        ident: espresso_api::v1::TxIdent,
2803    ) -> anyhow::Result<Self::TransactionDetail> {
2804        use hotshot_query_service::explorer::{ExplorerDataSource as _, TransactionIdentifier};
2805        let ds = &*self.data_source;
2806        let target = match ident {
2807            espresso_api::v1::TxIdent::HeightAndOffset(h, o) => {
2808                TransactionIdentifier::HeightAndOffset(h as usize, o as usize)
2809            },
2810            espresso_api::v1::TxIdent::Hash(h) => TransactionIdentifier::Hash(
2811                h.parse()
2812                    .map_err(|err| bad_request(format!("invalid tx hash {h}: {err}")))?,
2813            ),
2814            espresso_api::v1::TxIdent::Latest => TransactionIdentifier::Latest,
2815        };
2816        ds.get_transaction_detail(target)
2817            .await
2818            .map(Into::into)
2819            .map_err(|err| anyhow::anyhow!("{err}"))
2820    }
2821
2822    async fn get_transaction_summaries(
2823        &self,
2824        target: espresso_api::v1::TxIdent,
2825        limit: u64,
2826        filter: espresso_api::v1::TxSummaryFilter,
2827    ) -> anyhow::Result<Self::TransactionSummaries> {
2828        use hotshot_query_service::explorer::{
2829            ExplorerDataSource as _, GetTransactionSummariesRequest, TransactionIdentifier,
2830            TransactionRange, TransactionSummaryFilter,
2831        };
2832        let ds = &*self.data_source;
2833        let num_transactions = std::num::NonZeroUsize::new(limit as usize)
2834            .ok_or_else(|| bad_request("limit must be greater than 0"))?;
2835        if num_transactions.get() > 100 {
2836            return Err(bad_request("limit must be <= 100"));
2837        }
2838        let target = match target {
2839            espresso_api::v1::TxIdent::HeightAndOffset(h, o) => {
2840                TransactionIdentifier::HeightAndOffset(h as usize, o as usize)
2841            },
2842            espresso_api::v1::TxIdent::Hash(h) => TransactionIdentifier::Hash(
2843                h.parse()
2844                    .map_err(|err| bad_request(format!("invalid tx hash {h}: {err}")))?,
2845            ),
2846            espresso_api::v1::TxIdent::Latest => TransactionIdentifier::Latest,
2847        };
2848        let filter = match filter {
2849            espresso_api::v1::TxSummaryFilter::None => TransactionSummaryFilter::None,
2850            espresso_api::v1::TxSummaryFilter::Block(b) => {
2851                TransactionSummaryFilter::Block(b as usize)
2852            },
2853            espresso_api::v1::TxSummaryFilter::Namespace(n) => {
2854                TransactionSummaryFilter::RollUp(n.into())
2855            },
2856        };
2857        ds.get_transaction_summaries(GetTransactionSummariesRequest {
2858            range: TransactionRange {
2859                target,
2860                num_transactions,
2861            },
2862            filter,
2863        })
2864        .await
2865        .map(Into::into)
2866        .map_err(|err| anyhow::anyhow!("{err}"))
2867    }
2868
2869    async fn get_explorer_summary(&self) -> anyhow::Result<Self::ExplorerSummary> {
2870        use hotshot_query_service::explorer::ExplorerDataSource as _;
2871        let ds = &*self.data_source;
2872        ds.get_explorer_summary()
2873            .await
2874            .map(Into::into)
2875            .map_err(|err| anyhow::anyhow!("{err}"))
2876    }
2877
2878    async fn get_search_result(&self, query: String) -> anyhow::Result<Self::SearchResult> {
2879        use hotshot_query_service::explorer::ExplorerDataSource as _;
2880        let ds = &*self.data_source;
2881        let parsed: tagged_base64::TaggedBase64 = query
2882            .parse()
2883            .map_err(|err| bad_request(format!("invalid search query {query}: {err}")))?;
2884        ds.get_search_results(parsed)
2885            .await
2886            .map(Into::into)
2887            .map_err(|err| anyhow::anyhow!("{err}"))
2888    }
2889}
2890
2891// ============================================================================
2892// v1::LightClientApi implementation
2893// ============================================================================
2894
2895#[async_trait]
2896impl<D> espresso_api::v1::LightClientApi for NodeApiStateImpl<D>
2897where
2898    D: std::ops::Deref + Clone + Send + Sync + 'static,
2899    D::Target: AvailabilityDataSource<espresso_types::SeqTypes>
2900        + hotshot_query_service::merklized_state::MerklizedStateDataSource<
2901            espresso_types::SeqTypes,
2902            espresso_types::BlockMerkleTree,
2903            3,
2904        > + super::data_source::NodeStateDataSource
2905        + super::data_source::StakeTableDataSource<espresso_types::SeqTypes>
2906        + hotshot_query_service::data_source::VersionedDataSource
2907        + Sized
2908        + Send
2909        + Sync,
2910    for<'a> <D::Target as hotshot_query_service::data_source::VersionedDataSource>::ReadOnly<'a>:
2911        hotshot_query_service::data_source::storage::NodeStorage<espresso_types::SeqTypes>,
2912{
2913    type LeafProof = light_client::consensus::leaf::LeafProof;
2914    type HeaderProof = light_client::consensus::header::HeaderProof;
2915    type StakeTableEvents = Vec<espresso_types::v0_3::StakeTableEvent>;
2916    type PayloadProof = light_client::consensus::payload::PayloadProof;
2917    type NamespaceProof = light_client::consensus::namespace::NamespaceProof;
2918
2919    async fn get_leaf_proof(
2920        &self,
2921        query: espresso_api::v1::LeafQuery,
2922        finalized: Option<u64>,
2923    ) -> anyhow::Result<Self::LeafProof> {
2924        use hotshot_query_service::availability::LeafId;
2925        let ds = &*self.data_source;
2926        let fetch_timeout = lc_fetch_timeout();
2927
2928        let requested = match query {
2929            espresso_api::v1::LeafQuery::Height(h) => LeafId::Number(h as usize),
2930            espresso_api::v1::LeafQuery::Hash(h) => LeafId::Hash(
2931                h.parse()
2932                    .map_err(|err| bad_request(format!("invalid leaf hash {h}: {err}")))?,
2933            ),
2934            espresso_api::v1::LeafQuery::BlockHash(h) => {
2935                let parsed = h
2936                    .parse()
2937                    .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?;
2938                let header = AvailabilityDataSource::get_header(ds, HsBlockId::Hash(parsed))
2939                    .await
2940                    .with_timeout(fetch_timeout)
2941                    .await
2942                    .ok_or_else(|| not_found(format!("unknown block hash {h}")))?;
2943                LeafId::Number(header.height() as usize)
2944            },
2945            espresso_api::v1::LeafQuery::PayloadHash(h) => {
2946                let parsed = h
2947                    .parse()
2948                    .map_err(|err| bad_request(format!("invalid payload hash {h}: {err}")))?;
2949                let header = AvailabilityDataSource::get_header(ds, HsBlockId::PayloadHash(parsed))
2950                    .await
2951                    .with_timeout(fetch_timeout)
2952                    .await
2953                    .ok_or_else(|| not_found(format!("unknown payload hash {h}")))?;
2954                LeafId::Number(header.height() as usize)
2955            },
2956        };
2957
2958        let requested_leaf = AvailabilityDataSource::get_leaf(ds, requested)
2959            .await
2960            .with_timeout(fetch_timeout)
2961            .await
2962            .ok_or_else(|| not_found(format!("unknown leaf {requested}")))?;
2963
2964        crate::api::light_client::get_leaf_proof(
2965            ds,
2966            requested_leaf,
2967            finalized.map(|f| f as usize),
2968            fetch_timeout,
2969            lc_leaf_proof_chain_limit(),
2970        )
2971        .await
2972        .map_err(|err| anyhow::anyhow!("{err}"))
2973    }
2974
2975    async fn get_header_proof(
2976        &self,
2977        root: u64,
2978        requested: espresso_api::v1::HeaderQuery,
2979    ) -> anyhow::Result<Self::HeaderProof> {
2980        let ds = &*self.data_source;
2981        let fetch_timeout = lc_fetch_timeout();
2982        let requested = match requested {
2983            espresso_api::v1::HeaderQuery::Height(h) => HsBlockId::Number(h as usize),
2984            espresso_api::v1::HeaderQuery::Hash(h) => HsBlockId::Hash(
2985                h.parse()
2986                    .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2987            ),
2988            espresso_api::v1::HeaderQuery::PayloadHash(h) => HsBlockId::PayloadHash(
2989                h.parse()
2990                    .map_err(|err| bad_request(format!("invalid payload hash {h}: {err}")))?,
2991            ),
2992        };
2993        crate::api::light_client::get_header_proof(ds, root, requested, fetch_timeout)
2994            .await
2995            .map_err(|err| anyhow::anyhow!("{err}"))
2996    }
2997
2998    async fn get_light_client_stake_table(
2999        &self,
3000        epoch: u64,
3001    ) -> anyhow::Result<Self::StakeTableEvents> {
3002        use hotshot_types::utils::{epoch_from_block_number, root_block_in_epoch};
3003        let ds = &*self.data_source;
3004        let fetch_timeout = lc_fetch_timeout();
3005
3006        let node_state = super::data_source::NodeStateDataSource::node_state(ds).await;
3007        let epoch_height = node_state
3008            .epoch_height
3009            .ok_or_else(|| anyhow::anyhow!("epoch state not set"))?;
3010        let first_epoch = epoch_from_block_number(node_state.epoch_start_block, epoch_height);
3011        if epoch < first_epoch + 2 {
3012            return Err(bad_request(format!(
3013                "epoch must be at least {}",
3014                first_epoch + 2
3015            )));
3016        }
3017
3018        let epoch_root_height = root_block_in_epoch(epoch - 2, epoch_height) as usize;
3019        let epoch_root = AvailabilityDataSource::get_header::<HsBlockId<espresso_types::SeqTypes>>(
3020            ds,
3021            HsBlockId::Number(epoch_root_height),
3022        )
3023        .await
3024        .with_timeout(fetch_timeout)
3025        .await
3026        .ok_or_else(|| not_found(format!("missing epoch root header {epoch_root_height}")))?;
3027        let to_l1_block = epoch_root
3028            .l1_finalized()
3029            .ok_or_else(|| anyhow::anyhow!("epoch root header is missing L1 finalized block"))?
3030            .number();
3031
3032        let from_l1_block = if epoch >= first_epoch + 3 {
3033            let prev_epoch_root_height = root_block_in_epoch(epoch - 3, epoch_height) as usize;
3034            let prev_epoch_root = AvailabilityDataSource::get_header::<
3035                HsBlockId<espresso_types::SeqTypes>,
3036            >(ds, HsBlockId::Number(prev_epoch_root_height))
3037            .await
3038            .with_timeout(fetch_timeout)
3039            .await
3040            .ok_or_else(|| {
3041                not_found(format!(
3042                    "missing previous epoch root header {prev_epoch_root_height}"
3043                ))
3044            })?;
3045            prev_epoch_root
3046                .l1_finalized()
3047                .ok_or_else(|| {
3048                    anyhow::anyhow!("previous epoch root header is missing L1 finalized block")
3049                })?
3050                .number()
3051                + 1
3052        } else {
3053            0
3054        };
3055
3056        super::data_source::StakeTableDataSource::stake_table_events(ds, from_l1_block, to_l1_block)
3057            .await
3058    }
3059
3060    async fn get_payload_proof(&self, height: u64) -> anyhow::Result<Self::PayloadProof> {
3061        let ds = &*self.data_source;
3062        let fetch_timeout = lc_fetch_timeout();
3063        let height = height as usize;
3064        let payload = AvailabilityDataSource::get_payload(ds, height)
3065            .await
3066            .with_timeout(fetch_timeout)
3067            .await
3068            .ok_or_else(|| not_found(format!("missing payload {height}")))?;
3069        let vid_common = AvailabilityDataSource::get_vid_common(ds, height)
3070            .await
3071            .with_timeout(fetch_timeout)
3072            .await
3073            .ok_or_else(|| not_found(format!("missing VID common {height}")))?;
3074        Ok(light_client::consensus::payload::PayloadProof::new(
3075            payload.data().clone(),
3076            vid_common.common().clone(),
3077        ))
3078    }
3079
3080    async fn get_payload_proof_range(
3081        &self,
3082        start: u64,
3083        end: u64,
3084    ) -> anyhow::Result<Vec<Self::PayloadProof>> {
3085        use futures::StreamExt as _;
3086        let ds = &*self.data_source;
3087        let fetch_timeout = lc_fetch_timeout();
3088        let start = start as usize;
3089        let end = end as usize;
3090
3091        let payloads_stream = AvailabilityDataSource::get_payload_range(ds, start..end).await;
3092        let vid_stream = AvailabilityDataSource::get_vid_common_range(ds, start..end).await;
3093        let mut out = Vec::new();
3094        let mut payloads = payloads_stream.enumerate();
3095        let mut vid_commons = vid_stream.enumerate();
3096        loop {
3097            let (next_payload, next_vid) =
3098                futures::future::join(payloads.next(), vid_commons.next()).await;
3099            let (Some((i, payload_fut)), Some((_, vid_fut))) = (next_payload, next_vid) else {
3100                break;
3101            };
3102            let payload = payload_fut
3103                .with_timeout(fetch_timeout)
3104                .await
3105                .ok_or_else(|| not_found(format!("missing payload {}", start + i)))?;
3106            let vid_common = vid_fut
3107                .with_timeout(fetch_timeout)
3108                .await
3109                .ok_or_else(|| not_found(format!("missing VID common {}", start + i)))?;
3110            out.push(light_client::consensus::payload::PayloadProof::new(
3111                payload.data().clone(),
3112                vid_common.common().clone(),
3113            ));
3114        }
3115        Ok(out)
3116    }
3117
3118    async fn get_lc_namespace_proof(
3119        &self,
3120        height: u64,
3121        namespace: u64,
3122    ) -> anyhow::Result<Self::NamespaceProof> {
3123        let ds = &*self.data_source;
3124        let fetch_timeout = lc_fetch_timeout();
3125        let mut proofs = crate::api::light_client::get_namespace_proof_range(
3126            ds,
3127            height as usize,
3128            (height + 1) as usize,
3129            namespace,
3130            fetch_timeout,
3131            lc_large_object_range_limit(),
3132        )
3133        .await
3134        .map_err(|err| anyhow::anyhow!("{err}"))?;
3135        if proofs.len() != 1 {
3136            return Err(anyhow::anyhow!("internal consistency error"));
3137        }
3138        Ok(proofs.remove(0))
3139    }
3140
3141    async fn get_lc_namespace_proof_range(
3142        &self,
3143        start: u64,
3144        end: u64,
3145        namespace: u64,
3146    ) -> anyhow::Result<Vec<Self::NamespaceProof>> {
3147        let ds = &*self.data_source;
3148        let fetch_timeout = lc_fetch_timeout();
3149        crate::api::light_client::get_namespace_proof_range(
3150            ds,
3151            start as usize,
3152            end as usize,
3153            namespace,
3154            fetch_timeout,
3155            lc_large_object_range_limit(),
3156        )
3157        .await
3158        .map_err(|err| anyhow::anyhow!("{err}"))
3159    }
3160}
3161
3162fn lc_fetch_timeout() -> std::time::Duration {
3163    std::time::Duration::from_millis(500)
3164}
3165
3166fn lc_large_object_range_limit() -> usize {
3167    hotshot_query_service::availability::Options::default().large_object_range_limit
3168}
3169
3170fn lc_leaf_proof_chain_limit() -> usize {
3171    crate::api::light_client::Options::default().leaf_proof_chain_limit
3172}
3173
3174// ============================================================================
3175// v1::HotShotEventsApi implementation
3176// ============================================================================
3177
3178#[async_trait]
3179impl<D> espresso_api::v1::HotShotEventsApi for NodeApiStateImpl<D>
3180where
3181    D: std::ops::Deref + Clone + Send + Sync + 'static,
3182    D::Target:
3183        hotshot_events_service::events_source::EventsSource<espresso_types::SeqTypes> + Send + Sync,
3184{
3185    type Event = std::sync::Arc<hotshot_types::event::Event<espresso_types::SeqTypes>>;
3186    type StartupInfo = hotshot_events_service::events_source::StartupInfo<espresso_types::SeqTypes>;
3187
3188    async fn startup_info(&self) -> anyhow::Result<Self::StartupInfo> {
3189        use hotshot_events_service::events_source::EventsSource as _;
3190        let ds = &*self.data_source;
3191        Ok(ds.get_startup_info().await)
3192    }
3193
3194    async fn events(&self) -> anyhow::Result<futures::stream::BoxStream<'static, Self::Event>> {
3195        use hotshot_events_service::events_source::EventsSource as _;
3196        let ds = &*self.data_source;
3197        let stream = ds.get_event_stream(None).await;
3198        Ok(Box::pin(stream))
3199    }
3200}
3201
3202// ============================================================================
3203// v1::TokenApi implementation
3204// ============================================================================
3205
3206#[async_trait]
3207impl<D> espresso_api::v1::TokenApi for NodeApiStateImpl<D>
3208where
3209    D: std::ops::Deref + Clone + Send + Sync + 'static,
3210    D::Target: super::data_source::TokenDataSource<espresso_types::SeqTypes>
3211        + super::data_source::NodeStateDataSource
3212        + Send
3213        + Sync,
3214{
3215    async fn total_minted_supply(&self) -> anyhow::Result<String> {
3216        use super::data_source::TokenDataSource as _;
3217        let ds = &*self.data_source;
3218        let value = ds
3219            .get_total_supply_l1()
3220            .await
3221            .map_err(|err| not_found(format!("failed to get total supply. err={err:#}")))?;
3222        Ok(alloy::primitives::utils::format_ether(value))
3223    }
3224
3225    async fn circulating_supply(&self) -> anyhow::Result<String> {
3226        let calc = fetch_supply_inputs(&*self.data_source).await?;
3227        Ok(alloy::primitives::utils::format_ether(
3228            calc.circulating_supply(),
3229        ))
3230    }
3231
3232    async fn circulating_supply_ethereum(&self) -> anyhow::Result<String> {
3233        let calc = fetch_supply_inputs(&*self.data_source).await?;
3234        Ok(alloy::primitives::utils::format_ether(
3235            calc.circulating_supply_ethereum(),
3236        ))
3237    }
3238
3239    async fn total_issued_supply(&self) -> anyhow::Result<String> {
3240        let calc = fetch_supply_inputs(&*self.data_source).await?;
3241        Ok(alloy::primitives::utils::format_ether(
3242            calc.total_issued_supply(),
3243        ))
3244    }
3245
3246    async fn total_reward_distributed(&self) -> anyhow::Result<String> {
3247        let calc = fetch_supply_inputs(&*self.data_source).await?;
3248        Ok(alloy::primitives::utils::format_ether(
3249            calc.total_reward_distributed(),
3250        ))
3251    }
3252}
3253
3254async fn fetch_supply_inputs<S>(
3255    ds: &S,
3256) -> anyhow::Result<crate::api::unlock_schedule::SupplyCalculator>
3257where
3258    S: super::data_source::TokenDataSource<espresso_types::SeqTypes>
3259        + super::data_source::NodeStateDataSource
3260        + Sync
3261        + ?Sized,
3262{
3263    let node_state = ds.node_state().await;
3264    let chain_id = node_state.chain_config.chain_id;
3265
3266    let header = ds.get_decided_header().await;
3267    let now_secs = header.timestamp_internal();
3268    let total_reward_distributed = header.total_reward_distributed();
3269
3270    let initial_supply = ds
3271        .get_initial_supply_l1()
3272        .await
3273        .map_err(|err| anyhow::anyhow!("failed to get initial supply: {err:#}"))?;
3274
3275    let total_supply_l1 = ds
3276        .get_total_supply_l1()
3277        .await
3278        .map_err(|err| anyhow::anyhow!("failed to get total supply: {err:#}"))?;
3279
3280    Ok(crate::api::unlock_schedule::SupplyCalculator::new(
3281        chain_id,
3282        now_secs,
3283        initial_supply,
3284        total_supply_l1,
3285        total_reward_distributed,
3286    ))
3287}
3288
3289// ============================================================================
3290// v1::DatabaseApi implementation
3291// ============================================================================
3292
3293#[async_trait]
3294impl<D> espresso_api::v1::DatabaseApi for NodeApiStateImpl<D>
3295where
3296    D: std::ops::Deref + Clone + Send + Sync + 'static,
3297    D::Target: super::data_source::DatabaseMetadataSource + Send + Sync,
3298{
3299    type TableSizes = Vec<super::data_source::TableSize>;
3300
3301    async fn get_table_sizes(&self) -> anyhow::Result<Self::TableSizes> {
3302        use super::data_source::DatabaseMetadataSource as _;
3303        let ds = &*self.data_source;
3304        ds.get_table_sizes().await
3305    }
3306}