1use std::{
4 collections::{BTreeSet, HashMap},
5 env,
6 time::Duration,
7};
8
9use anyhow::Result;
10use committable::Committable;
11use espresso_types::{
12 v0_1::ADVZNsProof, v0_3::RewardAccountV1, v0_4::RewardAccountV2, FeeAccount, FeeMerkleTree,
13 NamespaceId, NsProof, PubKey, Transaction,
14};
15#[deprecated(note = "use espresso_types::ADVZNamespaceProofQueryData")]
18pub type ADVZNamespaceProofQueryData = espresso_types::ADVZNamespaceProofQueryData;
19#[deprecated(note = "use espresso_types::NamespaceProofQueryData")]
20pub type NamespaceProofQueryData = espresso_types::NamespaceProofQueryData;
21
22use futures::{try_join, FutureExt};
23use hotshot_query_service::{
24 availability::{self, AvailabilityDataSource, CustomSnafu, FetchBlockSnafu},
25 explorer::{self, ExplorerDataSource},
26 merklized_state::{
27 self, MerklizedState, MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot,
28 },
29 node::{self, NodeDataSource},
30 ApiState, Error, VidCommon,
31};
32use hotshot_types::{
33 data::{EpochNumber, VidCommitment, VidShare, ViewNumber},
34 traits::{
35 network::ConnectedNetwork,
36 node_implementation::{ConsensusTime, Versions},
37 },
38 vid::avidm::AvidMShare,
39};
40use jf_merkle_tree::MerkleTreeScheme;
41use serde::de::Error as _;
42use snafu::OptionExt;
43use tagged_base64::TaggedBase64;
44use tide_disco::{method::ReadState, Api, Error as _, RequestParams, StatusCode};
45use tracing::warn;
46use vbs::version::{StaticVersion, StaticVersionType};
47use vid::avid_m::namespaced::NsAvidMScheme;
48
49use super::{
50 data_source::{
51 CatchupDataSource, HotShotConfigDataSource, NodeStateDataSource, RequestResponseDataSource,
52 SequencerDataSource, StakeTableDataSource, StateSignatureDataSource, SubmitDataSource,
53 },
54 StorageState,
55};
56use crate::{SeqTypes, SequencerApiVersion, SequencerPersistence};
57
58pub(super) fn fee<State, Ver>(
59 api_ver: semver::Version,
60) -> Result<Api<State, merklized_state::Error, Ver>>
61where
62 State: 'static + Send + Sync + ReadState,
63 Ver: 'static + StaticVersionType,
64 <State as ReadState>::State: Send
65 + Sync
66 + MerklizedStateDataSource<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>
67 + MerklizedStateHeightPersistence,
68{
69 let mut options = merklized_state::Options::default();
70 let extension = toml::from_str(include_str!("../../api/fee.toml"))?;
71 options.extensions.push(extension);
72
73 let mut api =
74 merklized_state::define_api::<State, SeqTypes, FeeMerkleTree, Ver, 256>(&options, api_ver)?;
75
76 api.get("getfeebalance", move |req, state| {
77 async move {
78 let address = req.string_param("address")?;
79 let height = state.get_last_state_height().await?;
80 let snapshot = Snapshot::Index(height as u64);
81 let key = address
82 .parse()
83 .map_err(|_| merklized_state::Error::Custom {
84 message: "failed to parse address".to_string(),
85 status: StatusCode::BAD_REQUEST,
86 })?;
87 let path = state.get_path(snapshot, key).await?;
88 Ok(path.elem().copied())
89 }
90 .boxed()
91 })?;
92 Ok(api)
93}
94
95pub(super) fn reward<State, Ver, MT, const ARITY: usize>(
96 api_ver: semver::Version,
97) -> Result<Api<State, merklized_state::Error, Ver>>
98where
99 State: 'static + Send + Sync + ReadState,
100 Ver: 'static + StaticVersionType,
101 MT: MerklizedState<SeqTypes, ARITY>,
102 for<'a> <MT::Commit as TryFrom<&'a TaggedBase64>>::Error: std::fmt::Display,
103 <MT as MerklizedState<SeqTypes, ARITY>>::Entry: std::marker::Copy,
104 <State as ReadState>::State: Send
105 + Sync
106 + MerklizedStateDataSource<SeqTypes, MT, ARITY>
107 + MerklizedStateHeightPersistence,
108{
109 let mut options = merklized_state::Options::default();
110 let extension = toml::from_str(include_str!("../../api/reward.toml"))?;
111 options.extensions.push(extension);
112
113 let mut api =
114 merklized_state::define_api::<State, SeqTypes, MT, Ver, ARITY>(&options, api_ver)?;
115
116 api.get("get_latest_reward_balance", move |req, state| {
117 async move {
118 let address = req.string_param("address")?;
119 let height = state.get_last_state_height().await?;
120 let snapshot = Snapshot::Index(height as u64);
121 let key = address
122 .parse()
123 .map_err(|_| merklized_state::Error::Custom {
124 message: "failed to parse reward address".to_string(),
125 status: StatusCode::BAD_REQUEST,
126 })?;
127 let path = state.get_path(snapshot, key).await?;
128 Ok(path.elem().copied())
129 }
130 .boxed()
131 })?
132 .get("get_reward_balance", move |req, state| {
133 async move {
134 let address = req.string_param("address")?;
135 let height: usize = req.integer_param("height")?;
136 let snapshot = Snapshot::Index(height as u64);
137 let key = address
138 .parse()
139 .map_err(|_| merklized_state::Error::Custom {
140 message: "failed to parse reward address".to_string(),
141 status: StatusCode::BAD_REQUEST,
142 })?;
143 let path = state.get_path(snapshot, key).await?;
144 Ok(path.elem().copied())
145 }
146 .boxed()
147 })?;
148 Ok(api)
149}
150
151pub(super) type AvailState<N, P, D, ApiVer> = ApiState<StorageState<N, P, D, ApiVer>>;
152
153type AvailabilityApi<N, P, D, V, ApiVer> = Api<AvailState<N, P, D, V>, availability::Error, ApiVer>;
154
155pub(super) fn availability<N, P, D, V: Versions>(
159 api_ver: semver::Version,
160) -> Result<AvailabilityApi<N, P, D, V, SequencerApiVersion>>
161where
162 N: ConnectedNetwork<PubKey>,
163 D: SequencerDataSource + Send + Sync + 'static,
164 P: SequencerPersistence,
165{
166 let mut options = availability::Options::default();
167 let extension = toml::from_str(include_str!("../../api/availability.toml"))?;
168 options.extensions.push(extension);
169 let timeout = options.fetch_timeout;
170
171 let mut api = availability::define_api::<AvailState<N, P, D, _>, SeqTypes, _>(
172 &options,
173 SequencerApiVersion::instance(),
174 api_ver.clone(),
175 )?;
176
177 if api_ver.major == 1 {
178 if api_ver.minor >= 1 {
179 api.get("getnamespaceproof", move |req, state| {
181 async move {
182 let height: usize = req.integer_param("height")?;
183 let ns_id = NamespaceId::from(req.integer_param::<_, u32>("namespace")?);
184 let (block, common) = try_join!(
185 async move {
186 state
187 .get_block(height)
188 .await
189 .with_timeout(timeout)
190 .await
191 .context(FetchBlockSnafu {
192 resource: height.to_string(),
193 })
194 },
195 async move {
196 state
197 .get_vid_common(height)
198 .await
199 .with_timeout(timeout)
200 .await
201 .context(FetchBlockSnafu {
202 resource: height.to_string(),
203 })
204 }
205 )?;
206
207 let ns_table = block.payload().ns_table();
208 if let Some(ns_index) = ns_table.find_ns_id(&ns_id) {
209 match NsProof::v1_1_new_with_correct_encoding(
210 block.payload(),
211 &ns_index,
212 common.common(),
213 ) {
214 Some(proof) => Ok(espresso_types::NamespaceProofQueryData {
215 transactions: proof.export_all_txs(&ns_id),
216 proof: Some(proof),
217 }),
218 None => {
219 tracing::debug!(
221 "Failed to generate namespace proof for block {height} and \
222 namespace {ns_id}, trying to generate incorrect encoding \
223 proof"
224 );
225 let mut vid_shares = state
226 .request_vid_shares(
227 height as u64,
228 common.clone(),
229 Duration::from_secs(40),
230 )
231 .await
232 .map_err(|err| {
233 warn!("Failed to request VID shares from network: {err:#}");
234 hotshot_query_service::availability::Error::Custom {
235 message: "Failed to request VID shares from network"
236 .to_string(),
237 status: StatusCode::NOT_FOUND,
238 }
239 })?;
240 let vid_share = state.vid_share(height).await;
241 if let Ok(vid_share) = vid_share {
242 vid_shares.push(vid_share);
243 };
244
245 let vid_shares: Vec<AvidMShare> = vid_shares
247 .into_iter()
248 .filter_map(|share| {
249 if let VidShare::V1(share) = share {
250 Some(share)
251 } else {
252 None
253 }
254 })
255 .collect();
256
257 match NsProof::v1_1_new_with_incorrect_encoding(
258 &vid_shares,
259 ns_table,
260 &ns_index,
261 &common.payload_hash(),
262 common.common(),
263 ) {
264 Some(proof) => Ok(espresso_types::NamespaceProofQueryData {
265 transactions: vec![],
266 proof: Some(proof),
267 }),
268 None => {
269 warn!("Failed to generate proof of incorrect encoding");
270 Err(availability::Error::Custom {
271 message: "Failed to generate proof of incorrect \
272 encoding"
273 .to_string(),
274 status: StatusCode::INTERNAL_SERVER_ERROR,
275 })
276 },
277 }
278 },
279 }
280 } else {
281 Err(availability::Error::Custom {
283 message: "Namespace not found".to_string(),
284 status: StatusCode::NOT_FOUND,
285 })
286 }
287 }
288 .boxed()
289 })?
290 .at("incorrect_encoding_proof", |req, state| {
291 async move {
292 let block_number =
294 req.integer_param::<_, u64>("block_number").map_err(|_| {
295 hotshot_query_service::availability::Error::Custom {
296 message: "Block number is required".to_string(),
297 status: StatusCode::BAD_REQUEST,
298 }
299 })?;
300
301 let vid_common = state
304 .read(|state| state.get_vid_common(block_number as usize).boxed())
305 .await
306 .await;
307
308 let vid_common_clone = vid_common.clone();
311 let mut vid_shares = state
312 .read(|state| {
313 state.request_vid_shares(
314 block_number,
315 vid_common_clone,
316 Duration::from_secs(40),
317 )
318 })
319 .await
320 .map_err(|err| {
321 warn!("Failed to request VID shares from network: {err:#}");
322 hotshot_query_service::availability::Error::Custom {
323 message: "Failed to request VID shares from network".to_string(),
324 status: StatusCode::NOT_FOUND,
325 }
326 })?;
327
328 let vid_share = state
330 .read(|state| state.vid_share(block_number as usize).boxed())
331 .await;
332 if let Ok(vid_share) = vid_share {
333 vid_shares.push(vid_share);
334 };
335
336 let avidm_param = match vid_common.common() {
338 VidCommon::V0(_) => {
339 return Err(hotshot_query_service::availability::Error::Custom {
341 message: "V0 shares not supported yet".to_string(),
342 status: StatusCode::NOT_FOUND,
343 });
344 },
345 VidCommon::V1(v1) => v1,
346 };
347
348 let VidCommitment::V1(local_payload_hash) = vid_common.payload_hash() else {
350 return Err(hotshot_query_service::availability::Error::Custom {
351 message: "V0 shares not supported yet".to_string(),
352 status: StatusCode::NOT_FOUND,
353 });
354 };
355
356 let avidm_shares: Vec<AvidMShare> = vid_shares
358 .into_iter()
359 .filter_map(|share| {
360 if let VidShare::V1(share) = share {
361 Some(share)
362 } else {
363 None
364 }
365 })
366 .collect();
367
368 match NsAvidMScheme::proof_of_incorrect_encoding(
369 avidm_param,
370 &local_payload_hash,
371 &avidm_shares,
372 ) {
373 Ok(proof) => Ok(proof),
374 Err(err) => {
375 warn!("Failed to generate proof of incorrect encoding: {err:#}");
376 Err(hotshot_query_service::availability::Error::Custom {
377 message: "Failed to generate proof of incorrect encoding"
378 .to_string(),
379 status: StatusCode::INTERNAL_SERVER_ERROR,
380 })
381 },
382 }
383 }
384 .boxed()
385 })?;
386 } else {
387 api.get("getnamespaceproof", move |req, state| {
389 async move {
390 let height: usize = req.integer_param("height")?;
391 let ns_id = NamespaceId::from(req.integer_param::<_, u32>("namespace")?);
392 let (block, common) = try_join!(
393 async move {
394 state
395 .get_block(height)
396 .await
397 .with_timeout(timeout)
398 .await
399 .context(FetchBlockSnafu {
400 resource: height.to_string(),
401 })
402 },
403 async move {
404 state
405 .get_vid_common(height)
406 .await
407 .with_timeout(timeout)
408 .await
409 .context(FetchBlockSnafu {
410 resource: height.to_string(),
411 })
412 }
413 )?;
414
415 if let Some(ns_index) = block.payload().ns_table().find_ns_id(&ns_id) {
416 let proof = NsProof::new(block.payload(), &ns_index, common.common())
417 .context(CustomSnafu {
418 message: format!("failed to make proof for namespace {ns_id}"),
419 status: StatusCode::NOT_FOUND,
420 })?;
421
422 Ok(espresso_types::NamespaceProofQueryData {
423 transactions: proof.export_all_txs(&ns_id),
424 proof: Some(proof),
425 })
426 } else {
427 Ok(espresso_types::NamespaceProofQueryData {
429 proof: None,
430 transactions: Vec::new(),
431 })
432 }
433 }
434 .boxed()
435 })?;
436 }
437 } else {
438 api.get("getnamespaceproof", move |req, state| {
439 async move {
440 let height: usize = req.integer_param("height")?;
441 let ns_id = NamespaceId::from(req.integer_param::<_, u32>("namespace")?);
442 let (block, common) = try_join!(
443 async move {
444 state
445 .get_block(height)
446 .await
447 .with_timeout(timeout)
448 .await
449 .context(FetchBlockSnafu {
450 resource: height.to_string(),
451 })
452 },
453 async move {
454 state
455 .get_vid_common(height)
456 .await
457 .with_timeout(timeout)
458 .await
459 .context(FetchBlockSnafu {
460 resource: height.to_string(),
461 })
462 }
463 )?;
464
465 if let Some(ns_index) = block.payload().ns_table().find_ns_id(&ns_id) {
466 let VidCommon::V0(common) = &common.common().clone() else {
467 return Err(availability::Error::Custom {
468 message: "Unsupported VID version, use new API version instead."
469 .to_string(),
470 status: StatusCode::NOT_FOUND,
471 });
472 };
473 let proof = ADVZNsProof::new(block.payload(), &ns_index, common).context(
474 CustomSnafu {
475 message: format!("failed to make proof for namespace {ns_id}"),
476 status: StatusCode::NOT_FOUND,
477 },
478 )?;
479
480 Ok(espresso_types::ADVZNamespaceProofQueryData {
481 transactions: proof.export_all_txs(&ns_id),
482 proof: Some(proof),
483 })
484 } else {
485 Ok(espresso_types::ADVZNamespaceProofQueryData {
487 proof: None,
488 transactions: Vec::new(),
489 })
490 }
491 }
492 .boxed()
493 })?;
494 }
495
496 Ok(api)
497}
498
499type ExplorerApi<N, P, D, V, ApiVer> = Api<AvailState<N, P, D, V>, explorer::Error, ApiVer>;
500
501pub(super) fn explorer<N, P, D, V: Versions>(
502 api_ver: semver::Version,
503) -> Result<ExplorerApi<N, P, D, V, SequencerApiVersion>>
504where
505 N: ConnectedNetwork<PubKey>,
506 D: ExplorerDataSource<SeqTypes> + Send + Sync + 'static,
507 P: SequencerPersistence,
508{
509 let api = explorer::define_api::<AvailState<N, P, D, V>, SeqTypes, _>(
510 SequencerApiVersion::instance(),
511 api_ver,
512 )?;
513 Ok(api)
514}
515
516pub(super) fn node<S>(api_ver: semver::Version) -> Result<Api<S, node::Error, StaticVersion<0, 1>>>
517where
518 S: 'static + Send + Sync + ReadState,
519 <S as ReadState>::State: Send
520 + Sync
521 + StakeTableDataSource<SeqTypes>
522 + NodeDataSource<SeqTypes>
523 + AvailabilityDataSource<SeqTypes>,
524{
525 let mut options = node::Options::default();
527 let extension = toml::from_str(include_str!("../../api/node.toml"))?;
528 options.extensions.push(extension);
529
530 let mut api =
532 node::define_api::<S, SeqTypes, _>(&options, SequencerApiVersion::instance(), api_ver)?;
533
534 api.at("stake_table", |req, state| {
536 async move {
537 let epoch = req
540 .opt_integer_param("epoch_number")
541 .map_err(|_| hotshot_query_service::node::Error::Custom {
542 message: "Epoch number is required".to_string(),
543 status: StatusCode::BAD_REQUEST,
544 })?
545 .map(EpochNumber::new);
546
547 state
548 .read(|state| state.get_stake_table(epoch).boxed())
549 .await
550 .map_err(|err| node::Error::Custom {
551 message: format!("failed to get stake table for epoch={epoch:?}. err={err:#}"),
552 status: StatusCode::NOT_FOUND,
553 })
554 }
555 .boxed()
556 })?
557 .at("stake_table_current", |_, state| {
558 async move {
559 state
560 .read(|state| state.get_stake_table_current().boxed())
561 .await
562 .map_err(|err| node::Error::Custom {
563 message: format!("failed to get current stake table. err={err:#}"),
564 status: StatusCode::NOT_FOUND,
565 })
566 }
567 .boxed()
568 })?
569 .at("get_validators", |req, state| {
570 async move {
571 let epoch = req.integer_param::<_, u64>("epoch_number").map_err(|_| {
572 hotshot_query_service::node::Error::Custom {
573 message: "Epoch number is required".to_string(),
574 status: StatusCode::BAD_REQUEST,
575 }
576 })?;
577
578 state
579 .read(|state| state.get_validators(EpochNumber::new(epoch)).boxed())
580 .await
581 .map_err(|err| hotshot_query_service::node::Error::Custom {
582 message: format!("failed to get validators mapping: err: {err}"),
583 status: StatusCode::NOT_FOUND,
584 })
585 }
586 .boxed()
587 })?
588 .at("current_proposal_participation", |_, state| {
589 async move {
590 Ok(state
591 .read(|state| state.current_proposal_participation().boxed())
592 .await)
593 }
594 .boxed()
595 })?
596 .at("previous_proposal_participation", |_, state| {
597 async move {
598 Ok(state
599 .read(|state| state.previous_proposal_participation().boxed())
600 .await)
601 }
602 .boxed()
603 })?
604 .at("get_block_reward", |req, state| {
605 async move {
606 let epoch = req
607 .opt_integer_param::<_, u64>("epoch_number")?
608 .map(EpochNumber::new);
609
610 state
611 .read(|state| state.get_block_reward(epoch).boxed())
612 .await
613 .map_err(|err| node::Error::Custom {
614 message: format!("failed to get block reward. err={err:#}"),
615 status: StatusCode::NOT_FOUND,
616 })
617 }
618 .boxed()
619 })?;
620
621 Ok(api)
622}
623pub(super) fn submit<N, P, S, ApiVer: StaticVersionType + 'static>(
624 api_ver: semver::Version,
625) -> Result<Api<S, Error, ApiVer>>
626where
627 N: ConnectedNetwork<PubKey>,
628 S: 'static + Send + Sync + ReadState,
629 P: SequencerPersistence,
630 S::State: Send + Sync + SubmitDataSource<N, P>,
631{
632 let toml = toml::from_str::<toml::Value>(include_str!("../../api/submit.toml"))?;
633 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
634
635 api.with_version(api_ver).at("submit", |req, state| {
636 async move {
637 let tx = req
638 .body_auto::<Transaction, ApiVer>(ApiVer::instance())
639 .map_err(Error::from_request_error)?;
640
641 let hash = tx.commit();
642 state
643 .read(|state| state.submit(tx).boxed())
644 .await
645 .map_err(|err| Error::internal(err.to_string()))?;
646 Ok(hash)
647 }
648 .boxed()
649 })?;
650
651 Ok(api)
652}
653
654pub(super) fn state_signature<N, S, ApiVer: StaticVersionType + 'static>(
655 _: ApiVer,
656 api_ver: semver::Version,
657) -> Result<Api<S, Error, ApiVer>>
658where
659 N: ConnectedNetwork<PubKey>,
660 S: 'static + Send + Sync + ReadState,
661 S::State: Send + Sync + StateSignatureDataSource<N>,
662{
663 let toml = toml::from_str::<toml::Value>(include_str!("../../api/state_signature.toml"))?;
664 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
665 api.with_version(api_ver);
666
667 api.get("get_state_signature", |req, state| {
668 async move {
669 let height = req
670 .integer_param("height")
671 .map_err(Error::from_request_error)?;
672 state
673 .get_state_signature(height)
674 .await
675 .ok_or(tide_disco::Error::catch_all(
676 StatusCode::NOT_FOUND,
677 "Signature not found.".to_owned(),
678 ))
679 }
680 .boxed()
681 })?;
682
683 Ok(api)
684}
685
686pub(super) fn catchup<S, ApiVer: StaticVersionType + 'static>(
687 _: ApiVer,
688 api_ver: semver::Version,
689) -> Result<Api<S, Error, ApiVer>>
690where
691 S: 'static + Send + Sync + ReadState,
692 S::State: Send + Sync + NodeStateDataSource + CatchupDataSource,
693{
694 let toml = toml::from_str::<toml::Value>(include_str!("../../api/catchup.toml"))?;
695 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
696 api.with_version(api_ver);
697
698 let parse_height_view = |req: &RequestParams| -> Result<(u64, ViewNumber), Error> {
699 let height = req
700 .integer_param("height")
701 .map_err(Error::from_request_error)?;
702 let view = req
703 .integer_param("view")
704 .map_err(Error::from_request_error)?;
705 Ok((height, ViewNumber::new(view)))
706 };
707
708 let parse_fee_account = |req: &RequestParams| -> Result<FeeAccount, Error> {
709 let raw = req
710 .string_param("address")
711 .map_err(Error::from_request_error)?;
712 raw.parse().map_err(|err| {
713 Error::catch_all(
714 StatusCode::BAD_REQUEST,
715 format!("malformed fee account {raw}: {err}"),
716 )
717 })
718 };
719
720 let parse_reward_account = |req: &RequestParams| -> Result<RewardAccountV2, Error> {
721 let raw = req
722 .string_param("address")
723 .map_err(Error::from_request_error)?;
724 raw.parse().map_err(|err| {
725 Error::catch_all(
726 StatusCode::BAD_REQUEST,
727 format!("malformed reward account {raw}: {err}"),
728 )
729 })
730 };
731
732 api.get("account", move |req, state| {
733 async move {
734 let (height, view) = parse_height_view(&req)?;
735 let account = parse_fee_account(&req)?;
736 state
737 .get_account(&state.node_state().await, height, view, account)
738 .await
739 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
740 }
741 .boxed()
742 })?
743 .at("accounts", move |req, state| {
744 async move {
745 let (height, view) = parse_height_view(&req)?;
746 let accounts = req
747 .body_auto::<Vec<FeeAccount>, ApiVer>(ApiVer::instance())
748 .map_err(Error::from_request_error)?;
749
750 state
751 .read(|state| {
752 async move {
753 state
754 .get_accounts(&state.node_state().await, height, view, &accounts)
755 .await
756 .map_err(|err| {
757 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
758 })
759 }
760 .boxed()
761 })
762 .await
763 }
764 .boxed()
765 })?
766 .get("reward_account", move |req, state| {
767 async move {
768 let (height, view) = parse_height_view(&req)?;
769 let account = parse_reward_account(&req)?;
770 state
771 .get_reward_account_v1(&state.node_state().await, height, view, account.into())
772 .await
773 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
774 }
775 .boxed()
776 })?
777 .at("reward_accounts", move |req, state| {
778 async move {
779 let (height, view) = parse_height_view(&req)?;
780 let accounts = req
781 .body_auto::<Vec<RewardAccountV1>, ApiVer>(ApiVer::instance())
782 .map_err(Error::from_request_error)?;
783
784 state
785 .read(|state| {
786 async move {
787 state
788 .get_reward_accounts_v1(
789 &state.node_state().await,
790 height,
791 view,
792 &accounts,
793 )
794 .await
795 .map_err(|err| {
796 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
797 })
798 }
799 .boxed()
800 })
801 .await
802 }
803 .boxed()
804 })?
805 .get("reward_account_v2", move |req, state| {
806 async move {
807 let (height, view) = parse_height_view(&req)?;
808 let account = parse_reward_account(&req)?;
809
810 state
811 .get_reward_account_v2(&state.node_state().await, height, view, account)
812 .await
813 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
814 }
815 .boxed()
816 })?
817 .at("reward_accounts_v2", move |req, state| {
818 async move {
819 let (height, view) = parse_height_view(&req)?;
820 let accounts = req
821 .body_auto::<Vec<RewardAccountV2>, ApiVer>(ApiVer::instance())
822 .map_err(Error::from_request_error)?;
823
824 state
825 .read(|state| {
826 async move {
827 state
828 .get_reward_accounts_v2(
829 &state.node_state().await,
830 height,
831 view,
832 &accounts,
833 )
834 .await
835 .map_err(|err| {
836 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
837 })
838 }
839 .boxed()
840 })
841 .await
842 }
843 .boxed()
844 })?
845 .get("blocks", |req, state| {
846 async move {
847 let height = req
848 .integer_param("height")
849 .map_err(Error::from_request_error)?;
850 let view = req
851 .integer_param("view")
852 .map_err(Error::from_request_error)?;
853
854 state
855 .get_frontier(&state.node_state().await, height, ViewNumber::new(view))
856 .await
857 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
858 }
859 .boxed()
860 })?
861 .get("chainconfig", |req, state| {
862 async move {
863 let commitment = req
864 .blob_param("commitment")
865 .map_err(Error::from_request_error)?;
866
867 state
868 .get_chain_config(commitment)
869 .await
870 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
871 }
872 .boxed()
873 })?
874 .get("leafchain", |req, state| {
875 async move {
876 let height = req
877 .integer_param("height")
878 .map_err(Error::from_request_error)?;
879 state
880 .get_leaf_chain(height)
881 .await
882 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
883 }
884 .boxed()
885 })?;
886
887 Ok(api)
888}
889
890type MerklizedStateApi<N, P, D, V, ApiVer> =
891 Api<AvailState<N, P, D, V>, merklized_state::Error, ApiVer>;
892pub(super) fn merklized_state<N, P, D, S, V: Versions, const ARITY: usize>(
893 api_ver: semver::Version,
894) -> Result<MerklizedStateApi<N, P, D, V, SequencerApiVersion>>
895where
896 N: ConnectedNetwork<PubKey>,
897 D: MerklizedStateDataSource<SeqTypes, S, ARITY>
898 + Send
899 + Sync
900 + MerklizedStateHeightPersistence
901 + 'static,
902 S: MerklizedState<SeqTypes, ARITY>,
903 P: SequencerPersistence,
904 for<'a> <S::Commit as TryFrom<&'a TaggedBase64>>::Error: std::fmt::Display,
905{
906 let api = merklized_state::define_api::<
907 AvailState<N, P, D, V>,
908 SeqTypes,
909 S,
910 SequencerApiVersion,
911 ARITY,
912 >(&Default::default(), api_ver)?;
913 Ok(api)
914}
915
916pub(super) fn config<S, ApiVer: StaticVersionType + 'static>(
917 _: ApiVer,
918 api_ver: semver::Version,
919) -> Result<Api<S, Error, ApiVer>>
920where
921 S: 'static + Send + Sync + ReadState,
922 S::State: Send + Sync + HotShotConfigDataSource,
923{
924 let toml = toml::from_str::<toml::Value>(include_str!("../../api/config.toml"))?;
925 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
926 api.with_version(api_ver);
927
928 let env_variables = get_public_env_vars()
929 .map_err(|err| Error::catch_all(StatusCode::INTERNAL_SERVER_ERROR, format!("{err:#}")))?;
930
931 api.get("hotshot", |_, state| {
932 async move { Ok(state.get_config().await) }.boxed()
933 })?
934 .get("env", move |_, _| {
935 {
936 let env_variables = env_variables.clone();
937 async move { Ok(env_variables) }
938 }
939 .boxed()
940 })?;
941
942 Ok(api)
943}
944
945fn get_public_env_vars() -> Result<Vec<String>> {
946 let toml: toml::Value = toml::from_str(include_str!("../../api/public-env-vars.toml"))?;
947
948 let keys = toml
949 .get("variables")
950 .ok_or_else(|| toml::de::Error::custom("variables not found"))?
951 .as_array()
952 .ok_or_else(|| toml::de::Error::custom("variables is not an array"))?
953 .clone()
954 .into_iter()
955 .map(|v| v.try_into())
956 .collect::<Result<BTreeSet<String>, toml::de::Error>>()?;
957
958 let hashmap: HashMap<String, String> = env::vars().collect();
959 let mut public_env_vars: Vec<String> = Vec::new();
960 for key in keys {
961 let value = hashmap.get(&key).cloned().unwrap_or_default();
962 public_env_vars.push(format!("{key}={value}"));
963 }
964
965 Ok(public_env_vars)
966}