1use 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#[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 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 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 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 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
192impl<D> serialization_api::ApiSerializations for NodeApiStateImpl<D>
197where
198 D: std::ops::Deref + Send + Sync + 'static,
199 D::Target: RewardMerkleTreeDataSource + Send + Sync,
200{
201 type Address = alloy::primitives::Address;
203
204 type RewardClaimInput = InternalRewardClaimInput;
206 type RewardBalance = U256;
207 type RewardAccountQueryData = InternalRewardAccountQueryData;
208 type RewardBalances = (Vec<(RewardAccountV2, InternalRewardAmount)>, u64); type RewardMerkleTreeData = InternalRewardTreeData;
210
211 type NamespaceProof = espresso_types::NamespaceProofQueryData;
213 type IncorrectEncodingProof = espresso_types::v0_3::AvidMIncorrectEncodingNsProof;
214
215 type StateCertificate = espresso_types::StateCertQueryDataV2<espresso_types::SeqTypes>;
217 type StakeTable = Vec<hotshot_types::PeerConfig<espresso_types::SeqTypes>>;
218
219 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 fn serialize_reward_claim_input(
233 &self,
234 address: &str,
235 value: &Self::RewardClaimInput,
236 ) -> anyhow::Result<RewardClaimInput> {
237 let auth_data = serde_json::to_string(&value.auth_data)
239 .map_err(|e| anyhow::anyhow!("failed to serialize auth_data: {}", e))?
240 .trim_matches('"')
242 .to_string();
243
244 Ok(RewardClaimInput {
245 address: address.to_string(),
246 lifetime_rewards: format!("{:#x}", value.lifetime_rewards), 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(), })
258 }
259
260 fn serialize_reward_account_query_data(
261 &self,
262 value: &Self::RewardAccountQueryData,
263 ) -> anyhow::Result<RewardAccountQueryDataV2> {
264 let balance = value.balance.to_string();
266
267 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 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(), })
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 fn serialize_namespace_proof(
306 &self,
307 value: &Self::NamespaceProof,
308 ) -> anyhow::Result<v2::NamespaceProofResponse> {
309 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 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 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 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 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 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 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 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#[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 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 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 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 Ok(proof.balance)
571 }
572
573 async fn get_reward_account_proof(
574 &self,
575 address: Self::Address,
576 ) -> anyhow::Result<Self::RewardAccountQueryData> {
577 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 if limit > 10000 {
598 return Err(anyhow::anyhow!(
599 "limit {} exceeds maximum allowed value of 10000",
600 limit
601 ));
602 }
603
604 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 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 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 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 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 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#[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 let addr: alloy::primitives::Address = address
678 .parse()
679 .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
680
681 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 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 let addr: alloy::primitives::Address = address
724 .parse()
725 .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
726
727 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 let addr: alloy::primitives::Address = address
770 .parse()
771 .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
772
773 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 let addr: alloy::primitives::Address = address
796 .parse()
797 .map_err(|_| anyhow::anyhow!("invalid ethereum address: {}", address))?;
798
799 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 if limit > 10000 {
819 return Err(anyhow::anyhow!(
820 "limit {} exceeds maximum allowed value of 10000",
821 limit
822 ));
823 }
824
825 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 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 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#[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 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 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 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; 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 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 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 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 proofs.push(espresso_types::NamespaceProofQueryData {
1000 transactions: vec![],
1001 proof: None,
1002 });
1003 }
1004 } else {
1005 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 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#[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 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 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 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#[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 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 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 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 let Some(proof) = NsProof::new(block.payload(), &ns_index, vid_common.common()) else {
1208 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 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 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 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 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 proofs.push(espresso_types::NamespaceProofQueryData {
1288 transactions: vec![],
1289 proof: None,
1290 });
1291 }
1292 } else {
1293 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 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 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 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 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 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 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 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
1485fn 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#[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#[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#[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#[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 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#[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#[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#[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#[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#[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#[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#[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#[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}