1use std::{
4 collections::{BTreeSet, HashMap},
5 env,
6};
7
8use anyhow::Result;
9use committable::Committable;
10use espresso_types::{
11 v0_3::RewardAccountV1,
12 v0_4::{RewardAccountV2, RewardClaimError},
13 FeeAccount, FeeMerkleTree, PubKey, Transaction,
14};
15
16use crate::{api::data_source::TokenDataSource, U256};
17#[deprecated(note = "use espresso_types::ADVZNamespaceProofQueryData")]
20pub type ADVZNamespaceProofQueryData = espresso_types::ADVZNamespaceProofQueryData;
21#[deprecated(note = "use espresso_types::NamespaceProofQueryData")]
22pub type NamespaceProofQueryData = espresso_types::NamespaceProofQueryData;
23
24use futures::FutureExt;
25use hotshot_query_service::{
26 availability::AvailabilityDataSource,
27 explorer::{self, ExplorerDataSource},
28 merklized_state::{
29 self, MerklizedState, MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot,
30 },
31 node::{self, NodeDataSource},
32 Error,
33};
34use hotshot_types::{
35 data::{EpochNumber, ViewNumber},
36 traits::{
37 network::ConnectedNetwork,
38 node_implementation::{ConsensusTime, Versions},
39 },
40};
41use jf_merkle_tree_compat::MerkleTreeScheme;
42use serde::de::Error as _;
43use tagged_base64::TaggedBase64;
44use tide_disco::{method::ReadState, Api, Error as _, RequestParams, StatusCode};
45use vbs::version::{StaticVersion, StaticVersionType};
46
47use super::data_source::{
48 CatchupDataSource, HotShotConfigDataSource, NodeStateDataSource, StakeTableDataSource,
49 StateSignatureDataSource, SubmitDataSource,
50};
51use crate::{
52 api::RewardAccountProofDataSource, SeqTypes, SequencerApiVersion, SequencerPersistence,
53};
54
55mod availability;
56pub(super) use availability::*;
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 enum RewardMerkleTreeVersion {
96 V1,
97 V2,
98}
99
100pub(super) fn reward<State, Ver, MT, const ARITY: usize>(
101 api_ver: semver::Version,
102 merkle_tree_version: RewardMerkleTreeVersion,
103) -> Result<Api<State, merklized_state::Error, Ver>>
104where
105 State: 'static + Send + Sync + ReadState,
106 Ver: 'static + StaticVersionType,
107 MT: MerklizedState<SeqTypes, ARITY>,
108 for<'a> <MT::Commit as TryFrom<&'a TaggedBase64>>::Error: std::fmt::Display,
109 <MT as MerklizedState<SeqTypes, ARITY>>::Entry: std::marker::Copy,
110 <State as ReadState>::State: Send
111 + Sync
112 + RewardAccountProofDataSource
113 + MerklizedStateDataSource<SeqTypes, MT, ARITY>
114 + MerklizedStateHeightPersistence,
115{
116 let mut options = merklized_state::Options::default();
117 let extension = toml::from_str(include_str!("../../api/reward.toml"))?;
118 options.extensions.push(extension);
119
120 let mut api =
121 merklized_state::define_api::<State, SeqTypes, MT, Ver, ARITY>(&options, api_ver)?;
122
123 api.get("get_latest_reward_balance", move |req, state| {
124 async move {
125 let address = req.string_param("address")?;
126 let height = state.get_last_state_height().await?;
127 let snapshot = Snapshot::Index(height as u64);
128 let key = address
129 .parse()
130 .map_err(|_| merklized_state::Error::Custom {
131 message: "failed to parse reward address".to_string(),
132 status: StatusCode::BAD_REQUEST,
133 })?;
134 let path = state.get_path(snapshot, key).await?;
135 Ok(path.elem().copied())
136 }
137 .boxed()
138 })?
139 .get("get_reward_balance", move |req, state| {
140 async move {
141 let address = req.string_param("address")?;
142 let height: usize = req.integer_param("height")?;
143 let snapshot = Snapshot::Index(height as u64);
144 let key = address
145 .parse()
146 .map_err(|_| merklized_state::Error::Custom {
147 message: "failed to parse reward address".to_string(),
148 status: StatusCode::BAD_REQUEST,
149 })?;
150 let path = state.get_path(snapshot, key).await?;
151
152 let last_height = state.get_last_state_height().await?;
153
154 if height > last_height {
155 return Err(merklized_state::Error::Custom {
156 message: format!(
157 "requested height {height} is greater than last known height {last_height}"
158 ),
159 status: StatusCode::BAD_REQUEST,
160 });
161 }
162
163 Ok(path.elem().copied())
164 }
165 .boxed()
166 })?;
167
168 match merkle_tree_version {
169 RewardMerkleTreeVersion::V1 => {
170 api.get("get_reward_account_proof", move |req, state| {
171 async move {
172 let address = req.string_param("address")?;
173 let height = req.integer_param("height")?;
174 let account = address
175 .parse()
176 .map_err(|_| merklized_state::Error::Custom {
177 message: format!("invalid reward address: {address}"),
178 status: StatusCode::BAD_REQUEST,
179 })?;
180
181 state
182 .load_v1_reward_account_proof(height, account)
183 .await
184 .map_err(|err| merklized_state::Error::Custom {
185 message: format!(
186 "failed to load v1 reward account {address} at height {height}: \
187 {err}"
188 ),
189 status: StatusCode::NOT_FOUND,
190 })
191 }
192 .boxed()
193 })?;
194 },
195 RewardMerkleTreeVersion::V2 => {
196 api.get("get_reward_account_proof", move |req, state| {
197 async move {
198 let address = req.string_param("address")?;
199 let height = req.integer_param("height")?;
200 let account = address
201 .parse()
202 .map_err(|_| merklized_state::Error::Custom {
203 message: format!("invalid reward address: {address}"),
204 status: StatusCode::BAD_REQUEST,
205 })?;
206
207 state
208 .load_v2_reward_account_proof(height, account)
209 .await
210 .map_err(|err| merklized_state::Error::Custom {
211 message: format!(
212 "failed to load v2 reward account {address} at height {height}: \
213 {err}"
214 ),
215 status: StatusCode::NOT_FOUND,
216 })
217 }
218 .boxed()
219 })?;
220
221 api.get("get_reward_claim_input", move |req, state| {
222 async move {
223 let address = req.string_param("address")?;
224 let height = req.integer_param("height")?;
225 let account = address
226 .parse()
227 .map_err(|_| merklized_state::Error::Custom {
228 message: format!("invalid reward address: {address}"),
229 status: StatusCode::BAD_REQUEST,
230 })?;
231
232 let proof = state
233 .load_v2_reward_account_proof(height, account)
234 .await
235 .map_err(|err| merklized_state::Error::Custom {
236 message: format!(
237 "failed to load v2 reward account {address} at height {height}: \
238 {err}"
239 ),
240 status: StatusCode::NOT_FOUND,
241 })?;
242
243 let claim_input = match proof.to_reward_claim_input() {
246 Ok(input) => input,
247 Err(RewardClaimError::ZeroRewardError) => {
248 return Err(merklized_state::Error::Custom {
249 message: format!(
250 "zero reward balance for {address} at height {height}"
251 ),
252 status: StatusCode::NOT_FOUND,
253 })
254 },
255 Err(RewardClaimError::ProofConversionError(err)) => {
256 let message = format!(
257 "failed to create solidity proof for {address} at height \
258 {height}: {err}",
259 );
260 tracing::warn!("{message}");
261 return Err(merklized_state::Error::Custom {
265 message,
266 status: StatusCode::INTERNAL_SERVER_ERROR,
267 });
268 },
269 };
270
271 Ok(claim_input)
272 }
273 .boxed()
274 })?;
275 },
276 }
277
278 Ok(api)
279}
280
281type ExplorerApi<N, P, D, V, ApiVer> = Api<AvailState<N, P, D, V>, explorer::Error, ApiVer>;
282
283pub(super) fn explorer<N, P, D, V: Versions>(
284 api_ver: semver::Version,
285) -> Result<ExplorerApi<N, P, D, V, SequencerApiVersion>>
286where
287 N: ConnectedNetwork<PubKey>,
288 D: ExplorerDataSource<SeqTypes> + Send + Sync + 'static,
289 P: SequencerPersistence,
290{
291 let api = explorer::define_api::<AvailState<N, P, D, V>, SeqTypes, _>(
292 SequencerApiVersion::instance(),
293 api_ver,
294 )?;
295 Ok(api)
296}
297
298pub(super) fn token<S>(api_ver: semver::Version) -> Result<Api<S, node::Error, StaticVersion<0, 1>>>
299where
300 S: 'static + Send + Sync + ReadState,
301 <S as ReadState>::State: Send
302 + Sync
303 + TokenDataSource<SeqTypes>
304 + NodeDataSource<SeqTypes>
305 + AvailabilityDataSource<SeqTypes>,
306{
307 let mut options = node::Options::default();
309 let extension = toml::from_str(include_str!("../../api/token.toml"))?;
310 options.extensions.push(extension);
311
312 let mut api =
314 node::define_api::<S, SeqTypes, _>(&options, SequencerApiVersion::instance(), api_ver)?;
315
316 api.at("get_total_minted_supply", |_, state| {
318 async move {
319 let value = state
320 .read(|state| state.get_total_supply_l1().boxed())
321 .await
322 .map_err(|err| node::Error::Custom {
323 message: format!("failed to get total supply. err={err:#}"),
324 status: StatusCode::NOT_FOUND,
325 })?;
326
327 let scale = U256::from(10u64.pow(18));
328 let quotient = value / scale;
329 let remainder = value % scale;
330
331 Ok(format!("{quotient}.{remainder}"))
332 }
333 .boxed()
334 })?;
335
336 Ok(api)
337}
338
339pub(super) fn node<S>(api_ver: semver::Version) -> Result<Api<S, node::Error, StaticVersion<0, 1>>>
340where
341 S: 'static + Send + Sync + ReadState,
342 <S as ReadState>::State: Send
343 + Sync
344 + StakeTableDataSource<SeqTypes>
345 + NodeDataSource<SeqTypes>
346 + AvailabilityDataSource<SeqTypes>,
347{
348 let mut options = node::Options::default();
350 let extension = toml::from_str(include_str!("../../api/node.toml"))?;
351 options.extensions.push(extension);
352
353 let mut api =
355 node::define_api::<S, SeqTypes, _>(&options, SequencerApiVersion::instance(), api_ver)?;
356
357 api.at("stake_table", |req, state| {
359 async move {
360 let epoch = req
363 .opt_integer_param("epoch_number")
364 .map_err(|_| hotshot_query_service::node::Error::Custom {
365 message: "Epoch number is required".to_string(),
366 status: StatusCode::BAD_REQUEST,
367 })?
368 .map(EpochNumber::new);
369
370 state
371 .read(|state| state.get_stake_table(epoch).boxed())
372 .await
373 .map_err(|err| node::Error::Custom {
374 message: format!("failed to get stake table for epoch={epoch:?}. err={err:#}"),
375 status: StatusCode::NOT_FOUND,
376 })
377 }
378 .boxed()
379 })?
380 .at("stake_table_current", |_, state| {
381 async move {
382 state
383 .read(|state| state.get_stake_table_current().boxed())
384 .await
385 .map_err(|err| node::Error::Custom {
386 message: format!("failed to get current stake table. err={err:#}"),
387 status: StatusCode::NOT_FOUND,
388 })
389 }
390 .boxed()
391 })?
392 .at("da_stake_table", |req, state| {
393 async move {
394 let epoch = req
397 .opt_integer_param("epoch_number")
398 .map_err(|_| hotshot_query_service::node::Error::Custom {
399 message: "Epoch number is required".to_string(),
400 status: StatusCode::BAD_REQUEST,
401 })?
402 .map(EpochNumber::new);
403
404 state
405 .read(|state| state.get_da_stake_table(epoch).boxed())
406 .await
407 .map_err(|err| node::Error::Custom {
408 message: format!(
409 "failed to get DA stake table for epoch={epoch:?}. err={err:#}"
410 ),
411 status: StatusCode::NOT_FOUND,
412 })
413 }
414 .boxed()
415 })?
416 .at("da_stake_table_current", |_, state| {
417 async move {
418 state
419 .read(|state| state.get_da_stake_table_current().boxed())
420 .await
421 .map_err(|err| node::Error::Custom {
422 message: format!("failed to get current DA stake table. err={err:#}"),
423 status: StatusCode::NOT_FOUND,
424 })
425 }
426 .boxed()
427 })?
428 .at("get_validators", |req, state| {
429 async move {
430 let epoch = req.integer_param::<_, u64>("epoch_number").map_err(|_| {
431 hotshot_query_service::node::Error::Custom {
432 message: "Epoch number is required".to_string(),
433 status: StatusCode::BAD_REQUEST,
434 }
435 })?;
436
437 state
438 .read(|state| state.get_validators(EpochNumber::new(epoch)).boxed())
439 .await
440 .map_err(|err| hotshot_query_service::node::Error::Custom {
441 message: format!("failed to get validators mapping: err: {err}"),
442 status: StatusCode::NOT_FOUND,
443 })
444 }
445 .boxed()
446 })?
447 .at("get_all_validators", |req, state| {
448 async move {
449 let epoch = req.integer_param::<_, u64>("epoch_number").map_err(|_| {
450 hotshot_query_service::node::Error::Custom {
451 message: "Epoch number is required".to_string(),
452 status: StatusCode::BAD_REQUEST,
453 }
454 })?;
455
456 let offset = req.integer_param::<_, u64>("offset")?;
457
458 let limit = req.integer_param::<_, u64>("limit")?;
459 if limit > 1000 {
460 return Err(hotshot_query_service::node::Error::Custom {
461 message: "Limit cannot be greater than 1000".to_string(),
462 status: StatusCode::BAD_REQUEST,
463 });
464 }
465
466 state
467 .read(|state| {
468 state
469 .get_all_validators(EpochNumber::new(epoch), offset, limit)
470 .boxed()
471 })
472 .await
473 .map_err(|err| hotshot_query_service::node::Error::Custom {
474 message: format!("failed to get all validators : err: {err}"),
475 status: StatusCode::INTERNAL_SERVER_ERROR,
476 })
477 }
478 .boxed()
479 })?
480 .at("current_proposal_participation", |_, state| {
481 async move {
482 Ok(state
483 .read(|state| state.current_proposal_participation().boxed())
484 .await)
485 }
486 .boxed()
487 })?
488 .at("previous_proposal_participation", |_, state| {
489 async move {
490 Ok(state
491 .read(|state| state.previous_proposal_participation().boxed())
492 .await)
493 }
494 .boxed()
495 })?
496 .at("get_block_reward", |req, state| {
497 async move {
498 let epoch = req
499 .opt_integer_param::<_, u64>("epoch_number")?
500 .map(EpochNumber::new);
501
502 state
503 .read(|state| state.get_block_reward(epoch).boxed())
504 .await
505 .map_err(|err| node::Error::Custom {
506 message: format!("failed to get block reward. err={err:#}"),
507 status: StatusCode::NOT_FOUND,
508 })
509 }
510 .boxed()
511 })?;
512
513 Ok(api)
514}
515pub(super) fn submit<N, P, S, ApiVer: StaticVersionType + 'static>(
516 api_ver: semver::Version,
517) -> Result<Api<S, Error, ApiVer>>
518where
519 N: ConnectedNetwork<PubKey>,
520 S: 'static + Send + Sync + ReadState,
521 P: SequencerPersistence,
522 S::State: Send + Sync + SubmitDataSource<N, P>,
523{
524 let toml = toml::from_str::<toml::Value>(include_str!("../../api/submit.toml"))?;
525 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
526
527 api.with_version(api_ver).at("submit", |req, state| {
528 async move {
529 let tx = req
530 .body_auto::<Transaction, ApiVer>(ApiVer::instance())
531 .map_err(Error::from_request_error)?;
532
533 let hash = tx.commit();
534 state
535 .read(|state| state.submit(tx).boxed())
536 .await
537 .map_err(|err| Error::internal(err.to_string()))?;
538 Ok(hash)
539 }
540 .boxed()
541 })?;
542
543 Ok(api)
544}
545
546pub(super) fn state_signature<N, S, ApiVer: StaticVersionType + 'static>(
547 _: ApiVer,
548 api_ver: semver::Version,
549) -> Result<Api<S, Error, ApiVer>>
550where
551 N: ConnectedNetwork<PubKey>,
552 S: 'static + Send + Sync + ReadState,
553 S::State: Send + Sync + StateSignatureDataSource<N>,
554{
555 let toml = toml::from_str::<toml::Value>(include_str!("../../api/state_signature.toml"))?;
556 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
557 api.with_version(api_ver);
558
559 api.get("get_state_signature", |req, state| {
560 async move {
561 let height = req
562 .integer_param("height")
563 .map_err(Error::from_request_error)?;
564 state
565 .get_state_signature(height)
566 .await
567 .ok_or(tide_disco::Error::catch_all(
568 StatusCode::NOT_FOUND,
569 "Signature not found.".to_owned(),
570 ))
571 }
572 .boxed()
573 })?;
574
575 Ok(api)
576}
577
578pub(super) fn catchup<S, ApiVer: StaticVersionType + 'static>(
579 _: ApiVer,
580 api_ver: semver::Version,
581) -> Result<Api<S, Error, ApiVer>>
582where
583 S: 'static + Send + Sync + ReadState,
584 S::State: Send + Sync + NodeStateDataSource + CatchupDataSource,
585{
586 let toml = toml::from_str::<toml::Value>(include_str!("../../api/catchup.toml"))?;
587 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
588 api.with_version(api_ver);
589
590 let parse_height_view = |req: &RequestParams| -> Result<(u64, ViewNumber), Error> {
591 let height = req
592 .integer_param("height")
593 .map_err(Error::from_request_error)?;
594 let view = req
595 .integer_param("view")
596 .map_err(Error::from_request_error)?;
597 Ok((height, ViewNumber::new(view)))
598 };
599
600 let parse_fee_account = |req: &RequestParams| -> Result<FeeAccount, Error> {
601 let raw = req
602 .string_param("address")
603 .map_err(Error::from_request_error)?;
604 raw.parse().map_err(|err| {
605 Error::catch_all(
606 StatusCode::BAD_REQUEST,
607 format!("malformed fee account {raw}: {err}"),
608 )
609 })
610 };
611
612 let parse_reward_account = |req: &RequestParams| -> Result<RewardAccountV2, Error> {
613 let raw = req
614 .string_param("address")
615 .map_err(Error::from_request_error)?;
616 raw.parse().map_err(|err| {
617 Error::catch_all(
618 StatusCode::BAD_REQUEST,
619 format!("malformed reward account {raw}: {err}"),
620 )
621 })
622 };
623
624 api.get("account", move |req, state| {
625 async move {
626 let (height, view) = parse_height_view(&req)?;
627 let account = parse_fee_account(&req)?;
628 state
629 .get_account(&state.node_state().await, height, view, account)
630 .await
631 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
632 }
633 .boxed()
634 })?
635 .at("accounts", move |req, state| {
636 async move {
637 let (height, view) = parse_height_view(&req)?;
638 let accounts = req
639 .body_auto::<Vec<FeeAccount>, ApiVer>(ApiVer::instance())
640 .map_err(Error::from_request_error)?;
641
642 state
643 .read(|state| {
644 async move {
645 state
646 .get_accounts(&state.node_state().await, height, view, &accounts)
647 .await
648 .map_err(|err| {
649 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
650 })
651 }
652 .boxed()
653 })
654 .await
655 }
656 .boxed()
657 })?
658 .get("reward_account", move |req, state| {
659 async move {
660 let (height, view) = parse_height_view(&req)?;
661 let account = parse_reward_account(&req)?;
662 state
663 .get_reward_account_v1(&state.node_state().await, height, view, account.into())
664 .await
665 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
666 }
667 .boxed()
668 })?
669 .at("reward_accounts", move |req, state| {
670 async move {
671 let (height, view) = parse_height_view(&req)?;
672 let accounts = req
673 .body_auto::<Vec<RewardAccountV1>, ApiVer>(ApiVer::instance())
674 .map_err(Error::from_request_error)?;
675
676 state
677 .read(|state| {
678 async move {
679 state
680 .get_reward_accounts_v1(
681 &state.node_state().await,
682 height,
683 view,
684 &accounts,
685 )
686 .await
687 .map_err(|err| {
688 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
689 })
690 }
691 .boxed()
692 })
693 .await
694 }
695 .boxed()
696 })?
697 .get("reward_account_v2", move |req, state| {
698 async move {
699 let (height, view) = parse_height_view(&req)?;
700 let account = parse_reward_account(&req)?;
701
702 state
703 .get_reward_account_v2(&state.node_state().await, height, view, account)
704 .await
705 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
706 }
707 .boxed()
708 })?
709 .get("reward_amounts", move |req, state| {
710 async move {
711 let height = req
712 .integer_param::<_, u64>("height")
713 .map_err(Error::from_request_error)?;
714 let offset = req
715 .integer_param::<_, u64>("offset")
716 .map_err(Error::from_request_error)?;
717 let limit = req
718 .integer_param::<_, u64>("limit")
719 .map_err(Error::from_request_error)?;
720
721 if limit > 10_000 {
722 return Err(Error::catch_all(
723 StatusCode::BAD_REQUEST,
724 format!("limit {limit} exceeds maximum allowed 10000"),
725 ));
726 }
727
728 state
729 .get_all_reward_accounts(height, offset, limit)
730 .await
731 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
732 }
733 .boxed()
734 })?
735 .at("reward_accounts_v2", move |req, state| {
736 async move {
737 let (height, view) = parse_height_view(&req)?;
738 let accounts = req
739 .body_auto::<Vec<RewardAccountV2>, ApiVer>(ApiVer::instance())
740 .map_err(Error::from_request_error)?;
741
742 state
743 .read(|state| {
744 async move {
745 state
746 .get_reward_accounts_v2(
747 &state.node_state().await,
748 height,
749 view,
750 &accounts,
751 )
752 .await
753 .map_err(|err| {
754 Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}"))
755 })
756 }
757 .boxed()
758 })
759 .await
760 }
761 .boxed()
762 })?
763 .get("blocks", |req, state| {
764 async move {
765 let height = req
766 .integer_param("height")
767 .map_err(Error::from_request_error)?;
768 let view = req
769 .integer_param("view")
770 .map_err(Error::from_request_error)?;
771
772 state
773 .get_frontier(&state.node_state().await, height, ViewNumber::new(view))
774 .await
775 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
776 }
777 .boxed()
778 })?
779 .get("chainconfig", |req, state| {
780 async move {
781 let commitment = req
782 .blob_param("commitment")
783 .map_err(Error::from_request_error)?;
784
785 state
786 .get_chain_config(commitment)
787 .await
788 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
789 }
790 .boxed()
791 })?
792 .get("leafchain", |req, state| {
793 async move {
794 let height = req
795 .integer_param("height")
796 .map_err(Error::from_request_error)?;
797 state
798 .get_leaf_chain(height)
799 .await
800 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
801 }
802 .boxed()
803 })?
804 .get("state_cert", |req, state| {
805 async move {
806 let epoch = req
807 .integer_param("epoch")
808 .map_err(Error::from_request_error)?;
809 state
810 .get_state_cert(epoch)
811 .await
812 .map_err(|err| Error::catch_all(StatusCode::NOT_FOUND, format!("{err:#}")))
813 }
814 .boxed()
815 })?;
816
817 Ok(api)
818}
819
820type MerklizedStateApi<N, P, D, V, ApiVer> =
821 Api<AvailState<N, P, D, V>, merklized_state::Error, ApiVer>;
822pub(super) fn merklized_state<N, P, D, S, V: Versions, const ARITY: usize>(
823 api_ver: semver::Version,
824) -> Result<MerklizedStateApi<N, P, D, V, SequencerApiVersion>>
825where
826 N: ConnectedNetwork<PubKey>,
827 D: MerklizedStateDataSource<SeqTypes, S, ARITY>
828 + Send
829 + Sync
830 + MerklizedStateHeightPersistence
831 + 'static,
832 S: MerklizedState<SeqTypes, ARITY>,
833 P: SequencerPersistence,
834 for<'a> <S::Commit as TryFrom<&'a TaggedBase64>>::Error: std::fmt::Display,
835{
836 let api = merklized_state::define_api::<
837 AvailState<N, P, D, V>,
838 SeqTypes,
839 S,
840 SequencerApiVersion,
841 ARITY,
842 >(&Default::default(), api_ver)?;
843 Ok(api)
844}
845
846pub(super) fn config<S, ApiVer: StaticVersionType + 'static>(
847 _: ApiVer,
848 api_ver: semver::Version,
849) -> Result<Api<S, Error, ApiVer>>
850where
851 S: 'static + Send + Sync + ReadState,
852 S::State: Send + Sync + HotShotConfigDataSource,
853{
854 let toml = toml::from_str::<toml::Value>(include_str!("../../api/config.toml"))?;
855 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
856 api.with_version(api_ver);
857
858 let env_variables = get_public_env_vars()
859 .map_err(|err| Error::catch_all(StatusCode::INTERNAL_SERVER_ERROR, format!("{err:#}")))?;
860
861 api.get("hotshot", |_, state| {
862 async move { Ok(state.get_config().await) }.boxed()
863 })?
864 .get("env", move |_, _| {
865 {
866 let env_variables = env_variables.clone();
867 async move { Ok(env_variables) }
868 }
869 .boxed()
870 })?;
871
872 Ok(api)
873}
874
875fn get_public_env_vars() -> Result<Vec<String>> {
876 let toml: toml::Value = toml::from_str(include_str!("../../api/public-env-vars.toml"))?;
877
878 let keys = toml
879 .get("variables")
880 .ok_or_else(|| toml::de::Error::custom("variables not found"))?
881 .as_array()
882 .ok_or_else(|| toml::de::Error::custom("variables is not an array"))?
883 .clone()
884 .into_iter()
885 .map(|v| v.try_into())
886 .collect::<Result<BTreeSet<String>, toml::de::Error>>()?;
887
888 let hashmap: HashMap<String, String> = env::vars().collect();
889 let mut public_env_vars: Vec<String> = Vec::new();
890 for key in keys {
891 let value = hashmap.get(&key).cloned().unwrap_or_default();
892 public_env_vars.push(format!("{key}={value}"));
893 }
894
895 Ok(public_env_vars)
896}