1use std::{
5 ops::{Bound, Deref},
6 time::Duration,
7};
8
9use alloy::primitives::utils::format_ether;
10use async_trait::async_trait;
11use committable::Committable as _;
12use disco_types::{error::Error as _, status::StatusCode};
13use espresso_api::{
14 error::{AvailabilityError, to_status},
15 proto,
16 v1::{self, HotShotAvailabilityApi},
17};
18use espresso_types::{
19 NamespaceId, NamespaceProofQueryData, NsProof, SeqTypes,
20 v0::sparse_mt::KeccakNode,
21 v0_3::{RewardAccountV1, RewardAmount as InternalRewardAmount, RewardMerkleTreeV1},
22 v0_4::{
23 RewardAccountQueryDataV2 as InternalRewardAccountQueryData, RewardAccountV2,
24 RewardMerkleTreeV2,
25 },
26 v0_6::RewardClaimError,
27};
28use futures::{StreamExt as _, join, stream::BoxStream};
29use hotshot_contract_adapter::reward::RewardClaimInput as InternalRewardClaimInput;
30use hotshot_events_service::events_source::EventsSource as _;
31use hotshot_new_protocol::message::Certificate2;
32use hotshot_query_service::{
33 Header as HsHeader, QueryError,
34 availability::{
35 AvailabilityDataSource, BlockId as HsBlockId, BlockQueryData, BlockSummaryQueryData,
36 LeafId as HsLeafId, LeafQueryData, Limits as HsLimits, PayloadQueryData,
37 QueryablePayload as _, TransactionQueryData, TransactionWithProofQueryData,
38 VidCommonQueryData,
39 },
40 explorer::{
41 BlockIdentifier, BlockRange, ExplorerDataSource as _, GetBlockSummariesRequest,
42 GetTransactionSummariesRequest, TransactionIdentifier, TransactionRange,
43 TransactionSummaryFilter,
44 },
45 merklized_state::{
46 MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot as HsSnapshot,
47 },
48 node::{NodeDataSource as _, WindowStart},
49 status::HasMetrics as _,
50 types::HeightIndexed as _,
51};
52use hotshot_types::{
53 data::VidShare,
54 utils::{epoch_from_block_number, root_block_in_epoch},
55 vid::avidm::AvidMShare,
56};
57use jf_merkle_tree_compat::prelude::{
58 MerkleProof as InternalMerkleProof, MerkleProof as JfMerkleProof,
59};
60use prometheus::Encoder as _;
61use serde_json;
62use tagged_base64::TaggedBase64;
63
64use super::{
65 RewardMerkleTreeDataSource, RewardMerkleTreeV2Data as InternalRewardTreeData,
66 data_source::{
67 CatchupDataSource, DatabaseMetadataSource, HotShotConfigDataSource, MigrationStatus,
68 NodeKeysDataSource, NodePublicKeys, NodeStateDataSource, PruningDataSource,
69 RequestResponseDataSource, StakeTableDataSource, StakeTableWithEpochNumber,
70 StateCertDataSource, StateCertFetchingDataSource, StateSignatureDataSource,
71 SubmitDataSource, TableSize, TokenDataSource,
72 },
73};
74
75const FETCH_TIMEOUT: Duration = Duration::from_millis(500);
83
84#[derive(Clone)]
89pub struct NodeApiStateImpl<D> {
90 data_source: D,
91 env_vars: std::sync::Arc<Vec<String>>,
92 public_node_config: Option<std::sync::Arc<crate::options::PublicNodeConfig>>,
93}
94
95impl<D> NodeApiStateImpl<D> {
96 pub fn new(data_source: D) -> Self {
97 Self {
98 data_source,
99 env_vars: std::sync::Arc::new(Vec::new()),
100 public_node_config: None,
101 }
102 }
103
104 pub fn with_env_vars(mut self, env_vars: Vec<String>) -> Self {
105 self.env_vars = std::sync::Arc::new(env_vars);
106 self
107 }
108
109 pub fn with_public_node_config(
110 mut self,
111 config: Option<crate::options::PublicNodeConfig>,
112 ) -> Self {
113 self.public_node_config = config.map(std::sync::Arc::new);
114 self
115 }
116}
117
118#[async_trait]
119impl<D> v1::RewardApi for NodeApiStateImpl<D>
120where
121 D: RewardMerkleTreeDataSource + Deref,
122 D::Target: hotshot_query_service::merklized_state::MerklizedStateHeightPersistence
123 + hotshot_query_service::merklized_state::MerklizedStateDataSource<
124 SeqTypes,
125 espresso_types::v0_3::RewardMerkleTreeV1,
126 {
127 <espresso_types::v0_3::RewardMerkleTreeV1 as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY
128 },
129 > + hotshot_query_service::merklized_state::MerklizedStateDataSource<
130 SeqTypes,
131 espresso_types::v0_4::RewardMerkleTreeV2,
132 {
133 <espresso_types::v0_4::RewardMerkleTreeV2 as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY
134 },
135 > + Send
136 + Sync,
137{
138 type RewardClaimInput = InternalRewardClaimInput;
139 type RewardBalance = InternalRewardAmount;
140 type RewardAccountQueryData = InternalRewardAccountQueryData;
141 type RewardAmounts = Vec<(alloy::primitives::Address, InternalRewardAmount)>;
142 type RewardMerkleTreeData = Vec<u8>;
143 type RewardAccountQueryDataV1 = espresso_types::v0_3::RewardAccountQueryDataV1;
144 type RewardStatePathV1 = InternalMerkleProof<
145 InternalRewardAmount,
146 espresso_types::v0_3::RewardAccountV1,
147 jf_merkle_tree_compat::prelude::Sha3Node,
148 {
149 <espresso_types::v0_3::RewardMerkleTreeV1 as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY
150 },
151 >;
152 type RewardStatePathV2 = InternalMerkleProof<
153 InternalRewardAmount,
154 RewardAccountV2,
155 KeccakNode,
156 {
157 <espresso_types::v0_4::RewardMerkleTreeV2 as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY
158 },
159 >;
160
161 async fn get_reward_state_height(&self) -> anyhow::Result<u64> {
162 let ds = &*self.data_source;
163 ds.get_last_state_height()
164 .await
165 .map(|h| h as u64)
166 .map_err(classify_query_error)
167 }
168
169 async fn get_reward_state_v2_height(&self) -> anyhow::Result<u64> {
170 self.get_reward_state_height().await
173 }
174
175 async fn get_reward_account_proof_v1(
176 &self,
177 height: u64,
178 address: String,
179 ) -> anyhow::Result<Self::RewardAccountQueryDataV1> {
180 let account: RewardAccountV1 = address
181 .parse()
182 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
183
184 self.data_source
185 .load_v1_reward_account_proof(height, account)
186 .await
187 .map_err(|err| {
188 not_found(format!(
189 "failed to load v1 reward account {} at height {}: {}",
190 address, height, err
191 ))
192 })
193 }
194
195 async fn get_reward_claim_input(
196 &self,
197 block_height: u64,
198 address: String,
199 ) -> anyhow::Result<Self::RewardClaimInput> {
200 let addr: alloy::primitives::Address = address
202 .parse()
203 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
204
205 let proof = self
207 .data_source
208 .load_reward_account_proof_v2(block_height, addr.into())
209 .await
210 .map_err(|err| {
211 not_found(format!(
212 "failed to load reward account {} at height {}: {}",
213 address, block_height, err
214 ))
215 })?;
216
217 let claim_input = proof.to_reward_claim_input().map_err(|err| match err {
219 RewardClaimError::ZeroRewardError => not_found(format!(
220 "zero reward balance for {} at height {}",
221 address, block_height
222 )),
223 RewardClaimError::ProofConversionError(e) => {
224 anyhow::anyhow!(
225 "failed to create solidity proof for {} at height {}: {}",
226 address,
227 block_height,
228 e
229 )
230 },
231 })?;
232
233 Ok(claim_input)
234 }
235
236 async fn get_reward_balance(
237 &self,
238 height: u64,
239 address: String,
240 ) -> anyhow::Result<Self::RewardBalance> {
241 let addr: alloy::primitives::Address = address
243 .parse()
244 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
245
246 let proof = self
248 .data_source
249 .load_reward_account_proof_v2(height, addr.into())
250 .await
251 .map_err(|err| {
252 not_found(format!(
253 "failed to load reward account {} at height {}: {}",
254 address, height, err
255 ))
256 })?;
257
258 Ok(InternalRewardAmount(proof.balance))
259 }
260
261 async fn get_latest_reward_balance(
262 &self,
263 address: String,
264 ) -> anyhow::Result<Self::RewardBalance> {
265 let addr: alloy::primitives::Address = address
266 .parse()
267 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
268
269 let proof = self
270 .data_source
271 .load_latest_reward_account_proof_v2(addr.into())
272 .await
273 .map_err(|err| {
274 not_found(format!(
275 "failed to load latest reward account {}: {}",
276 address, err
277 ))
278 })?;
279
280 Ok(InternalRewardAmount(proof.balance))
281 }
282
283 async fn get_reward_account_proof(
284 &self,
285 height: u64,
286 address: String,
287 ) -> anyhow::Result<Self::RewardAccountQueryData> {
288 let addr: alloy::primitives::Address = address
290 .parse()
291 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
292
293 let proof = self
295 .data_source
296 .load_reward_account_proof_v2(height, addr.into())
297 .await
298 .map_err(|err| {
299 not_found(format!(
300 "failed to load reward account {} at height {}: {}",
301 address, height, err
302 ))
303 })?;
304
305 Ok(proof)
306 }
307
308 async fn get_latest_reward_account_proof(
309 &self,
310 address: String,
311 ) -> anyhow::Result<Self::RewardAccountQueryData> {
312 let addr: alloy::primitives::Address = address
314 .parse()
315 .map_err(|_| bad_request(format!("invalid ethereum address: {}", address)))?;
316
317 let proof = self
318 .data_source
319 .load_latest_reward_account_proof_v2(addr.into())
320 .await
321 .map_err(|err| {
322 not_found(format!(
323 "failed to load latest reward account {}: {}",
324 address, err
325 ))
326 })?;
327
328 Ok(proof)
329 }
330
331 async fn get_reward_amounts(
332 &self,
333 height: u64,
334 offset: u64,
335 limit: u64,
336 ) -> anyhow::Result<Self::RewardAmounts> {
337 if limit > 10000 {
338 return Err(bad_request(format!(
339 "limit {} exceeds maximum allowed value of 10000",
340 limit
341 )));
342 }
343
344 let tree_bytes = self.data_source.load_tree(height).await.map_err(|err| {
345 not_found(format!(
346 "failed to load reward tree at height {}: {}",
347 height, err
348 ))
349 })?;
350
351 let tree_data: InternalRewardTreeData =
352 bincode::deserialize(&tree_bytes).map_err(|err| {
353 not_found(format!(
354 "failed to deserialize RewardMerkleTreeV2Data at height {}: {}",
355 height, err
356 ))
357 })?;
358
359 let offset_usize = offset as usize;
360 let limit_usize = limit as usize;
361
362 if offset_usize > tree_data.balances.len() {
363 return Err(not_found(format!("offset {} out of bounds", offset)));
364 }
365
366 let end = std::cmp::min(offset_usize + limit_usize, tree_data.balances.len());
367 let slice = &tree_data.balances[offset_usize..end];
368
369 let result: Vec<(alloy::primitives::Address, InternalRewardAmount)> = slice
370 .iter()
371 .rev()
372 .map(|(account, amount)| (account.0, *amount))
373 .collect();
374
375 Ok(result)
376 }
377
378 async fn get_reward_merkle_tree_v2(
379 &self,
380 height: u64,
381 ) -> anyhow::Result<Self::RewardMerkleTreeData> {
382 self.data_source.load_tree(height).await.map_err(|err| {
383 not_found(format!(
384 "failed to load reward tree at height {}: {}",
385 height, err
386 ))
387 })
388 }
389
390 async fn get_reward_state_path_v1(
391 &self,
392 snapshot: v1::Snapshot,
393 key: String,
394 ) -> anyhow::Result<Self::RewardStatePathV1> {
395 let hs_snapshot = match snapshot {
396 v1::Snapshot::Height(h) => HsSnapshot::Index(h),
397 v1::Snapshot::Commit(c) => {
398 let tb64: TaggedBase64 = c
399 .parse()
400 .map_err(|_| bad_request("failed to parse commit param"))?;
401 let commit = (&tb64)
402 .try_into()
403 .map_err(|_| bad_request("failed to parse commit param"))?;
404 HsSnapshot::Commit(commit)
405 },
406 };
407 let key: RewardAccountV1 = key
408 .parse()
409 .map_err(|_| bad_request("failed to parse Key param"))?;
410 let ds = &*self.data_source;
411 MerklizedStateDataSource::<SeqTypes, RewardMerkleTreeV1, _>::get_path(ds, hs_snapshot, key)
412 .await
413 .map_err(classify_query_error)
414 }
415
416 async fn get_reward_state_path_v2(
417 &self,
418 snapshot: v1::Snapshot,
419 key: String,
420 ) -> anyhow::Result<Self::RewardStatePathV2> {
421 let hs_snapshot = match snapshot {
422 v1::Snapshot::Height(h) => HsSnapshot::Index(h),
423 v1::Snapshot::Commit(c) => {
424 let tb64: TaggedBase64 = c
425 .parse()
426 .map_err(|_| bad_request("failed to parse commit param"))?;
427 let commit = (&tb64)
428 .try_into()
429 .map_err(|_| bad_request("failed to parse commit param"))?;
430 HsSnapshot::Commit(commit)
431 },
432 };
433 let key: RewardAccountV2 = key
434 .parse()
435 .map_err(|_| bad_request("failed to parse Key param"))?;
436 let ds = &*self.data_source;
437 MerklizedStateDataSource::<SeqTypes, RewardMerkleTreeV2, _>::get_path(ds, hs_snapshot, key)
438 .await
439 .map_err(classify_query_error)
440 }
441}
442
443#[async_trait]
444impl<D> v1::AvailabilityApi for NodeApiStateImpl<D>
445where
446 D: Deref + Clone + Send + Sync + 'static,
447 D::Target: hotshot_query_service::availability::AvailabilityDataSource<SeqTypes>
451 + hotshot_query_service::node::NodeDataSource<SeqTypes>
452 + RequestResponseDataSource<SeqTypes>
453 + StateCertDataSource
454 + StateCertFetchingDataSource<SeqTypes>
455 + Send
456 + Sync,
457{
458 type NamespaceProofQueryData = espresso_types::NamespaceProofQueryData;
459 type IncorrectEncodingProof = espresso_types::v0_3::AvidMIncorrectEncodingNsProof;
460 type StateCertQueryDataV1 = espresso_types::StateCertQueryDataV1<SeqTypes>;
461 type StateCertQueryDataV2 = espresso_types::StateCertQueryDataV2<SeqTypes>;
462
463 async fn get_namespace_proof(
464 &self,
465 block_id: v1::availability::BlockId,
466 namespace: u32,
467 ) -> anyhow::Result<Self::NamespaceProofQueryData> {
468 let ns_id = NamespaceId::from(namespace);
469
470 let hs_block_id = match block_id {
472 v1::availability::BlockId::Height(h) => HsBlockId::Number(h as usize),
473 v1::availability::BlockId::Hash(h) => {
474 let hash = h
475 .parse()
476 .map_err(|_| bad_request(format!("invalid block hash: {}", h)))?;
477 HsBlockId::Hash(hash)
478 },
479 v1::availability::BlockId::PayloadHash(h) => {
480 let payload_hash = h
481 .parse()
482 .map_err(|_| bad_request(format!("invalid payload hash: {}", h)))?;
483 HsBlockId::PayloadHash(payload_hash)
484 },
485 };
486
487 let ds = &*self.data_source;
489 let timeout = FETCH_TIMEOUT;
490 let (block_fetch, vid_fetch) =
491 join!(ds.get_block(hs_block_id), ds.get_vid_common(hs_block_id));
492 let (block, vid_common) = join!(
493 block_fetch.with_timeout(timeout),
494 vid_fetch.with_timeout(timeout)
495 );
496
497 let block =
498 block.ok_or_else(|| not_found(format!("block {} not available", hs_block_id)))?;
499 let vid_common = vid_common.ok_or_else(|| {
500 not_found(format!(
501 "VID common for block {} not available",
502 hs_block_id
503 ))
504 })?;
505
506 let ns_table = block.payload().ns_table();
508 let Some(ns_index) = ns_table.find_ns_id(&ns_id) else {
509 return Ok(espresso_types::NamespaceProofQueryData {
510 transactions: vec![],
511 proof: None,
512 });
513 };
514
515 let Some(proof) = NsProof::new(block.payload(), &ns_index, vid_common.common()) else {
517 return Ok(espresso_types::NamespaceProofQueryData {
519 transactions: vec![],
520 proof: None,
521 });
522 };
523
524 let transactions = proof.export_all_txs(&ns_id);
525
526 Ok(espresso_types::NamespaceProofQueryData {
527 transactions,
528 proof: Some(proof),
529 })
530 }
531
532 async fn get_namespace_proof_range(
533 &self,
534 from: u64,
535 until: u64,
536 namespace: u32,
537 ) -> anyhow::Result<Vec<Self::NamespaceProofQueryData>> {
538 let ns_id = NamespaceId::from(namespace);
539
540 if until <= from {
542 return Err(bad_request(format!(
543 "invalid range: until ({}) must be greater than from ({})",
544 until, from
545 )));
546 }
547
548 let range_size = until - from;
549 const MAX_RANGE: u64 = 100;
550 if range_size > MAX_RANGE {
551 return Err(range_exceeded(format!(
552 "range too large: {} blocks (max {})",
553 range_size, MAX_RANGE
554 )));
555 }
556
557 let (blocks_stream, vids_stream) = join!(
559 self.data_source
560 .get_block_range(from as usize..until as usize),
561 self.data_source
562 .get_vid_common_range(from as usize..until as usize)
563 );
564
565 let blocks: Vec<_> = blocks_stream
566 .then(|block| async move { block.resolve().await })
567 .collect()
568 .await;
569 let vids: Vec<_> = vids_stream
570 .then(|vid| async move { vid.resolve().await })
571 .collect()
572 .await;
573
574 if blocks.len() != vids.len() {
575 return Err(anyhow::anyhow!(
576 "mismatch between blocks and VID common data"
577 ));
578 }
579
580 let mut proofs = Vec::new();
582
583 for (block, vid) in blocks.into_iter().zip(vids) {
584 let ns_table = block.payload().ns_table();
585
586 if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
588 if let Some(proof) = NsProof::new(block.payload(), &ns_index, vid.common()) {
589 let transactions = proof.export_all_txs(&ns_id);
590 proofs.push(espresso_types::NamespaceProofQueryData {
591 transactions,
592 proof: Some(proof),
593 });
594 } else {
595 proofs.push(espresso_types::NamespaceProofQueryData {
597 transactions: vec![],
598 proof: None,
599 });
600 }
601 } else {
602 proofs.push(espresso_types::NamespaceProofQueryData {
604 transactions: vec![],
605 proof: None,
606 });
607 }
608 }
609
610 Ok(proofs)
611 }
612
613 async fn stream_namespace_proofs(
614 &self,
615 from: usize,
616 namespace: u32,
617 ) -> anyhow::Result<BoxStream<'static, Self::NamespaceProofQueryData>> {
618 let ns_id = NamespaceId::from(namespace);
619 let ds = self.data_source.clone();
620 let blocks = (*ds).subscribe_blocks(from).await;
621 let vids = (*ds).subscribe_vid_common(from).await;
622
623 let stream = blocks
624 .zip(vids)
625 .map(move |(block, vid)| {
626 let ns_table = block.payload().ns_table();
627 if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
628 if let Some(proof) = NsProof::new(block.payload(), &ns_index, vid.common()) {
629 let transactions = proof.export_all_txs(&ns_id);
630 NamespaceProofQueryData {
631 transactions,
632 proof: Some(proof),
633 }
634 } else {
635 NamespaceProofQueryData {
636 transactions: vec![],
637 proof: None,
638 }
639 }
640 } else {
641 NamespaceProofQueryData {
642 transactions: vec![],
643 proof: None,
644 }
645 }
646 })
647 .boxed();
648
649 Ok(stream)
650 }
651
652 async fn get_incorrect_encoding_proof(
653 &self,
654 block_id: v1::availability::BlockId,
655 namespace: u32,
656 ) -> anyhow::Result<Self::IncorrectEncodingProof> {
657 let ns_id = NamespaceId::from(namespace);
658
659 let hs_block_id = match block_id {
660 v1::availability::BlockId::Height(h) => HsBlockId::Number(h as usize),
661 v1::availability::BlockId::Hash(h) => {
662 let hash = h
663 .parse()
664 .map_err(|_| anyhow::anyhow!("invalid block hash: {}", h))?;
665 HsBlockId::Hash(hash)
666 },
667 v1::availability::BlockId::PayloadHash(h) => {
668 let payload_hash = h
669 .parse()
670 .map_err(|_| anyhow::anyhow!("invalid payload hash: {}", h))?;
671 HsBlockId::PayloadHash(payload_hash)
672 },
673 };
674
675 let ds = &*self.data_source;
676 let timeout = FETCH_TIMEOUT;
677 let (block_fetch, vid_fetch) =
678 join!(ds.get_block(hs_block_id), ds.get_vid_common(hs_block_id));
679 let (block, vid_common) = join!(
680 block_fetch.with_timeout(timeout),
681 vid_fetch.with_timeout(timeout)
682 );
683
684 let block = block.ok_or_else(|| anyhow::anyhow!("block not found"))?;
685 let vid_common = vid_common.ok_or_else(|| anyhow::anyhow!("VID common data not found"))?;
686
687 let ns_table = block.payload().ns_table();
688 let ns_index = ns_table
689 .find_ns_id(&ns_id)
690 .ok_or_else(|| anyhow::anyhow!("namespace {} not present in block", namespace))?;
691
692 if NsProof::new(block.payload(), &ns_index, vid_common.common()).is_some() {
693 return Err(anyhow::anyhow!("block was correctly encoded"));
694 }
695
696 let vid_shares_future = ds
698 .request_vid_shares(block.height(), vid_common.clone(), Duration::from_secs(40))
699 .await;
700 let mut vid_shares = vid_shares_future
701 .await
702 .map_err(|e| anyhow::anyhow!("failed to fetch VID shares: {e:#}"))?;
703
704 if let Ok(local_share) = ds.vid_share(block.height() as usize).await {
705 vid_shares.push(local_share);
706 }
707
708 let avidm_shares: Vec<AvidMShare> = vid_shares
709 .into_iter()
710 .filter_map(|s| {
711 if let VidShare::V1(s) = s {
712 Some(s)
713 } else {
714 None
715 }
716 })
717 .collect();
718
719 match NsProof::v1_1_new_with_incorrect_encoding(
720 &avidm_shares,
721 ns_table,
722 &ns_index,
723 &vid_common.payload_hash(),
724 vid_common.common(),
725 ) {
726 Some(NsProof::V1IncorrectEncoding(proof)) => Ok(proof),
727 _ => Err(anyhow::anyhow!(
728 "failed to generate incorrect encoding proof"
729 )),
730 }
731 }
732
733 async fn get_state_cert(&self, epoch: u64) -> anyhow::Result<Self::StateCertQueryDataV1> {
734 let state_cert = self.data_source.get_state_cert_by_epoch(epoch).await?;
736
737 let cert = match state_cert {
738 Some(cert) => cert,
739 None => {
740 const TIMEOUT: Duration = Duration::from_secs(40);
742 let cert = self
743 .data_source
744 .request_state_cert(epoch, TIMEOUT)
745 .await
746 .map_err(|e| {
747 anyhow::anyhow!("failed to fetch state cert for epoch {}: {}", epoch, e)
748 })?;
749
750 self.data_source
752 .insert_state_cert(epoch, cert.clone())
753 .await?;
754
755 cert
756 },
757 };
758
759 Ok(espresso_types::StateCertQueryDataV1::from(
760 espresso_types::StateCertQueryDataV2(cert),
761 ))
762 }
763
764 async fn get_state_cert_v2(&self, epoch: u64) -> anyhow::Result<Self::StateCertQueryDataV2> {
765 let state_cert = self.data_source.get_state_cert_by_epoch(epoch).await?;
767
768 let cert = match state_cert {
769 Some(cert) => cert,
770 None => {
771 const TIMEOUT: Duration = Duration::from_secs(40);
773 let cert = self
774 .data_source
775 .request_state_cert(epoch, TIMEOUT)
776 .await
777 .map_err(|e| {
778 anyhow::anyhow!("failed to fetch state cert for epoch {}: {}", epoch, e)
779 })?;
780
781 self.data_source
783 .insert_state_cert(epoch, cert.clone())
784 .await?;
785
786 cert
787 },
788 };
789
790 Ok(espresso_types::StateCertQueryDataV2(cert))
791 }
792}
793
794fn not_found(msg: impl Into<String>) -> anyhow::Error {
795 AvailabilityError::NotFound(msg.into()).into()
796}
797
798fn bad_request(msg: impl Into<String>) -> anyhow::Error {
799 AvailabilityError::BadRequest(msg.into()).into()
800}
801
802fn range_exceeded(msg: impl Into<String>) -> anyhow::Error {
803 AvailabilityError::RangeExceeded(msg.into()).into()
804}
805
806fn enforce_range(from: usize, until: usize, limit: usize) -> anyhow::Result<()> {
807 if until.saturating_sub(from) > limit {
808 return Err(range_exceeded(format!(
809 "range {from}..{until} exceeds limit {limit}"
810 )));
811 }
812 Ok(())
813}
814
815fn small_object_range_limit() -> usize {
818 hotshot_query_service::availability::Options::default().small_object_range_limit
819}
820
821fn large_object_range_limit() -> usize {
822 hotshot_query_service::availability::Options::default().large_object_range_limit
823}
824
825#[async_trait]
826impl<D> HotShotAvailabilityApi for NodeApiStateImpl<D>
827where
828 D: Deref + Clone + Send + Sync + 'static,
829 D::Target: AvailabilityDataSource<SeqTypes> + Send + Sync,
830{
831 type Leaf = LeafQueryData<SeqTypes>;
832 type Block = BlockQueryData<SeqTypes>;
833 type Header = HsHeader<SeqTypes>;
834 type Payload = PayloadQueryData<SeqTypes>;
835 type VidCommon = VidCommonQueryData<SeqTypes>;
836 type Transaction = TransactionQueryData<SeqTypes>;
837 type TransactionWithProof = TransactionWithProofQueryData<SeqTypes>;
838 type BlockSummary = BlockSummaryQueryData<SeqTypes>;
839 type Limits = HsLimits;
840 type Cert2 = Certificate2<SeqTypes>;
841
842 async fn get_leaf(&self, id: v1::availability::LeafId) -> anyhow::Result<Self::Leaf> {
843 let hs_id = match id {
844 v1::availability::LeafId::Height(h) => HsLeafId::Number(h as usize),
845 v1::availability::LeafId::Hash(h) => {
846 HsLeafId::Hash(h.parse().map_err(|_| bad_request("invalid leaf hash"))?)
847 },
848 };
849 let ds = &*self.data_source;
850 ds.get_leaf(hs_id)
851 .await
852 .with_timeout(FETCH_TIMEOUT)
853 .await
854 .ok_or_else(|| not_found("leaf not found"))
855 }
856
857 async fn get_leaf_range(&self, from: usize, until: usize) -> anyhow::Result<Vec<Self::Leaf>> {
858 enforce_range(from, until, small_object_range_limit())?;
859 let timeout = FETCH_TIMEOUT;
860 let ds = &*self.data_source;
861 let stream = ds.get_leaf_range(from..until).await;
862 let mut results = Vec::new();
863 futures::pin_mut!(stream);
864 let mut i = from;
865 while let Some(fetch) = stream.next().await {
866 let item = fetch
867 .with_timeout(timeout)
868 .await
869 .ok_or_else(|| not_found(format!("leaf {} not found", i)))?;
870 results.push(item);
871 i += 1;
872 }
873 Ok(results)
874 }
875
876 async fn get_header(&self, id: v1::availability::BlockId) -> anyhow::Result<Self::Header> {
877 let hs_id = block_id_to_hs(id)?;
878 let ds = &*self.data_source;
879 ds.get_header(hs_id)
880 .await
881 .with_timeout(FETCH_TIMEOUT)
882 .await
883 .ok_or_else(|| not_found(format!("header not found for {}", hs_id)))
884 }
885
886 async fn get_header_range(
887 &self,
888 from: usize,
889 until: usize,
890 ) -> anyhow::Result<Vec<Self::Header>> {
891 enforce_range(from, until, large_object_range_limit())?;
892 let timeout = FETCH_TIMEOUT;
893 let ds = &*self.data_source;
894 let stream = ds.get_header_range(from..until).await;
895 let mut results = Vec::new();
896 futures::pin_mut!(stream);
897 let mut i = from;
898 while let Some(fetch) = stream.next().await {
899 let item = fetch
900 .with_timeout(timeout)
901 .await
902 .ok_or_else(|| not_found(format!("header {} not found", i)))?;
903 results.push(item);
904 i += 1;
905 }
906 Ok(results)
907 }
908
909 async fn get_block(&self, id: v1::availability::BlockId) -> anyhow::Result<Self::Block> {
910 let hs_id = block_id_to_hs(id)?;
911 let ds = &*self.data_source;
912 ds.get_block(hs_id)
913 .await
914 .with_timeout(FETCH_TIMEOUT)
915 .await
916 .ok_or_else(|| not_found(format!("block not found for {}", hs_id)))
917 }
918
919 async fn get_block_range(&self, from: usize, until: usize) -> anyhow::Result<Vec<Self::Block>> {
920 enforce_range(from, until, large_object_range_limit())?;
921 let timeout = FETCH_TIMEOUT;
922 let ds = &*self.data_source;
923 let stream = ds.get_block_range(from..until).await;
924 let mut results = Vec::new();
925 futures::pin_mut!(stream);
926 let mut i = from;
927 while let Some(fetch) = stream.next().await {
928 let item = fetch
929 .with_timeout(timeout)
930 .await
931 .ok_or_else(|| not_found(format!("block {} not found", i)))?;
932 results.push(item);
933 i += 1;
934 }
935 Ok(results)
936 }
937
938 async fn get_payload(&self, id: v1::availability::PayloadId) -> anyhow::Result<Self::Payload> {
939 let hs_id = payload_id_to_hs(id)?;
940 let ds = &*self.data_source;
941 ds.get_payload(hs_id)
942 .await
943 .with_timeout(FETCH_TIMEOUT)
944 .await
945 .ok_or_else(|| not_found(format!("payload not found for {}", hs_id)))
946 }
947
948 async fn get_payload_range(
949 &self,
950 from: usize,
951 until: usize,
952 ) -> anyhow::Result<Vec<Self::Payload>> {
953 enforce_range(from, until, large_object_range_limit())?;
954 let timeout = FETCH_TIMEOUT;
955 let ds = &*self.data_source;
956 let stream = ds.get_payload_range(from..until).await;
957 let mut results = Vec::new();
958 futures::pin_mut!(stream);
959 let mut i = from;
960 while let Some(fetch) = stream.next().await {
961 let item = fetch
962 .with_timeout(timeout)
963 .await
964 .ok_or_else(|| not_found(format!("payload {} not found", i)))?;
965 results.push(item);
966 i += 1;
967 }
968 Ok(results)
969 }
970
971 async fn get_vid_common(
972 &self,
973 id: v1::availability::BlockId,
974 ) -> anyhow::Result<Self::VidCommon> {
975 let hs_id = block_id_to_hs(id)?;
976 let ds = &*self.data_source;
977 ds.get_vid_common(hs_id)
978 .await
979 .with_timeout(FETCH_TIMEOUT)
980 .await
981 .ok_or_else(|| not_found(format!("VID common not found for {}", hs_id)))
982 }
983
984 async fn get_vid_common_range(
985 &self,
986 from: usize,
987 until: usize,
988 ) -> anyhow::Result<Vec<Self::VidCommon>> {
989 enforce_range(from, until, small_object_range_limit())?;
990 let timeout = FETCH_TIMEOUT;
991 let ds = &*self.data_source;
992 let stream = ds.get_vid_common_range(from..until).await;
993 let mut results = Vec::new();
994 futures::pin_mut!(stream);
995 let mut i = from;
996 while let Some(fetch) = stream.next().await {
997 let item = fetch
998 .with_timeout(timeout)
999 .await
1000 .ok_or_else(|| not_found(format!("VID common {} not found", i)))?;
1001 results.push(item);
1002 i += 1;
1003 }
1004 Ok(results)
1005 }
1006
1007 async fn get_transaction_by_position(
1008 &self,
1009 height: u64,
1010 index: u64,
1011 ) -> anyhow::Result<Self::Transaction> {
1012 let ds = &*self.data_source;
1013 let block = ds
1014 .get_block(HsBlockId::Number(height as usize))
1015 .await
1016 .with_timeout(FETCH_TIMEOUT)
1017 .await
1018 .ok_or_else(|| not_found(format!("block {} not found", height)))?;
1019
1020 let idx = block
1021 .payload()
1022 .nth(block.metadata(), index as usize)
1023 .ok_or_else(|| {
1024 not_found(format!(
1025 "transaction index {} out of bounds in block {}",
1026 index, height
1027 ))
1028 })?;
1029 let tx = block
1030 .transaction(&idx)
1031 .ok_or_else(|| not_found(format!("transaction not found at index {}", index)))?;
1032 TransactionQueryData::new(tx, &block, &idx, index)
1033 .ok_or_else(|| anyhow::anyhow!("failed to build transaction query data"))
1034 }
1035
1036 async fn get_transaction_by_hash(&self, hash: String) -> anyhow::Result<Self::Transaction> {
1037 let ds = &*self.data_source;
1038 let tx_hash: hotshot_query_service::availability::TransactionHash<SeqTypes> = hash
1039 .parse()
1040 .map_err(|_| bad_request(format!("invalid transaction hash: {}", hash)))?;
1041 let bwt = ds
1042 .get_block_containing_transaction(tx_hash)
1043 .await
1044 .with_timeout(FETCH_TIMEOUT)
1045 .await
1046 .ok_or_else(|| not_found("transaction not found"))?;
1047 Ok(bwt.transaction)
1048 }
1049
1050 async fn get_transaction_proof_by_position(
1051 &self,
1052 height: u64,
1053 index: u64,
1054 ) -> anyhow::Result<Self::TransactionWithProof> {
1055 let ds = &*self.data_source;
1056 let timeout = FETCH_TIMEOUT;
1057
1058 let (block_fetch, vid_fetch) = futures::join!(
1059 ds.get_block(HsBlockId::Number(height as usize)),
1060 ds.get_vid_common(HsBlockId::Number(height as usize))
1061 );
1062 let (block, vid) = futures::join!(
1063 block_fetch.with_timeout(timeout),
1064 vid_fetch.with_timeout(timeout)
1065 );
1066
1067 let block = block.ok_or_else(|| not_found(format!("block {} not found", height)))?;
1068 let vid =
1069 vid.ok_or_else(|| not_found(format!("VID common not found for block {}", height)))?;
1070
1071 let idx = block
1072 .payload()
1073 .nth(block.metadata(), index as usize)
1074 .ok_or_else(|| {
1075 not_found(format!(
1076 "transaction index {} out of bounds in block {}",
1077 index, height
1078 ))
1079 })?;
1080 let tx = block
1081 .transaction(&idx)
1082 .ok_or_else(|| not_found(format!("transaction not found at index {}", index)))?;
1083 let tx_data = TransactionQueryData::new(tx, &block, &idx, index)
1084 .ok_or_else(|| anyhow::anyhow!("failed to build transaction query data"))?;
1085 let proof = block
1086 .transaction_proof(&vid, &idx)
1087 .ok_or_else(|| anyhow::anyhow!("failed to build transaction proof"))?;
1088 Ok(TransactionWithProofQueryData::new(tx_data, proof))
1089 }
1090
1091 async fn get_transaction_proof_by_hash(
1092 &self,
1093 hash: String,
1094 ) -> anyhow::Result<Self::TransactionWithProof> {
1095 let ds = &*self.data_source;
1096 let timeout = FETCH_TIMEOUT;
1097
1098 let tx_hash: hotshot_query_service::availability::TransactionHash<SeqTypes> = hash
1099 .parse()
1100 .map_err(|_| bad_request(format!("invalid transaction hash: {}", hash)))?;
1101 let bwt = ds
1102 .get_block_containing_transaction(tx_hash)
1103 .await
1104 .with_timeout(timeout)
1105 .await
1106 .ok_or_else(|| not_found("transaction not found"))?;
1107
1108 let vid = ds
1109 .get_vid_common(HsBlockId::Number(bwt.block.height() as usize))
1110 .await
1111 .with_timeout(timeout)
1112 .await
1113 .ok_or_else(|| {
1114 not_found(format!(
1115 "VID common not found for block {}",
1116 bwt.block.height()
1117 ))
1118 })?;
1119
1120 let proof = bwt
1121 .block
1122 .transaction_proof(&vid, &bwt.index)
1123 .ok_or_else(|| anyhow::anyhow!("failed to build transaction proof"))?;
1124 Ok(TransactionWithProofQueryData::new(bwt.transaction, proof))
1125 }
1126
1127 async fn get_block_summary(&self, height: usize) -> anyhow::Result<Self::BlockSummary> {
1128 let ds = &*self.data_source;
1129 let block = ds
1130 .get_block(HsBlockId::Number(height))
1131 .await
1132 .with_timeout(FETCH_TIMEOUT)
1133 .await
1134 .ok_or_else(|| not_found(format!("block {} not found", height)))?;
1135 Ok(BlockSummaryQueryData::from(block))
1136 }
1137
1138 async fn get_block_summary_range(
1139 &self,
1140 from: usize,
1141 until: usize,
1142 ) -> anyhow::Result<Vec<Self::BlockSummary>> {
1143 enforce_range(from, until, large_object_range_limit())?;
1144 let timeout = FETCH_TIMEOUT;
1145 let ds = &*self.data_source;
1146 let stream = ds.get_block_range(from..until).await;
1147 let mut results = Vec::new();
1148 futures::pin_mut!(stream);
1149 let mut i = from;
1150 while let Some(fetch) = stream.next().await {
1151 let block = fetch
1152 .with_timeout(timeout)
1153 .await
1154 .ok_or_else(|| not_found(format!("block {} not found", i)))?;
1155 results.push(BlockSummaryQueryData::from(block));
1156 i += 1;
1157 }
1158 Ok(results)
1159 }
1160
1161 async fn get_limits(&self) -> anyhow::Result<Self::Limits> {
1162 Ok(HsLimits {
1163 small_object_range_limit: small_object_range_limit(),
1164 large_object_range_limit: large_object_range_limit(),
1165 })
1166 }
1167
1168 async fn get_cert2(&self, height: u64) -> anyhow::Result<Option<Self::Cert2>> {
1169 Ok(self
1170 .data_source
1171 .get_cert2(height)
1172 .await
1173 .with_timeout(FETCH_TIMEOUT)
1174 .await)
1175 }
1176
1177 async fn stream_leaves(&self, from: usize) -> anyhow::Result<BoxStream<'static, Self::Leaf>> {
1178 let ds = self.data_source.clone();
1179 Ok((*ds).subscribe_leaves(from).await.boxed())
1180 }
1181
1182 async fn stream_headers(
1183 &self,
1184 from: usize,
1185 ) -> anyhow::Result<BoxStream<'static, Self::Header>> {
1186 let ds = self.data_source.clone();
1187 Ok((*ds).subscribe_headers(from).await.boxed())
1188 }
1189
1190 async fn stream_blocks(&self, from: usize) -> anyhow::Result<BoxStream<'static, Self::Block>> {
1191 let ds = self.data_source.clone();
1192 Ok((*ds).subscribe_blocks(from).await.boxed())
1193 }
1194
1195 async fn stream_payloads(
1196 &self,
1197 from: usize,
1198 ) -> anyhow::Result<BoxStream<'static, Self::Payload>> {
1199 let ds = self.data_source.clone();
1200 Ok((*ds).subscribe_payloads(from).await.boxed())
1201 }
1202
1203 async fn stream_vid_common(
1204 &self,
1205 from: usize,
1206 ) -> anyhow::Result<BoxStream<'static, Self::VidCommon>> {
1207 let ds = self.data_source.clone();
1208 Ok((*ds).subscribe_vid_common(from).await.boxed())
1209 }
1210
1211 async fn stream_transactions(
1212 &self,
1213 from: usize,
1214 namespace: Option<u32>,
1215 ) -> anyhow::Result<BoxStream<'static, Self::Transaction>> {
1216 let ds = self.data_source.clone();
1217 let stream = (*ds)
1218 .subscribe_blocks(from)
1219 .await
1220 .flat_map(move |block| {
1221 let ns_filter = namespace.map(NamespaceId::from);
1222 let txs: Vec<Self::Transaction> = block
1223 .enumerate()
1224 .enumerate()
1225 .filter_map(|(position_in_block, (tx_index, _tx))| {
1226 let tx = block.transaction(&tx_index)?;
1227 if let Some(ns) = ns_filter
1228 && tx.namespace() != ns
1229 {
1230 return None;
1231 }
1232 TransactionQueryData::new(tx, &block, &tx_index, position_in_block as u64)
1233 })
1234 .collect();
1235 futures::stream::iter(txs)
1236 })
1237 .boxed();
1238 Ok(stream)
1239 }
1240}
1241
1242fn block_id_to_hs(id: v1::availability::BlockId) -> anyhow::Result<HsBlockId<SeqTypes>> {
1243 match id {
1244 v1::availability::BlockId::Height(h) => Ok(HsBlockId::Number(h as usize)),
1245 v1::availability::BlockId::Hash(h) => {
1246 let hash = h
1247 .parse()
1248 .map_err(|_| bad_request(format!("invalid block hash: {}", h)))?;
1249 Ok(HsBlockId::Hash(hash))
1250 },
1251 v1::availability::BlockId::PayloadHash(h) => {
1252 let payload_hash = h
1253 .parse()
1254 .map_err(|_| bad_request(format!("invalid payload hash: {}", h)))?;
1255 Ok(HsBlockId::PayloadHash(payload_hash))
1256 },
1257 }
1258}
1259
1260fn payload_id_to_hs(id: v1::availability::PayloadId) -> anyhow::Result<HsBlockId<SeqTypes>> {
1261 match id {
1262 v1::availability::PayloadId::Height(h) => Ok(HsBlockId::Number(h as usize)),
1263 v1::availability::PayloadId::Hash(h) => {
1264 let payload_hash = h
1265 .parse()
1266 .map_err(|_| bad_request(format!("invalid payload hash: {}", h)))?;
1267 Ok(HsBlockId::PayloadHash(payload_hash))
1268 },
1269 v1::availability::PayloadId::BlockHash(h) => {
1270 let hash = h
1271 .parse()
1272 .map_err(|_| bad_request(format!("invalid block hash: {}", h)))?;
1273 Ok(HsBlockId::Hash(hash))
1274 },
1275 }
1276}
1277
1278fn classify_query_error(err: hotshot_query_service::QueryError) -> anyhow::Error {
1279 match err {
1280 QueryError::NotFound | QueryError::Missing => not_found(err.to_string()),
1281 QueryError::Error { .. } => anyhow::anyhow!(err.to_string()),
1282 }
1283}
1284
1285#[async_trait]
1286impl<D> v1::BlockStateApi for NodeApiStateImpl<D>
1287where
1288 D: Deref + Clone + Send + Sync + 'static,
1289 D::Target: hotshot_query_service::merklized_state::MerklizedStateDataSource<
1290 SeqTypes,
1291 espresso_types::BlockMerkleTree,
1292 { <espresso_types::BlockMerkleTree as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY },
1293 > + hotshot_query_service::merklized_state::MerklizedStateHeightPersistence
1294 + Send
1295 + Sync,
1296{
1297 type MerkleProof = InternalMerkleProof<
1298 committable::Commitment<espresso_types::Header>,
1299 u64,
1300 jf_merkle_tree_compat::prelude::Sha3Node,
1301 3,
1302 >;
1303
1304 async fn get_block_state_path(
1305 &self,
1306 snapshot: v1::Snapshot,
1307 key: String,
1308 ) -> anyhow::Result<Self::MerkleProof> {
1309 let hs_snapshot = match snapshot {
1310 v1::Snapshot::Height(h) => HsSnapshot::Index(h),
1311 v1::Snapshot::Commit(c) => {
1312 let tb64: TaggedBase64 = c
1313 .parse()
1314 .map_err(|_| bad_request("failed to parse commit param"))?;
1315 let commit = (&tb64)
1316 .try_into()
1317 .map_err(|_| bad_request("failed to parse commit param"))?;
1318 HsSnapshot::Commit(commit)
1319 },
1320 };
1321 let key: u64 = key
1322 .parse()
1323 .map_err(|_| bad_request("failed to parse Key param"))?;
1324 let ds = &*self.data_source;
1325 MerklizedStateDataSource::<SeqTypes, espresso_types::BlockMerkleTree, _>::get_path(
1326 ds,
1327 hs_snapshot,
1328 key,
1329 )
1330 .await
1331 .map_err(classify_query_error)
1332 }
1333
1334 async fn get_block_state_height(&self) -> anyhow::Result<u64> {
1335 let ds = &*self.data_source;
1336 ds.get_last_state_height()
1337 .await
1338 .map(|h| h as u64)
1339 .map_err(classify_query_error)
1340 }
1341}
1342
1343#[async_trait]
1344impl<D> v1::FeeStateApi for NodeApiStateImpl<D>
1345where
1346 D: Deref + Clone + Send + Sync + 'static,
1347 D::Target: hotshot_query_service::merklized_state::MerklizedStateDataSource<
1348 SeqTypes,
1349 espresso_types::FeeMerkleTree,
1350 { <espresso_types::FeeMerkleTree as jf_merkle_tree_compat::MerkleTreeScheme>::ARITY },
1351 > + hotshot_query_service::merklized_state::MerklizedStateHeightPersistence
1352 + Send
1353 + Sync,
1354{
1355 type MerkleProof = InternalMerkleProof<
1356 espresso_types::FeeAmount,
1357 espresso_types::FeeAccount,
1358 jf_merkle_tree_compat::prelude::Sha3Node,
1359 256,
1360 >;
1361 type FeeAmount = espresso_types::FeeAmount;
1362
1363 async fn get_fee_state_path(
1364 &self,
1365 snapshot: v1::Snapshot,
1366 key: String,
1367 ) -> anyhow::Result<Self::MerkleProof> {
1368 let hs_snapshot = match snapshot {
1369 v1::Snapshot::Height(h) => HsSnapshot::Index(h),
1370 v1::Snapshot::Commit(c) => {
1371 let tb64: TaggedBase64 = c
1372 .parse()
1373 .map_err(|_| bad_request("failed to parse commit param"))?;
1374 let commit = (&tb64)
1375 .try_into()
1376 .map_err(|_| bad_request("failed to parse commit param"))?;
1377 HsSnapshot::Commit(commit)
1378 },
1379 };
1380 let key: espresso_types::FeeAccount = key
1381 .parse()
1382 .map_err(|_| bad_request("failed to parse Key param"))?;
1383 let ds = &*self.data_source;
1384 MerklizedStateDataSource::<SeqTypes, espresso_types::FeeMerkleTree, _>::get_path(
1385 ds,
1386 hs_snapshot,
1387 key,
1388 )
1389 .await
1390 .map_err(classify_query_error)
1391 }
1392
1393 async fn get_fee_state_height(&self) -> anyhow::Result<u64> {
1394 let ds = &*self.data_source;
1395 ds.get_last_state_height()
1396 .await
1397 .map(|h| h as u64)
1398 .map_err(classify_query_error)
1399 }
1400
1401 async fn get_fee_balance_latest(
1402 &self,
1403 address: String,
1404 ) -> anyhow::Result<Option<Self::FeeAmount>> {
1405 let key: espresso_types::FeeAccount = address
1406 .parse()
1407 .map_err(|_| bad_request("failed to parse address"))?;
1408 let ds = &*self.data_source;
1409 let height = ds
1410 .get_last_state_height()
1411 .await
1412 .map_err(classify_query_error)?;
1413 let path: JfMerkleProof<
1414 espresso_types::FeeAmount,
1415 espresso_types::FeeAccount,
1416 jf_merkle_tree_compat::prelude::Sha3Node,
1417 256,
1418 > = MerklizedStateDataSource::<SeqTypes, espresso_types::FeeMerkleTree, _>::get_path(
1419 ds,
1420 HsSnapshot::Index(height as u64),
1421 key,
1422 )
1423 .await
1424 .map_err(classify_query_error)?;
1425 Ok(path.elem().copied())
1426 }
1427}
1428
1429#[async_trait]
1430impl<D> v1::StatusApi for NodeApiStateImpl<D>
1431where
1432 D: Deref + Clone + Send + Sync + 'static,
1433 D::Target: hotshot_query_service::status::StatusDataSource + NodeKeysDataSource + Send + Sync,
1434{
1435 type Keys = NodePublicKeys;
1436
1437 async fn block_height(&self) -> anyhow::Result<u64> {
1438 let ds = &*self.data_source;
1439 let h = hotshot_query_service::status::StatusDataSource::block_height(ds)
1440 .await
1441 .map_err(|e| anyhow::anyhow!("{e}"))?;
1442 Ok(h as u64)
1443 }
1444
1445 async fn success_rate(&self) -> anyhow::Result<f64> {
1446 let ds = &*self.data_source;
1447 hotshot_query_service::status::StatusDataSource::success_rate(ds)
1448 .await
1449 .map_err(|e| anyhow::anyhow!("{e}"))
1450 }
1451
1452 async fn time_since_last_decide(&self) -> anyhow::Result<u64> {
1453 let ds = &*self.data_source;
1454 hotshot_query_service::status::StatusDataSource::elapsed_time_since_last_decide(ds)
1455 .await
1456 .map_err(|e| anyhow::anyhow!("{e}"))
1457 }
1458
1459 async fn metrics(&self) -> anyhow::Result<String> {
1460 let ds = &*self.data_source;
1461 let mut buffer = Vec::new();
1463 prometheus::TextEncoder::new().encode(&ds.metrics().registry().gather(), &mut buffer)?;
1464 Ok(String::from_utf8(buffer)?)
1465 }
1466
1467 async fn keys(&self) -> anyhow::Result<NodePublicKeys> {
1468 Ok(self.data_source.node_public_keys().await)
1469 }
1470}
1471
1472#[tonic::async_trait]
1473impl<D> proto::status_service_server::StatusService for NodeApiStateImpl<D>
1474where
1475 D: Deref + Clone + Send + Sync + 'static,
1476 D::Target: hotshot_query_service::status::StatusDataSource + NodeKeysDataSource + Send + Sync,
1477{
1478 async fn get_block_height(
1479 &self,
1480 _request: tonic::Request<proto::GetBlockHeightRequest>,
1481 ) -> Result<tonic::Response<proto::BlockHeightResponse>, tonic::Status> {
1482 let height = <Self as v1::StatusApi>::block_height(self)
1483 .await
1484 .map_err(to_status)?;
1485 Ok(tonic::Response::new(proto::BlockHeightResponse { height }))
1486 }
1487
1488 async fn get_success_rate(
1489 &self,
1490 _request: tonic::Request<proto::GetSuccessRateRequest>,
1491 ) -> Result<tonic::Response<proto::SuccessRateResponse>, tonic::Status> {
1492 let rate = <Self as v1::StatusApi>::success_rate(self)
1493 .await
1494 .map_err(to_status)?;
1495 let rate = if rate.is_finite() { rate } else { 0. };
1499 Ok(tonic::Response::new(proto::SuccessRateResponse { rate }))
1500 }
1501
1502 async fn get_time_since_last_decide(
1503 &self,
1504 _request: tonic::Request<proto::GetTimeSinceLastDecideRequest>,
1505 ) -> Result<tonic::Response<proto::TimeSinceLastDecideResponse>, tonic::Status> {
1506 let seconds = <Self as v1::StatusApi>::time_since_last_decide(self)
1507 .await
1508 .map_err(to_status)?;
1509 Ok(tonic::Response::new(proto::TimeSinceLastDecideResponse {
1510 seconds,
1511 }))
1512 }
1513
1514 async fn get_node_keys(
1515 &self,
1516 _request: tonic::Request<proto::GetNodeKeysRequest>,
1517 ) -> Result<tonic::Response<proto::NodeKeysResponse>, tonic::Status> {
1518 let keys = <Self as v1::StatusApi>::keys(self)
1519 .await
1520 .map_err(to_status)?;
1521 Ok(tonic::Response::new(proto::NodeKeysResponse {
1522 eth_account: keys.eth_account.map(|account| format!("{account:#x}")),
1523 consensus_key: Some(proto::BlsPublicKey {
1524 key: keys.consensus_key.to_string(),
1525 }),
1526 state_ver_key: Some(proto::SchnorrPublicKey {
1527 key: keys.state_ver_key.to_string(),
1528 }),
1529 x25519_key: keys.x25519_key.as_ref().map(ToString::to_string),
1530 }))
1531 }
1532}
1533
1534#[async_trait]
1535impl<D> v1::ConfigApi for NodeApiStateImpl<D>
1536where
1537 D: Deref + Clone + Send + Sync + 'static,
1538 D::Target: HotShotConfigDataSource + Send + Sync,
1539{
1540 type HotShotConfig = espresso_types::config::PublicNetworkConfig;
1541 type RuntimeConfig = crate::options::PublicNodeConfig;
1542
1543 async fn hotshot_config(&self) -> anyhow::Result<Self::HotShotConfig> {
1544 let ds = &*self.data_source;
1545 Ok(ds.get_config().await)
1546 }
1547
1548 async fn env(&self) -> anyhow::Result<Vec<String>> {
1549 Ok((*self.env_vars).clone())
1550 }
1551
1552 async fn runtime_config(&self) -> anyhow::Result<Self::RuntimeConfig> {
1553 self.public_node_config.as_deref().cloned().ok_or_else(|| {
1554 espresso_api::error::AvailabilityError::NotFound(
1555 "runtime config not available".to_string(),
1556 )
1557 .into()
1558 })
1559 }
1560}
1561
1562#[async_trait]
1563impl<D> v1::NodeApi for NodeApiStateImpl<D>
1564where
1565 D: Deref + Clone + Send + Sync + 'static,
1566 D::Target: hotshot_query_service::node::NodeDataSource<SeqTypes>
1567 + StakeTableDataSource<SeqTypes>
1568 + PruningDataSource
1569 + Send
1570 + Sync,
1571{
1572 type VidShare = hotshot_types::data::VidShare;
1573 type SyncStatus = hotshot_query_service::node::SyncStatusQueryData;
1574 type HeaderWindow =
1575 hotshot_query_service::node::TimeWindowQueryData<hotshot_query_service::Header<SeqTypes>>;
1576 type Limits = hotshot_query_service::node::Limits;
1577 type StakeTable = Vec<hotshot_types::PeerConfig<SeqTypes>>;
1578 type StakeTableCurrent = StakeTableWithEpochNumber<SeqTypes>;
1579 type Validators = indexmap::IndexMap<
1580 alloy::primitives::Address,
1581 espresso_types::v0_3::AuthenticatedValidator<espresso_types::PubKey>,
1582 >;
1583 type AllValidators = Vec<espresso_types::v0_3::RegisteredValidator<espresso_types::PubKey>>;
1584 type Participation = std::collections::HashMap<espresso_types::PubKey, f64>;
1585 type BlockReward = Option<espresso_types::v0_3::RewardAmount>;
1586 type Block = hotshot_query_service::availability::BlockQueryData<SeqTypes>;
1587 type Leaf = hotshot_query_service::availability::LeafQueryData<SeqTypes>;
1588
1589 async fn block_height(&self) -> anyhow::Result<u64> {
1590 let ds = &*self.data_source;
1591 let h = hotshot_query_service::node::NodeDataSource::block_height(ds)
1592 .await
1593 .map_err(classify_query_error)?;
1594 Ok(h as u64)
1595 }
1596
1597 async fn count_transactions(
1598 &self,
1599 from: Option<u64>,
1600 to: Option<u64>,
1601 namespace: Option<u64>,
1602 ) -> anyhow::Result<u64> {
1603 let ds = &*self.data_source;
1604 let from = match from {
1605 Some(f) => Bound::Included(f as usize),
1606 None => Bound::Unbounded,
1607 };
1608 let to = match to {
1609 Some(t) => Bound::Included(t as usize),
1610 None => Bound::Unbounded,
1611 };
1612 let ns = namespace.map(espresso_types::NamespaceId::from);
1613 let count = ds
1614 .count_transactions_in_range((from, to), ns)
1615 .await
1616 .map_err(classify_query_error)?;
1617 Ok(count as u64)
1618 }
1619
1620 async fn payload_size(
1621 &self,
1622 from: Option<u64>,
1623 to: Option<u64>,
1624 namespace: Option<u64>,
1625 ) -> anyhow::Result<u64> {
1626 let ds = &*self.data_source;
1627 let from = match from {
1628 Some(f) => Bound::Included(f as usize),
1629 None => Bound::Unbounded,
1630 };
1631 let to = match to {
1632 Some(t) => Bound::Included(t as usize),
1633 None => Bound::Unbounded,
1634 };
1635 let ns = namespace.map(espresso_types::NamespaceId::from);
1636 let size = ds
1637 .payload_size_in_range((from, to), ns)
1638 .await
1639 .map_err(classify_query_error)?;
1640 Ok(size as u64)
1641 }
1642
1643 async fn get_vid_share(&self, id: v1::VidShareId) -> anyhow::Result<Self::VidShare> {
1644 let ds = &*self.data_source;
1645 let node_id: HsBlockId<SeqTypes> = match id {
1646 v1::VidShareId::Height(h) => HsBlockId::Number(h as usize),
1647 v1::VidShareId::Hash(h) => HsBlockId::Hash(
1648 h.parse()
1649 .map_err(|_| bad_request(format!("invalid block hash: {h}")))?,
1650 ),
1651 v1::VidShareId::PayloadHash(h) => HsBlockId::PayloadHash(
1652 h.parse()
1653 .map_err(|_| bad_request(format!("invalid payload hash: {h}")))?,
1654 ),
1655 };
1656 hotshot_query_service::node::NodeDataSource::vid_share(ds, node_id)
1657 .await
1658 .map_err(classify_query_error)
1659 }
1660
1661 async fn sync_status(&self) -> anyhow::Result<Self::SyncStatus> {
1662 let ds = &*self.data_source;
1663 hotshot_query_service::node::NodeDataSource::sync_status(ds)
1664 .await
1665 .map_err(classify_query_error)
1666 }
1667
1668 async fn get_header_window(
1669 &self,
1670 start: v1::HeaderWindowStart,
1671 end: u64,
1672 ) -> anyhow::Result<Self::HeaderWindow> {
1673 let ds = &*self.data_source;
1674 let start: WindowStart<SeqTypes> = match start {
1675 v1::HeaderWindowStart::Time(t) => WindowStart::Time(t),
1676 v1::HeaderWindowStart::Height(h) => WindowStart::Height(h),
1677 v1::HeaderWindowStart::Hash(h) => WindowStart::Hash(
1678 h.parse()
1679 .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
1680 ),
1681 };
1682 ds.get_header_window(start, end, node_window_limit())
1683 .await
1684 .map_err(classify_query_error)
1685 }
1686
1687 async fn limits(&self) -> anyhow::Result<Self::Limits> {
1688 Ok(hotshot_query_service::node::Limits {
1689 window_limit: node_window_limit(),
1690 })
1691 }
1692
1693 async fn stake_table(&self, epoch: u64) -> anyhow::Result<Self::StakeTable> {
1694 let ds = &*self.data_source;
1695 ds.get_stake_table(Some(hotshot_types::data::EpochNumber::new(epoch)))
1696 .await
1697 }
1698
1699 async fn stake_table_current(&self) -> anyhow::Result<Self::StakeTableCurrent> {
1700 let ds = &*self.data_source;
1701 ds.get_stake_table_current().await
1702 }
1703
1704 async fn da_stake_table(&self, epoch: u64) -> anyhow::Result<Self::StakeTable> {
1705 let ds = &*self.data_source;
1706 ds.get_da_stake_table(Some(hotshot_types::data::EpochNumber::new(epoch)))
1707 .await
1708 }
1709
1710 async fn da_stake_table_current(&self) -> anyhow::Result<Self::StakeTableCurrent> {
1711 let ds = &*self.data_source;
1712 ds.get_da_stake_table_current().await
1713 }
1714
1715 async fn get_validators(&self, epoch: u64) -> anyhow::Result<Self::Validators> {
1716 let ds = &*self.data_source;
1717 ds.get_validators(hotshot_types::data::EpochNumber::new(epoch))
1718 .await
1719 }
1720
1721 async fn get_all_validators(
1722 &self,
1723 epoch: u64,
1724 offset: u64,
1725 limit: u64,
1726 ) -> anyhow::Result<Self::AllValidators> {
1727 if limit > 1000 {
1728 return Err(anyhow::anyhow!("Limit cannot be greater than 1000"));
1729 }
1730 let ds = &*self.data_source;
1731 ds.get_all_validators(hotshot_types::data::EpochNumber::new(epoch), offset, limit)
1732 .await
1733 }
1734
1735 async fn current_proposal_participation(&self) -> anyhow::Result<Self::Participation> {
1736 let ds = &*self.data_source;
1737 Ok(ds.current_proposal_participation().await)
1738 }
1739
1740 async fn proposal_participation(&self, epoch: u64) -> anyhow::Result<Self::Participation> {
1741 let ds = &*self.data_source;
1742 Ok(ds
1743 .proposal_participation(hotshot_types::data::EpochNumber::new(epoch))
1744 .await)
1745 }
1746
1747 async fn current_vote_participation(&self) -> anyhow::Result<Self::Participation> {
1748 let ds = &*self.data_source;
1749 Ok(ds.current_vote_participation().await)
1750 }
1751
1752 async fn vote_participation(&self, epoch: u64) -> anyhow::Result<Self::Participation> {
1753 let ds = &*self.data_source;
1754 Ok(ds
1755 .vote_participation(hotshot_types::data::EpochNumber::new(epoch))
1756 .await)
1757 }
1758
1759 async fn get_block_reward(&self, epoch: Option<u64>) -> anyhow::Result<Self::BlockReward> {
1760 let ds = &*self.data_source;
1761 ds.get_block_reward(epoch.map(hotshot_types::data::EpochNumber::new))
1762 .await
1763 }
1764
1765 async fn get_oldest_block(&self) -> anyhow::Result<Option<Self::Block>> {
1766 let ds = &*self.data_source;
1767 ds.get_oldest_block().await
1768 }
1769
1770 async fn get_oldest_leaf(&self) -> anyhow::Result<Option<Self::Leaf>> {
1771 let ds = &*self.data_source;
1772 ds.get_oldest_leaf().await
1773 }
1774}
1775
1776fn node_window_limit() -> usize {
1777 hotshot_query_service::node::Options::default().window_limit
1778}
1779
1780#[async_trait]
1781impl<D> v1::CatchupApi for NodeApiStateImpl<D>
1782where
1783 D: Deref + Clone + Send + Sync + 'static,
1784 D::Target: CatchupDataSource + NodeStateDataSource + Send + Sync,
1785{
1786 type FeeAccount = espresso_types::FeeAccount;
1787 type RewardAccountV1 = espresso_types::v0_3::RewardAccountV1;
1788 type RewardAccountV2 = espresso_types::v0_4::RewardAccountV2;
1789
1790 type AccountQueryData = espresso_types::AccountQueryData;
1791 type FeeMerkleTree = espresso_types::FeeMerkleTree;
1792 type BlocksFrontier = super::BlocksFrontier;
1793 type ChainConfig = espresso_types::v0_3::ChainConfig;
1794 type LeafChain = Vec<espresso_types::Leaf2>;
1795 type Cert2 = espresso_types::Certificate2<SeqTypes>;
1796 type RewardAccountQueryDataV1 = espresso_types::v0_3::RewardAccountQueryDataV1;
1797 type RewardMerkleTreeV1 = espresso_types::v0_3::RewardMerkleTreeV1;
1798 type RewardAccountQueryDataV2 = espresso_types::v0_4::RewardAccountQueryDataV2;
1799 type RewardMerkleTreeV2Data = serde_json::Value;
1800 type StateCert =
1801 hotshot_types::simple_certificate::LightClientStateUpdateCertificateV2<SeqTypes>;
1802
1803 async fn get_account(
1804 &self,
1805 height: u64,
1806 view: u64,
1807 address: String,
1808 ) -> anyhow::Result<Self::AccountQueryData> {
1809 let ds = &*self.data_source;
1810 let view = hotshot_types::data::ViewNumber::new(view);
1811 let account: espresso_types::FeeAccount = address
1812 .parse()
1813 .map_err(|err| bad_request(format!("malformed fee account {address}: {err}")))?;
1814 let instance = ds.node_state().await;
1815 ds.get_account(&instance, height, view, account)
1816 .await
1817 .map_err(|err| not_found(format!("{err:#}")))
1818 }
1819
1820 async fn get_accounts(
1821 &self,
1822 height: u64,
1823 view: u64,
1824 accounts: Vec<Self::FeeAccount>,
1825 ) -> anyhow::Result<Self::FeeMerkleTree> {
1826 let ds = &*self.data_source;
1827 let view = hotshot_types::data::ViewNumber::new(view);
1828 let instance = ds.node_state().await;
1829 ds.get_accounts(&instance, height, view, &accounts)
1830 .await
1831 .map_err(|err| not_found(format!("{err:#}")))
1832 }
1833
1834 async fn get_blocks_frontier(
1835 &self,
1836 height: u64,
1837 view: u64,
1838 ) -> anyhow::Result<Self::BlocksFrontier> {
1839 let ds = &*self.data_source;
1840 let view = hotshot_types::data::ViewNumber::new(view);
1841 let instance = ds.node_state().await;
1842 ds.get_frontier(&instance, height, view)
1843 .await
1844 .map_err(|err| not_found(format!("{err:#}")))
1845 }
1846
1847 async fn get_chain_config(&self, commitment: String) -> anyhow::Result<Self::ChainConfig> {
1848 let ds = &*self.data_source;
1849 let parsed: committable::Commitment<espresso_types::v0_3::ChainConfig> = commitment
1850 .parse()
1851 .map_err(|err| bad_request(format!("malformed chain config commitment: {err}")))?;
1852 ds.get_chain_config(parsed)
1853 .await
1854 .map_err(|err| not_found(format!("{err:#}")))
1855 }
1856
1857 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Self::LeafChain> {
1858 let ds = &*self.data_source;
1859 ds.get_leaf_chain(height)
1860 .await
1861 .map_err(|err| not_found(format!("{err:#}")))
1862 }
1863
1864 async fn get_cert2(&self, height: u64) -> anyhow::Result<Self::Cert2> {
1865 let ds = &*self.data_source;
1866 let response = ds
1867 .get_cert2(height)
1868 .await
1869 .map_err(|err| not_found(format!("{err:#}")))?;
1870 response.ok_or_else(|| not_found(format!("no cert2 available for height {height}")))
1871 }
1872
1873 async fn get_reward_account_v1(
1874 &self,
1875 height: u64,
1876 view: u64,
1877 address: String,
1878 ) -> anyhow::Result<Self::RewardAccountQueryDataV1> {
1879 let ds = &*self.data_source;
1880 let view = hotshot_types::data::ViewNumber::new(view);
1881 let account: espresso_types::v0_4::RewardAccountV2 = address
1882 .parse()
1883 .map_err(|err| bad_request(format!("malformed reward account {address}: {err}")))?;
1884 let instance = ds.node_state().await;
1885 ds.get_reward_account_v1(&instance, height, view, account.into())
1886 .await
1887 .map_err(|err| not_found(format!("{err:#}")))
1888 }
1889
1890 async fn get_reward_accounts_v1(
1891 &self,
1892 height: u64,
1893 view: u64,
1894 accounts: Vec<Self::RewardAccountV1>,
1895 ) -> anyhow::Result<Self::RewardMerkleTreeV1> {
1896 let ds = &*self.data_source;
1897 let view = hotshot_types::data::ViewNumber::new(view);
1898 let instance = ds.node_state().await;
1899 ds.get_reward_accounts_v1(&instance, height, view, &accounts)
1900 .await
1901 .map_err(|err| not_found(format!("{err:#}")))
1902 }
1903
1904 async fn get_reward_account_v2(
1905 &self,
1906 height: u64,
1907 view: u64,
1908 address: String,
1909 ) -> anyhow::Result<Self::RewardAccountQueryDataV2> {
1910 let ds = &*self.data_source;
1911 let view = hotshot_types::data::ViewNumber::new(view);
1912 let account: espresso_types::v0_4::RewardAccountV2 = address
1913 .parse()
1914 .map_err(|err| bad_request(format!("malformed reward account {address}: {err}")))?;
1915 let instance = ds.node_state().await;
1916 ds.get_reward_account_v2(&instance, height, view, account)
1917 .await
1918 .map_err(|err| not_found(format!("{err:#}")))
1919 }
1920
1921 async fn get_reward_merkle_tree_v2(
1922 &self,
1923 height: u64,
1924 view: u64,
1925 ) -> anyhow::Result<Self::RewardMerkleTreeV2Data> {
1926 let ds = &*self.data_source;
1927 let view = hotshot_types::data::ViewNumber::new(view);
1928 let bytes = ds
1929 .get_reward_merkle_tree_v2(height, view)
1930 .await
1931 .map_err(|err| not_found(format!("{err:#}")))?;
1932 Ok(serde_json::to_value(bytes)?)
1935 }
1936
1937 async fn get_state_cert(&self, epoch: u64) -> anyhow::Result<Self::StateCert> {
1938 let ds = &*self.data_source;
1939 ds.get_state_cert(epoch)
1940 .await
1941 .map_err(|err| not_found(format!("{err:#}")))
1942 }
1943}
1944
1945#[async_trait]
1946impl<D> v1::SubmitApi for NodeApiStateImpl<D>
1947where
1948 D: Deref + Clone + Send + Sync + 'static,
1949 D::Target: SubmitDataSourceErased + Send + Sync,
1950{
1951 type Transaction = espresso_types::Transaction;
1952 type TxHash = committable::Commitment<espresso_types::Transaction>;
1953
1954 async fn submit(&self, tx: Self::Transaction) -> anyhow::Result<Self::TxHash> {
1955 let hash = tx.commit();
1956 let ds = &*self.data_source;
1957 ds.submit_erased(tx)
1958 .await
1959 .map_err(|err| anyhow::anyhow!("{err:#}"))?;
1960 Ok(hash)
1961 }
1962}
1963
1964#[async_trait]
1968pub(crate) trait SubmitDataSourceErased {
1969 async fn submit_erased(&self, tx: espresso_types::Transaction) -> anyhow::Result<()>;
1970}
1971
1972#[async_trait]
1973impl<N, P, D> SubmitDataSourceErased
1974 for hotshot_query_service::data_source::ExtensibleDataSource<D, crate::api::ApiState<N, P>>
1975where
1976 N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
1977 P: espresso_types::v0::traits::SequencerPersistence,
1978 D: Send + Sync,
1979{
1980 async fn submit_erased(&self, tx: espresso_types::Transaction) -> anyhow::Result<()> {
1981 <Self as SubmitDataSource<N, P>>::submit(self, tx).await
1982 }
1983}
1984
1985#[async_trait]
1988impl<N, P> SubmitDataSourceErased for crate::api::ApiState<N, P>
1989where
1990 N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
1991 P: espresso_types::v0::traits::SequencerPersistence,
1992{
1993 async fn submit_erased(&self, tx: espresso_types::Transaction) -> anyhow::Result<()> {
1994 <Self as SubmitDataSource<N, P>>::submit(self, tx).await
1995 }
1996}
1997
1998#[async_trait]
1999impl<D> v1::StateSignatureApi for NodeApiStateImpl<D>
2000where
2001 D: Deref + Clone + Send + Sync + 'static,
2002 D::Target: StateSignatureDataSourceErased + Send + Sync,
2003{
2004 type Signature = hotshot_types::light_client::LCV3StateSignatureRequestBody;
2005
2006 async fn get_state_signature(&self, height: u64) -> anyhow::Result<Self::Signature> {
2007 let ds = &*self.data_source;
2008 ds.get_state_signature_erased(height)
2009 .await
2010 .ok_or_else(|| not_found("Signature not found."))
2011 }
2012}
2013
2014#[async_trait]
2015pub(crate) trait StateSignatureDataSourceErased {
2016 async fn get_state_signature_erased(
2017 &self,
2018 height: u64,
2019 ) -> Option<hotshot_types::light_client::LCV3StateSignatureRequestBody>;
2020}
2021
2022#[async_trait]
2023impl<N, P, D> StateSignatureDataSourceErased
2024 for hotshot_query_service::data_source::ExtensibleDataSource<D, crate::api::ApiState<N, P>>
2025where
2026 N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
2027 P: espresso_types::v0::traits::SequencerPersistence,
2028 D: Send + Sync,
2029{
2030 async fn get_state_signature_erased(
2031 &self,
2032 height: u64,
2033 ) -> Option<hotshot_types::light_client::LCV3StateSignatureRequestBody> {
2034 <Self as StateSignatureDataSource<N>>::get_state_signature(self, height).await
2035 }
2036}
2037
2038#[async_trait]
2041impl<N, P> StateSignatureDataSourceErased for crate::api::ApiState<N, P>
2042where
2043 N: hotshot_types::traits::network::ConnectedNetwork<espresso_types::PubKey>,
2044 P: espresso_types::v0::traits::SequencerPersistence,
2045{
2046 async fn get_state_signature_erased(
2047 &self,
2048 height: u64,
2049 ) -> Option<hotshot_types::light_client::LCV3StateSignatureRequestBody> {
2050 <Self as StateSignatureDataSource<N>>::get_state_signature(self, height).await
2051 }
2052}
2053
2054#[async_trait]
2055impl<D> v1::ExplorerApi for NodeApiStateImpl<D>
2056where
2057 D: Deref + Clone + Send + Sync + 'static,
2058 D::Target: hotshot_query_service::explorer::ExplorerDataSource<SeqTypes> + Send + Sync,
2059{
2060 type BlockDetail = hotshot_query_service::explorer::BlockDetailResponse<SeqTypes>;
2061 type BlockSummaries = hotshot_query_service::explorer::BlockSummaryResponse<SeqTypes>;
2062 type TransactionDetail = hotshot_query_service::explorer::TransactionDetailResponse<SeqTypes>;
2063 type TransactionSummaries =
2064 hotshot_query_service::explorer::TransactionSummariesResponse<SeqTypes>;
2065 type ExplorerSummary = hotshot_query_service::explorer::ExplorerSummaryResponse<SeqTypes>;
2066 type SearchResult = hotshot_query_service::explorer::SearchResultResponse<SeqTypes>;
2067
2068 async fn get_block_detail(&self, ident: v1::BlockIdent) -> anyhow::Result<Self::BlockDetail> {
2069 let ds = &*self.data_source;
2070 let target = match ident {
2071 v1::BlockIdent::Height(h) => BlockIdentifier::Height(h as usize),
2072 v1::BlockIdent::Hash(h) => BlockIdentifier::Hash(
2073 h.parse()
2074 .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2075 ),
2076 v1::BlockIdent::Latest => BlockIdentifier::Latest,
2077 };
2078 ds.get_block_detail(target)
2079 .await
2080 .map(Into::into)
2081 .map_err(|err| anyhow::anyhow!("{err}"))
2082 }
2083
2084 async fn get_block_summaries(
2085 &self,
2086 target: v1::BlockIdent,
2087 limit: u64,
2088 ) -> anyhow::Result<Self::BlockSummaries> {
2089 let ds = &*self.data_source;
2090 let num_blocks = std::num::NonZeroUsize::new(limit as usize)
2091 .ok_or_else(|| bad_request("limit must be greater than 0"))?;
2092 if num_blocks.get() > 100 {
2093 return Err(bad_request("limit must be <= 100"));
2094 }
2095 let target = match target {
2096 v1::BlockIdent::Height(h) => BlockIdentifier::Height(h as usize),
2097 v1::BlockIdent::Hash(h) => BlockIdentifier::Hash(
2098 h.parse()
2099 .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2100 ),
2101 v1::BlockIdent::Latest => BlockIdentifier::Latest,
2102 };
2103 ds.get_block_summaries(GetBlockSummariesRequest(BlockRange { target, num_blocks }))
2104 .await
2105 .map(Into::into)
2106 .map_err(|err| anyhow::anyhow!("{err}"))
2107 }
2108
2109 async fn get_transaction_detail(
2110 &self,
2111 ident: v1::TxIdent,
2112 ) -> anyhow::Result<Self::TransactionDetail> {
2113 let ds = &*self.data_source;
2114 let target = match ident {
2115 v1::TxIdent::HeightAndOffset(h, o) => {
2116 TransactionIdentifier::HeightAndOffset(h as usize, o as usize)
2117 },
2118 v1::TxIdent::Hash(h) => TransactionIdentifier::Hash(
2119 h.parse()
2120 .map_err(|err| bad_request(format!("invalid tx hash {h}: {err}")))?,
2121 ),
2122 v1::TxIdent::Latest => TransactionIdentifier::Latest,
2123 };
2124 ds.get_transaction_detail(target)
2125 .await
2126 .map(Into::into)
2127 .map_err(|err| anyhow::anyhow!("{err}"))
2128 }
2129
2130 async fn get_transaction_summaries(
2131 &self,
2132 target: v1::TxIdent,
2133 limit: u64,
2134 filter: v1::TxSummaryFilter,
2135 ) -> anyhow::Result<Self::TransactionSummaries> {
2136 let ds = &*self.data_source;
2137 let num_transactions = std::num::NonZeroUsize::new(limit as usize)
2138 .ok_or_else(|| bad_request("limit must be greater than 0"))?;
2139 if num_transactions.get() > 100 {
2140 return Err(bad_request("limit must be <= 100"));
2141 }
2142 let target = match target {
2143 v1::TxIdent::HeightAndOffset(h, o) => {
2144 TransactionIdentifier::HeightAndOffset(h as usize, o as usize)
2145 },
2146 v1::TxIdent::Hash(h) => TransactionIdentifier::Hash(
2147 h.parse()
2148 .map_err(|err| bad_request(format!("invalid tx hash {h}: {err}")))?,
2149 ),
2150 v1::TxIdent::Latest => TransactionIdentifier::Latest,
2151 };
2152 let filter = match filter {
2153 v1::TxSummaryFilter::None => TransactionSummaryFilter::None,
2154 v1::TxSummaryFilter::Block(b) => TransactionSummaryFilter::Block(b as usize),
2155 v1::TxSummaryFilter::Namespace(n) => TransactionSummaryFilter::RollUp(n.into()),
2156 };
2157 ds.get_transaction_summaries(GetTransactionSummariesRequest {
2158 range: TransactionRange {
2159 target,
2160 num_transactions,
2161 },
2162 filter,
2163 })
2164 .await
2165 .map(Into::into)
2166 .map_err(|err| anyhow::anyhow!("{err}"))
2167 }
2168
2169 async fn get_explorer_summary(&self) -> anyhow::Result<Self::ExplorerSummary> {
2170 let ds = &*self.data_source;
2171 ds.get_explorer_summary()
2172 .await
2173 .map(Into::into)
2174 .map_err(|err| anyhow::anyhow!("{err}"))
2175 }
2176
2177 async fn get_search_result(&self, query: String) -> anyhow::Result<Self::SearchResult> {
2178 let ds = &*self.data_source;
2179 let parsed: tagged_base64::TaggedBase64 = query
2180 .parse()
2181 .map_err(|err| bad_request(format!("invalid search query {query}: {err}")))?;
2182 ds.get_search_results(parsed)
2183 .await
2184 .map(Into::into)
2185 .map_err(|err| anyhow::anyhow!("{err}"))
2186 }
2187}
2188
2189#[async_trait]
2190impl<D> v1::LightClientApi for NodeApiStateImpl<D>
2191where
2192 D: Deref + Clone + Send + Sync + 'static,
2193 D::Target: AvailabilityDataSource<SeqTypes>
2194 + hotshot_query_service::merklized_state::MerklizedStateDataSource<
2195 SeqTypes,
2196 espresso_types::BlockMerkleTree,
2197 3,
2198 > + NodeStateDataSource
2199 + StakeTableDataSource<SeqTypes>
2200 + hotshot_query_service::data_source::VersionedDataSource
2201 + Sized
2202 + Send
2203 + Sync,
2204 for<'a> <D::Target as hotshot_query_service::data_source::VersionedDataSource>::ReadOnly<'a>:
2205 hotshot_query_service::data_source::storage::NodeStorage<SeqTypes>,
2206{
2207 type LeafProof = light_client::consensus::leaf::LeafProof;
2208 type HeaderProof = light_client::consensus::header::HeaderProof;
2209 type StakeTableEvents = Vec<espresso_types::v0_3::StakeTableEvent>;
2210 type PayloadProof = light_client::consensus::payload::PayloadProof;
2211 type NamespaceProof = light_client::consensus::namespace::NamespaceProof;
2212
2213 async fn get_leaf_proof(
2214 &self,
2215 query: v1::LeafQuery,
2216 finalized: Option<u64>,
2217 ) -> anyhow::Result<Self::LeafProof> {
2218 let ds = &*self.data_source;
2219 let fetch_timeout = FETCH_TIMEOUT;
2220
2221 let requested = match query {
2222 v1::LeafQuery::Height(h) => HsLeafId::Number(h as usize),
2223 v1::LeafQuery::Hash(h) => HsLeafId::Hash(
2224 h.parse()
2225 .map_err(|err| bad_request(format!("invalid leaf hash {h}: {err}")))?,
2226 ),
2227 v1::LeafQuery::BlockHash(h) => {
2228 let parsed = h
2229 .parse()
2230 .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?;
2231 let header = AvailabilityDataSource::get_header(ds, HsBlockId::Hash(parsed))
2232 .await
2233 .with_timeout(fetch_timeout)
2234 .await
2235 .ok_or_else(|| not_found(format!("unknown block hash {h}")))?;
2236 HsLeafId::Number(header.height() as usize)
2237 },
2238 v1::LeafQuery::PayloadHash(h) => {
2239 let parsed = h
2240 .parse()
2241 .map_err(|err| bad_request(format!("invalid payload hash {h}: {err}")))?;
2242 let header = AvailabilityDataSource::get_header(ds, HsBlockId::PayloadHash(parsed))
2243 .await
2244 .with_timeout(fetch_timeout)
2245 .await
2246 .ok_or_else(|| not_found(format!("unknown payload hash {h}")))?;
2247 HsLeafId::Number(header.height() as usize)
2248 },
2249 };
2250
2251 let requested_leaf = AvailabilityDataSource::get_leaf(ds, requested)
2252 .await
2253 .with_timeout(fetch_timeout)
2254 .await
2255 .ok_or_else(|| not_found(format!("unknown leaf {requested}")))?;
2256
2257 crate::api::light_client::get_leaf_proof(
2258 ds,
2259 requested_leaf,
2260 finalized.map(|f| f as usize),
2261 fetch_timeout,
2262 lc_leaf_proof_chain_limit(),
2263 )
2264 .await
2265 .map_err(|err| anyhow::anyhow!("{err}"))
2266 }
2267
2268 async fn get_header_proof(
2269 &self,
2270 root: u64,
2271 requested: v1::HeaderQuery,
2272 ) -> anyhow::Result<Self::HeaderProof> {
2273 let ds = &*self.data_source;
2274 let fetch_timeout = FETCH_TIMEOUT;
2275 let requested = match requested {
2276 v1::HeaderQuery::Height(h) => HsBlockId::Number(h as usize),
2277 v1::HeaderQuery::Hash(h) => HsBlockId::Hash(
2278 h.parse()
2279 .map_err(|err| bad_request(format!("invalid block hash {h}: {err}")))?,
2280 ),
2281 v1::HeaderQuery::PayloadHash(h) => HsBlockId::PayloadHash(
2282 h.parse()
2283 .map_err(|err| bad_request(format!("invalid payload hash {h}: {err}")))?,
2284 ),
2285 };
2286 crate::api::light_client::get_header_proof(ds, root, requested, fetch_timeout)
2287 .await
2288 .map_err(|err| anyhow::anyhow!("{err}"))
2289 }
2290
2291 async fn get_light_client_stake_table(
2292 &self,
2293 epoch: u64,
2294 ) -> anyhow::Result<Self::StakeTableEvents> {
2295 let ds = &*self.data_source;
2296 let fetch_timeout = FETCH_TIMEOUT;
2297
2298 let node_state = NodeStateDataSource::node_state(ds).await;
2299 let epoch_height = node_state
2300 .epoch_height
2301 .ok_or_else(|| anyhow::anyhow!("epoch state not set"))?;
2302 let first_epoch = epoch_from_block_number(node_state.epoch_start_block, epoch_height);
2303 if epoch < first_epoch + 2 {
2304 return Err(bad_request(format!(
2305 "epoch must be at least {}",
2306 first_epoch + 2
2307 )));
2308 }
2309
2310 let epoch_root_height = root_block_in_epoch(epoch - 2, epoch_height) as usize;
2311 let epoch_root = AvailabilityDataSource::get_header::<HsBlockId<SeqTypes>>(
2312 ds,
2313 HsBlockId::Number(epoch_root_height),
2314 )
2315 .await
2316 .with_timeout(fetch_timeout)
2317 .await
2318 .ok_or_else(|| not_found(format!("missing epoch root header {epoch_root_height}")))?;
2319 let to_l1_block = epoch_root
2320 .l1_finalized()
2321 .ok_or_else(|| anyhow::anyhow!("epoch root header is missing L1 finalized block"))?
2322 .number();
2323
2324 let from_l1_block = if epoch >= first_epoch + 3 {
2325 let prev_epoch_root_height = root_block_in_epoch(epoch - 3, epoch_height) as usize;
2326 let prev_epoch_root = AvailabilityDataSource::get_header::<HsBlockId<SeqTypes>>(
2327 ds,
2328 HsBlockId::Number(prev_epoch_root_height),
2329 )
2330 .await
2331 .with_timeout(fetch_timeout)
2332 .await
2333 .ok_or_else(|| {
2334 not_found(format!(
2335 "missing previous epoch root header {prev_epoch_root_height}"
2336 ))
2337 })?;
2338 prev_epoch_root
2339 .l1_finalized()
2340 .ok_or_else(|| {
2341 anyhow::anyhow!("previous epoch root header is missing L1 finalized block")
2342 })?
2343 .number()
2344 + 1
2345 } else {
2346 0
2347 };
2348
2349 StakeTableDataSource::stake_table_events(ds, from_l1_block, to_l1_block).await
2350 }
2351
2352 async fn get_payload_proof(&self, height: u64) -> anyhow::Result<Self::PayloadProof> {
2353 let ds = &*self.data_source;
2354 let fetch_timeout = FETCH_TIMEOUT;
2355 let height = height as usize;
2356 let payload = AvailabilityDataSource::get_payload(ds, height)
2357 .await
2358 .with_timeout(fetch_timeout)
2359 .await
2360 .ok_or_else(|| not_found(format!("missing payload {height}")))?;
2361 let vid_common = AvailabilityDataSource::get_vid_common(ds, height)
2362 .await
2363 .with_timeout(fetch_timeout)
2364 .await
2365 .ok_or_else(|| not_found(format!("missing VID common {height}")))?;
2366 Ok(light_client::consensus::payload::PayloadProof::new(
2367 payload.data().clone(),
2368 vid_common.common().clone(),
2369 ))
2370 }
2371
2372 async fn get_payload_proof_range(
2373 &self,
2374 start: u64,
2375 end: u64,
2376 ) -> anyhow::Result<Vec<Self::PayloadProof>> {
2377 let ds = &*self.data_source;
2378 let fetch_timeout = FETCH_TIMEOUT;
2379 let start = start as usize;
2380 let end = end as usize;
2381
2382 let payloads_stream = AvailabilityDataSource::get_payload_range(ds, start..end).await;
2383 let vid_stream = AvailabilityDataSource::get_vid_common_range(ds, start..end).await;
2384 let mut out = Vec::new();
2385 let mut payloads = payloads_stream.enumerate();
2386 let mut vid_commons = vid_stream.enumerate();
2387 loop {
2388 let (next_payload, next_vid) =
2389 futures::future::join(payloads.next(), vid_commons.next()).await;
2390 let (Some((i, payload_fut)), Some((_, vid_fut))) = (next_payload, next_vid) else {
2391 break;
2392 };
2393 let payload = payload_fut
2394 .with_timeout(fetch_timeout)
2395 .await
2396 .ok_or_else(|| not_found(format!("missing payload {}", start + i)))?;
2397 let vid_common = vid_fut
2398 .with_timeout(fetch_timeout)
2399 .await
2400 .ok_or_else(|| not_found(format!("missing VID common {}", start + i)))?;
2401 out.push(light_client::consensus::payload::PayloadProof::new(
2402 payload.data().clone(),
2403 vid_common.common().clone(),
2404 ));
2405 }
2406 Ok(out)
2407 }
2408
2409 async fn get_lc_namespace_proof(
2410 &self,
2411 height: u64,
2412 namespace: u64,
2413 ) -> anyhow::Result<Self::NamespaceProof> {
2414 let ds = &*self.data_source;
2415 let fetch_timeout = FETCH_TIMEOUT;
2416 let mut proofs = crate::api::light_client::get_namespace_proof_range(
2417 ds,
2418 height as usize,
2419 (height + 1) as usize,
2420 namespace,
2421 fetch_timeout,
2422 lc_large_object_range_limit(),
2423 )
2424 .await
2425 .map_err(lc_error)?;
2426 if proofs.len() != 1 {
2427 return Err(anyhow::anyhow!("internal consistency error"));
2428 }
2429 Ok(proofs.remove(0))
2430 }
2431
2432 async fn get_lc_namespace_proof_range(
2433 &self,
2434 start: u64,
2435 end: u64,
2436 namespace: u64,
2437 ) -> anyhow::Result<Vec<Self::NamespaceProof>> {
2438 let ds = &*self.data_source;
2439 let fetch_timeout = FETCH_TIMEOUT;
2440 crate::api::light_client::get_namespace_proof_range(
2441 ds,
2442 start as usize,
2443 end as usize,
2444 namespace,
2445 fetch_timeout,
2446 lc_large_object_range_limit(),
2447 )
2448 .await
2449 .map_err(lc_error)
2450 }
2451
2452 async fn get_lc_namespaces_proof_range(
2453 &self,
2454 start: u64,
2455 end: u64,
2456 namespaces: String,
2457 ) -> anyhow::Result<Vec<std::collections::HashMap<u64, Self::NamespaceProof>>> {
2458 let namespaces = crate::api::light_client::parse_namespaces_str(&namespaces)
2459 .map_err(|err| bad_request(err.to_string()))?;
2460 let ds = &*self.data_source;
2461 let fetch_timeout = FETCH_TIMEOUT;
2462 crate::api::light_client::get_namespaces_proof_range(
2463 ds,
2464 start as usize,
2465 end as usize,
2466 &namespaces,
2467 fetch_timeout,
2468 lc_large_object_range_limit(),
2469 )
2470 .await
2471 .map_err(lc_error)
2472 }
2473}
2474
2475fn lc_large_object_range_limit() -> usize {
2476 hotshot_query_service::availability::Options::default().large_object_range_limit
2477}
2478
2479pub(crate) fn lc_error(err: hotshot_query_service::Error) -> anyhow::Error {
2482 match err.status() {
2483 StatusCode::NOT_FOUND => not_found(err.to_string()),
2484 StatusCode::BAD_REQUEST => bad_request(err.to_string()),
2485 _ => anyhow::anyhow!("{err}"),
2486 }
2487}
2488
2489fn lc_leaf_proof_chain_limit() -> usize {
2494 hotshot_query_service::availability::Options::default().small_object_range_limit
2495}
2496
2497#[async_trait]
2498impl<D> v1::HotShotEventsApi for NodeApiStateImpl<D>
2499where
2500 D: Deref + Clone + Send + Sync + 'static,
2501 D::Target: hotshot_events_service::events_source::EventsSource<SeqTypes> + Send + Sync,
2502{
2503 type Event = std::sync::Arc<hotshot_types::event::Event<SeqTypes>>;
2504 type StartupInfo = hotshot_events_service::events_source::StartupInfo<SeqTypes>;
2505
2506 async fn startup_info(&self) -> anyhow::Result<Self::StartupInfo> {
2507 let ds = &*self.data_source;
2508 Ok(ds.get_startup_info().await)
2509 }
2510
2511 async fn events(&self) -> anyhow::Result<futures::stream::BoxStream<'static, Self::Event>> {
2512 let ds = &*self.data_source;
2513 let stream = ds.get_event_stream(None).await;
2514 Ok(Box::pin(stream))
2515 }
2516}
2517
2518#[async_trait]
2519impl<D> v1::TokenApi for NodeApiStateImpl<D>
2520where
2521 D: Deref + Clone + Send + Sync + 'static,
2522 D::Target: TokenDataSource<SeqTypes> + NodeStateDataSource + Send + Sync,
2523{
2524 async fn total_minted_supply(&self) -> anyhow::Result<String> {
2525 let ds = &*self.data_source;
2526 let value = ds
2527 .get_total_supply_l1()
2528 .await
2529 .map_err(|err| anyhow::anyhow!("failed to get total supply: {err:#}"))?;
2530 Ok(format_ether(value))
2531 }
2532
2533 async fn circulating_supply(&self) -> anyhow::Result<String> {
2534 let calc = fetch_supply_inputs(&*self.data_source).await?;
2535 Ok(format_ether(calc.circulating_supply()))
2536 }
2537
2538 async fn circulating_supply_ethereum(&self) -> anyhow::Result<String> {
2539 let calc = fetch_supply_inputs(&*self.data_source).await?;
2540 Ok(format_ether(calc.circulating_supply_ethereum()))
2541 }
2542
2543 async fn total_issued_supply(&self) -> anyhow::Result<String> {
2544 let calc = fetch_supply_inputs(&*self.data_source).await?;
2545 Ok(format_ether(calc.total_issued_supply()))
2546 }
2547
2548 async fn total_reward_distributed(&self) -> anyhow::Result<String> {
2549 let calc = fetch_supply_inputs(&*self.data_source).await?;
2550 Ok(format_ether(calc.total_reward_distributed()))
2551 }
2552}
2553
2554async fn fetch_supply_inputs<S>(
2555 ds: &S,
2556) -> anyhow::Result<crate::api::unlock_schedule::SupplyCalculator>
2557where
2558 S: TokenDataSource<SeqTypes> + NodeStateDataSource + Sync + ?Sized,
2559{
2560 let node_state = ds.node_state().await;
2561 let chain_id = node_state.chain_config.chain_id;
2562
2563 let header = ds.get_decided_header().await;
2564 let now_secs = header.timestamp_internal();
2565 let total_reward_distributed = header.total_reward_distributed();
2566
2567 let initial_supply = ds
2568 .get_initial_supply_l1()
2569 .await
2570 .map_err(|err| anyhow::anyhow!("failed to get initial supply: {err:#}"))?;
2571
2572 let total_supply_l1 = ds
2573 .get_total_supply_l1()
2574 .await
2575 .map_err(|err| anyhow::anyhow!("failed to get total supply: {err:#}"))?;
2576
2577 Ok(crate::api::unlock_schedule::SupplyCalculator::new(
2578 chain_id,
2579 now_secs,
2580 initial_supply,
2581 total_supply_l1,
2582 total_reward_distributed,
2583 ))
2584}
2585
2586#[tonic::async_trait]
2587impl<D> proto::token_service_server::TokenService for NodeApiStateImpl<D>
2588where
2589 D: Deref + Clone + Send + Sync + 'static,
2590 D::Target: TokenDataSource<SeqTypes> + NodeStateDataSource + Send + Sync,
2591{
2592 async fn get_total_minted_supply(
2593 &self,
2594 _request: tonic::Request<proto::GetTotalMintedSupplyRequest>,
2595 ) -> Result<tonic::Response<proto::TotalMintedSupplyResponse>, tonic::Status> {
2596 let amount = <Self as v1::TokenApi>::total_minted_supply(self)
2597 .await
2598 .map_err(to_status)?;
2599 Ok(tonic::Response::new(proto::TotalMintedSupplyResponse {
2600 amount,
2601 }))
2602 }
2603
2604 async fn get_circulating_supply(
2605 &self,
2606 _request: tonic::Request<proto::GetCirculatingSupplyRequest>,
2607 ) -> Result<tonic::Response<proto::CirculatingSupplyResponse>, tonic::Status> {
2608 let amount = <Self as v1::TokenApi>::circulating_supply(self)
2609 .await
2610 .map_err(to_status)?;
2611 Ok(tonic::Response::new(proto::CirculatingSupplyResponse {
2612 amount,
2613 }))
2614 }
2615
2616 async fn get_circulating_supply_ethereum(
2617 &self,
2618 _request: tonic::Request<proto::GetCirculatingSupplyEthereumRequest>,
2619 ) -> Result<tonic::Response<proto::CirculatingSupplyEthereumResponse>, tonic::Status> {
2620 let amount = <Self as v1::TokenApi>::circulating_supply_ethereum(self)
2621 .await
2622 .map_err(to_status)?;
2623 Ok(tonic::Response::new(
2624 proto::CirculatingSupplyEthereumResponse { amount },
2625 ))
2626 }
2627
2628 async fn get_total_issued_supply(
2629 &self,
2630 _request: tonic::Request<proto::GetTotalIssuedSupplyRequest>,
2631 ) -> Result<tonic::Response<proto::TotalIssuedSupplyResponse>, tonic::Status> {
2632 let amount = <Self as v1::TokenApi>::total_issued_supply(self)
2633 .await
2634 .map_err(to_status)?;
2635 Ok(tonic::Response::new(proto::TotalIssuedSupplyResponse {
2636 amount,
2637 }))
2638 }
2639
2640 async fn get_total_reward_distributed(
2641 &self,
2642 _request: tonic::Request<proto::GetTotalRewardDistributedRequest>,
2643 ) -> Result<tonic::Response<proto::TotalRewardDistributedResponse>, tonic::Status> {
2644 let amount = <Self as v1::TokenApi>::total_reward_distributed(self)
2645 .await
2646 .map_err(to_status)?;
2647 Ok(tonic::Response::new(
2648 proto::TotalRewardDistributedResponse { amount },
2649 ))
2650 }
2651}
2652
2653#[async_trait]
2654impl<D> v1::DatabaseApi for NodeApiStateImpl<D>
2655where
2656 D: Deref + Clone + Send + Sync + 'static,
2657 D::Target: DatabaseMetadataSource + Send + Sync,
2658{
2659 type TableSizes = Vec<TableSize>;
2660 type MigrationStatus = Vec<MigrationStatus>;
2661
2662 async fn get_table_sizes(&self) -> anyhow::Result<Self::TableSizes> {
2663 let ds = &*self.data_source;
2664 ds.get_table_sizes().await
2665 }
2666
2667 async fn get_migration_status(&self) -> anyhow::Result<Self::MigrationStatus> {
2668 let ds = &*self.data_source;
2669 ds.get_migration_status().await
2670 }
2671}
2672
2673#[cfg(test)]
2674mod tests {
2675 use super::*;
2676
2677 fn custom(status: StatusCode) -> hotshot_query_service::Error {
2678 hotshot_query_service::Error::Custom {
2679 message: "boom".into(),
2680 status,
2681 }
2682 }
2683
2684 #[test]
2688 fn range_at_limit_is_allowed() {
2689 let limit = small_object_range_limit();
2690 enforce_range(0, limit, limit).unwrap();
2691 enforce_range(3, limit + 3, limit).unwrap();
2692 }
2693
2694 #[test]
2695 fn range_past_limit_is_rejected() {
2696 for limit in [small_object_range_limit(), large_object_range_limit()] {
2697 let err = enforce_range(0, limit + 1, limit).unwrap_err();
2698 assert!(matches!(
2699 err.downcast_ref::<AvailabilityError>(),
2700 Some(AvailabilityError::RangeExceeded(_))
2701 ));
2702 }
2703 }
2704
2705 #[test]
2709 fn range_limits_track_known_defaults() {
2710 assert_eq!(small_object_range_limit(), 500);
2711 assert_eq!(large_object_range_limit(), 100);
2712 assert_eq!(node_window_limit(), 500);
2713 }
2714
2715 #[test]
2718 fn lc_error_preserves_bad_request() {
2719 let err = lc_error(custom(StatusCode::BAD_REQUEST));
2720 assert!(matches!(
2721 err.downcast_ref::<AvailabilityError>(),
2722 Some(AvailabilityError::BadRequest(_))
2723 ));
2724 }
2725
2726 #[test]
2727 fn lc_error_preserves_not_found() {
2728 let err = lc_error(custom(StatusCode::NOT_FOUND));
2729 assert!(matches!(
2730 err.downcast_ref::<AvailabilityError>(),
2731 Some(AvailabilityError::NotFound(_))
2732 ));
2733 }
2734
2735 #[test]
2736 fn lc_error_other_statuses_stay_internal() {
2737 let err = lc_error(custom(StatusCode::INTERNAL_SERVER_ERROR));
2738 assert!(err.downcast_ref::<AvailabilityError>().is_none());
2739 }
2740}