1use std::{collections::HashMap, pin::Pin, sync::Arc, time::Duration};
2
3use ::light_client::{
4 LightClient,
5 client::{FallbackClient, QueryServiceClient},
6 state::{Genesis, LightClientOptions},
7 storage::{LightClientSqliteOptions, SqliteStorage},
8};
9use alloy::primitives::U256;
10use anyhow::{Context, bail, ensure};
11use async_lock::RwLock;
12use async_once_cell::Lazy;
13use async_trait::async_trait;
14use committable::Commitment;
15use data_source::{
16 CatchupDataSource, RequestResponseDataSource, StakeTableDataSource, StakeTableWithEpochNumber,
17 StateCertDataSource, StateCertFetchingDataSource, SubmitDataSource,
18};
19use derivative::Derivative;
20use espresso_types::{
21 AccountQueryData, AuthenticatedValidatorMap, BlockMerkleTree, FeeAccount, FeeMerkleTree, Leaf2,
22 NodeState, PubKey, Transaction,
23 config::PublicNetworkConfig,
24 retain_accounts,
25 traits::EventsPersistenceRead,
26 v0::traits::{SequencerPersistence, StateCatchup},
27 v0_3::{
28 ChainConfig, RegisteredValidator, RewardAccountQueryDataV1, RewardAccountV1, RewardAmount,
29 RewardMerkleTreeV1, StakeTableEvent,
30 },
31 v0_4::{
32 PermittedRewardMerkleTreeV2, RewardAccountQueryDataV2, RewardAccountV2, RewardMerkleTreeV2,
33 },
34};
35use futures::{
36 future::{BoxFuture, Future, FutureExt},
37 stream::BoxStream,
38};
39use hotshot_contract_adapter::sol_types::EspToken;
40use hotshot_events_service::events_source::{
41 EventFilterSet, EventsSource, EventsStreamer, StartupInfo,
42};
43use hotshot_query_service::{
44 availability::VidCommonQueryData,
45 data_source::ExtensibleDataSource,
46 fetching::{self, Provider},
47};
48use hotshot_types::{
49 PeerConfig,
50 data::{EpochNumber, VidCommitment, VidCommon, VidShare, ViewNumber},
51 event::{Event, LegacyEvent},
52 light_client::LCV3StateSignatureRequestBody,
53 network::NetworkConfig,
54 simple_certificate::LightClientStateUpdateCertificateV2,
55 stake_table::HSStakeTable,
56 traits::{
57 election::{Membership, MembershipSnapshot, NonEpochMembershipSnapshot},
58 network::ConnectedNetwork,
59 },
60 utils::epoch_from_block_number,
61 vid::avidm::{AvidMScheme, init_avidm_param},
62 vote::HasViewNumber,
63};
64use itertools::Itertools;
65use jf_merkle_tree_compat::MerkleTreeScheme;
66use moka::future::Cache;
67use rand::Rng;
68use request_response::RequestType;
69use serde::{Deserialize, Serialize};
70use tokio::time::timeout;
71use url::Url;
72use vbs::version::Version;
73
74use self::data_source::{
75 HotShotConfigDataSource, NodeKeysDataSource, NodePublicKeys, NodeStateDataSource,
76 StateSignatureDataSource,
77};
78use crate::{
79 SeqTypes, SequencerApiVersion, SequencerContext,
80 api::data_source::TokenDataSource,
81 catchup::{
82 CatchupStorage, add_fee_accounts_to_state, add_v1_reward_accounts_to_state,
83 add_v2_reward_accounts_to_state,
84 },
85 consensus_handle::ConsensusHandle,
86 context::ConsensusNode,
87 request_response::{
88 data_source::{retain_v1_reward_accounts, retain_v2_reward_accounts},
89 request::{Request, Response},
90 },
91 state_cert::{StateCertFetchError, validate_state_cert},
92 state_signature::StateSigner,
93};
94
95pub mod data_source;
96pub mod fs;
97pub mod light_client;
98pub mod options;
99pub mod sql;
100pub mod state;
101pub mod unlock_schedule;
102mod update;
103
104pub use options::Options;
105
106pub type BlocksFrontier = <BlockMerkleTree as MerkleTreeScheme>::MembershipProof;
107
108type BoxLazy<T> = Pin<Arc<Lazy<T, BoxFuture<'static, T>>>>;
109
110#[derive(Derivative)]
111#[derivative(Clone(bound = ""), Debug(bound = ""))]
112struct ApiState<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> {
113 #[derivative(Debug = "ignore")]
118 sequencer_context: BoxLazy<SequencerContext<N, P>>,
119
120 token_supply: Cache<(), U256>,
122}
123
124impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> ApiState<N, P> {
125 fn new(context_init: impl Future<Output = SequencerContext<N, P>> + Send + 'static) -> Self {
126 Self {
127 sequencer_context: Arc::pin(Lazy::from_future(context_init.boxed())),
128 token_supply: Cache::builder()
129 .max_capacity(1)
130 .time_to_live(Duration::from_secs(3600))
131 .build(),
132 }
133 }
134
135 async fn state_signer(&self) -> Arc<RwLock<StateSigner<SequencerApiVersion>>> {
136 self.sequencer_context
137 .as_ref()
138 .get()
139 .await
140 .get_ref()
141 .state_signer()
142 }
143
144 async fn event_streamer(&self) -> Arc<RwLock<EventsStreamer<SeqTypes>>> {
145 self.sequencer_context
146 .as_ref()
147 .get()
148 .await
149 .get_ref()
150 .event_streamer()
151 }
152
153 async fn consensus_handle(&self) -> Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>> {
154 self.sequencer_context
155 .as_ref()
156 .get()
157 .await
158 .get_ref()
159 .consensus_handle()
160 }
161
162 async fn network_config(&self) -> NetworkConfig<SeqTypes> {
163 self.sequencer_context
164 .as_ref()
165 .get()
166 .await
167 .get_ref()
168 .network_config()
169 }
170}
171
172type StorageState<N, P, D> = ExtensibleDataSource<D, ApiState<N, P>>;
173
174#[async_trait]
175impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> EventsSource<SeqTypes>
176 for ApiState<N, P>
177{
178 type EventStream = BoxStream<'static, Arc<Event<SeqTypes>>>;
179 type LegacyEventStream = BoxStream<'static, Arc<LegacyEvent<SeqTypes>>>;
180
181 async fn get_event_stream(
182 &self,
183 _filter: Option<EventFilterSet<SeqTypes>>,
184 ) -> Self::EventStream {
185 self.event_streamer()
186 .await
187 .read()
188 .await
189 .get_event_stream(None)
190 .await
191 }
192
193 async fn get_legacy_event_stream(
194 &self,
195 _filter: Option<EventFilterSet<SeqTypes>>,
196 ) -> Self::LegacyEventStream {
197 self.event_streamer()
198 .await
199 .read()
200 .await
201 .get_legacy_event_stream(None)
202 .await
203 }
204
205 async fn get_startup_info(&self) -> StartupInfo<SeqTypes> {
206 self.event_streamer()
207 .await
208 .read()
209 .await
210 .get_startup_info()
211 .await
212 }
213}
214
215impl<N: ConnectedNetwork<PubKey>, D: Send + Sync, P: SequencerPersistence> TokenDataSource<SeqTypes>
216 for StorageState<N, P, D>
217{
218 async fn get_initial_supply_l1(&self) -> anyhow::Result<U256> {
219 self.as_ref().get_initial_supply_l1().await
220 }
221
222 async fn get_total_supply_l1(&self) -> anyhow::Result<U256> {
223 self.as_ref().get_total_supply_l1().await
224 }
225
226 async fn get_decided_header(&self) -> espresso_types::Header {
227 self.as_ref().get_decided_header().await
228 }
229}
230
231impl<N: ConnectedNetwork<PubKey>, D: Send + Sync, P: SequencerPersistence> SubmitDataSource<N, P>
232 for StorageState<N, P, D>
233{
234 async fn submit(&self, tx: Transaction) -> anyhow::Result<()> {
235 self.as_ref().submit(tx).await
236 }
237}
238
239impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence> StakeTableDataSource<SeqTypes>
240 for StorageState<N, P, D>
241{
242 async fn get_stake_table(
244 &self,
245 epoch: Option<EpochNumber>,
246 ) -> anyhow::Result<Vec<PeerConfig<SeqTypes>>> {
247 self.as_ref().get_stake_table(epoch).await
248 }
249
250 async fn get_stake_table_current(&self) -> anyhow::Result<StakeTableWithEpochNumber<SeqTypes>> {
252 self.as_ref().get_stake_table_current().await
253 }
254
255 async fn get_da_stake_table(
257 &self,
258 epoch: Option<EpochNumber>,
259 ) -> anyhow::Result<Vec<PeerConfig<SeqTypes>>> {
260 self.as_ref().get_da_stake_table(epoch).await
261 }
262
263 async fn get_da_stake_table_current(
265 &self,
266 ) -> anyhow::Result<StakeTableWithEpochNumber<SeqTypes>> {
267 self.as_ref().get_da_stake_table_current().await
268 }
269
270 async fn get_validators(
272 &self,
273 epoch: EpochNumber,
274 ) -> anyhow::Result<AuthenticatedValidatorMap> {
275 self.as_ref().get_validators(epoch).await
276 }
277
278 async fn get_block_reward(
279 &self,
280 epoch: Option<EpochNumber>,
281 ) -> anyhow::Result<Option<RewardAmount>> {
282 self.as_ref().get_block_reward(epoch).await
283 }
284 async fn current_proposal_participation(&self) -> HashMap<PubKey, f64> {
286 self.as_ref().current_proposal_participation().await
287 }
288 async fn proposal_participation(&self, epoch: EpochNumber) -> HashMap<PubKey, f64> {
290 self.as_ref().proposal_participation(epoch).await
291 }
292 async fn current_vote_participation(&self) -> HashMap<PubKey, f64> {
294 self.as_ref().current_vote_participation().await
295 }
296 async fn vote_participation(&self, epoch: EpochNumber) -> HashMap<PubKey, f64> {
298 self.as_ref().vote_participation(epoch).await
299 }
300
301 async fn get_all_validators(
302 &self,
303 epoch: EpochNumber,
304 offset: u64,
305 limit: u64,
306 ) -> anyhow::Result<Vec<RegisteredValidator<PubKey>>> {
307 self.as_ref().get_all_validators(epoch, offset, limit).await
308 }
309
310 async fn stake_table_events(
311 &self,
312 from_l1_block: u64,
313 to_l1_block: u64,
314 ) -> anyhow::Result<Vec<StakeTableEvent>> {
315 self.as_ref()
316 .stake_table_events(from_l1_block, to_l1_block)
317 .await
318 }
319}
320
321impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> TokenDataSource<SeqTypes>
322 for ApiState<N, P>
323{
324 async fn get_initial_supply_l1(&self) -> anyhow::Result<U256> {
325 let node_state = self.sequencer_context.as_ref().get().await.node_state();
326 let fetcher = node_state.coordinator.membership().fetcher().clone();
327 Ok(fetcher.initial_supply_or_fetch().await?)
328 }
329
330 async fn get_total_supply_l1(&self) -> anyhow::Result<U256> {
331 match self.token_supply.get(&()).await {
332 Some(supply) => Ok(supply),
333 None => {
334 let node_state = self.sequencer_context.as_ref().get().await.node_state();
335 let token_contract_address = node_state.token_contract_address().await?;
336
337 let provider = node_state.l1_client.provider;
338
339 let token = EspToken::new(token_contract_address, provider.clone());
340
341 let supply = token
342 .totalSupply()
343 .call()
344 .await
345 .context("Failed to retrieve totalSupply from the contract")?;
346
347 self.token_supply.insert((), supply).await;
348
349 Ok(supply)
350 },
351 }
352 }
353
354 async fn get_decided_header(&self) -> espresso_types::Header {
355 self.consensus_handle()
356 .await
357 .decided_leaf()
358 .await
359 .block_header()
360 .clone()
361 }
362}
363
364impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> StakeTableDataSource<SeqTypes>
365 for ApiState<N, P>
366{
367 async fn get_stake_table(
369 &self,
370 epoch: Option<EpochNumber>,
371 ) -> anyhow::Result<Vec<PeerConfig<SeqTypes>>> {
372 let handle = self.consensus_handle().await;
373 if let Some(requested) = epoch {
374 let first_epoch = handle
375 .membership_coordinator()
376 .await
377 .membership()
378 .first_epoch();
379 if let Some(first_epoch) = first_epoch
380 && requested < first_epoch
381 {
382 return Err(anyhow::anyhow!(
383 "requested stake table for epoch {requested:?} is below the first epoch \
384 {first_epoch:?}"
385 ));
386 }
387 }
388 let highest_epoch = handle.current_epoch().await.map(|e| e + 1);
389 if epoch > highest_epoch {
390 return Err(anyhow::anyhow!(
391 "requested stake table for epoch {epoch:?} is beyond the current epoch + 1 \
392 {highest_epoch:?}"
393 ));
394 }
395 let mem = handle
396 .membership_coordinator()
397 .await
398 .stake_table_for_epoch(epoch)?;
399
400 Ok(mem.stake_table().cloned().collect())
401 }
402
403 async fn get_stake_table_current(&self) -> anyhow::Result<StakeTableWithEpochNumber<SeqTypes>> {
405 let epoch = self.consensus_handle().await.current_epoch().await;
406
407 Ok(StakeTableWithEpochNumber {
408 epoch,
409 stake_table: self.get_stake_table(epoch).await?,
410 })
411 }
412
413 async fn get_da_stake_table(
415 &self,
416 epoch: Option<EpochNumber>,
417 ) -> anyhow::Result<Vec<PeerConfig<SeqTypes>>> {
418 let coordinator = self.consensus_handle().await.membership_coordinator().await;
419 Ok(match epoch {
420 Some(e) => coordinator
421 .membership()
422 .snapshot(e)
423 .map(|s| s.da_stake_table().cloned().collect())
424 .unwrap_or_default(),
425 None => coordinator
426 .membership()
427 .non_epoch_snapshot()
428 .da_stake_table()
429 .cloned()
430 .collect(),
431 })
432 }
433
434 async fn get_da_stake_table_current(
436 &self,
437 ) -> anyhow::Result<StakeTableWithEpochNumber<SeqTypes>> {
438 let epoch = self.consensus_handle().await.current_epoch().await;
439
440 Ok(StakeTableWithEpochNumber {
441 epoch,
442 stake_table: self.get_da_stake_table(epoch).await?,
443 })
444 }
445
446 async fn get_block_reward(
447 &self,
448 epoch: Option<EpochNumber>,
449 ) -> anyhow::Result<Option<RewardAmount>> {
450 let coordinator = self.consensus_handle().await.membership_coordinator().await;
451
452 let membership = coordinator.membership();
453 let block_reward = match epoch {
454 None => membership.fixed_block_reward(),
455 Some(e) => membership.epoch_block_reward(e),
456 };
457
458 Ok(block_reward)
459 }
460
461 async fn get_validators(&self, e: EpochNumber) -> anyhow::Result<AuthenticatedValidatorMap> {
463 Ok(self
464 .consensus_handle()
465 .await
466 .membership_coordinator()
467 .await
468 .membership_for_epoch(Some(e))
469 .context("membership not found")?
470 .snapshot()
471 .with_context(|| format!("no committee for epoch={e}"))?
472 .validators()
473 .clone())
474 }
475
476 async fn current_proposal_participation(&self) -> HashMap<PubKey, f64> {
478 self.consensus_handle()
479 .await
480 .current_proposal_participation()
481 .await
482 }
483
484 async fn proposal_participation(&self, epoch: EpochNumber) -> HashMap<PubKey, f64> {
486 self.consensus_handle()
487 .await
488 .proposal_participation(epoch)
489 .await
490 }
491
492 async fn current_vote_participation(&self) -> HashMap<PubKey, f64> {
494 self.consensus_handle()
495 .await
496 .current_vote_participation()
497 .await
498 }
499
500 async fn vote_participation(&self, epoch: EpochNumber) -> HashMap<PubKey, f64> {
502 self.consensus_handle()
503 .await
504 .vote_participation(epoch)
505 .await
506 }
507
508 async fn get_all_validators(
509 &self,
510 epoch: EpochNumber,
511 offset: u64,
512 limit: u64,
513 ) -> anyhow::Result<Vec<RegisteredValidator<PubKey>>> {
514 let storage = self.consensus_handle().await.storage().await;
515 storage.load_all_validators(epoch, offset, limit).await
516 }
517
518 async fn stake_table_events(
519 &self,
520 from_l1_block: u64,
521 to_l1_block: u64,
522 ) -> anyhow::Result<Vec<StakeTableEvent>> {
523 let storage = self.consensus_handle().await.storage().await;
524 let (status, events) = storage.load_events(from_l1_block, to_l1_block).await?;
525 ensure!(
526 status == Some(EventsPersistenceRead::Complete),
527 "some events in range [{from_l1_block}, {to_l1_block}] are not available ({status:?})"
528 );
529 Ok(events.into_iter().map(|(_, event)| event).collect())
530 }
531}
532
533impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence>
534 RequestResponseDataSource<SeqTypes> for StorageState<N, P, D>
535{
536 async fn request_vid_shares(
537 &self,
538 block_number: u64,
539 vid_common_data: VidCommonQueryData<SeqTypes>,
540 timeout_duration: Duration,
541 ) -> BoxFuture<'static, anyhow::Result<Vec<VidShare>>> {
542 self.as_ref()
543 .request_vid_shares(block_number, vid_common_data, timeout_duration)
544 .await
545 }
546}
547
548#[async_trait]
549impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence>
550 StateCertFetchingDataSource<SeqTypes> for StorageState<N, P, D>
551{
552 async fn request_state_cert(
553 &self,
554 epoch: u64,
555 timeout: Duration,
556 ) -> Result<LightClientStateUpdateCertificateV2<SeqTypes>, StateCertFetchError> {
557 self.as_ref().request_state_cert(epoch, timeout).await
558 }
559}
560
561impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> RequestResponseDataSource<SeqTypes>
562 for ApiState<N, P>
563{
564 async fn request_vid_shares(
565 &self,
566 block_number: u64,
567 vid_common_data: VidCommonQueryData<SeqTypes>,
568 duration: Duration,
569 ) -> BoxFuture<'static, anyhow::Result<Vec<VidShare>>> {
570 let request_response_protocol = self
572 .sequencer_context
573 .as_ref()
574 .get()
575 .await
576 .request_response_protocol
577 .clone();
578
579 async move {
580 let total_weight = match vid_common_data.common() {
582 VidCommon::V0(_) => {
583 return Err(anyhow::anyhow!(
585 "V0 total weight calculation not supported yet"
586 ));
587 },
588 VidCommon::V1(v1) => v1.total_weights,
589 VidCommon::V2(v2) => v2.param.total_weights,
590 };
591
592 let avidm_param = init_avidm_param(total_weight)
594 .with_context(|| "failed to initialize avidm param")?;
595
596 let VidCommitment::V1(local_payload_hash) = vid_common_data.payload_hash() else {
598 bail!("V0 share verification not supported yet");
599 };
600
601 let request_id = rand::thread_rng().r#gen();
603
604 let received_shares = Arc::new(parking_lot::Mutex::new(Vec::new()));
606 let received_shares_clone = received_shares.clone();
607 let request_result: anyhow::Result<_, _> = timeout(
608 duration,
609 request_response_protocol.request_indefinitely::<_, _, _>(
610 Request::VidShare(block_number, request_id),
611 RequestType::Batched,
612 move |_request, response| {
613 let avidm_param = avidm_param.clone();
614 let received_shares = received_shares_clone.clone();
615 async move {
616 let Response::VidShare(VidShare::V1(received_share)) = response else {
618 bail!("V0 share verification not supported yet");
619 };
620
621 let Ok(Ok(_)) = AvidMScheme::verify_share(
623 &avidm_param,
624 &local_payload_hash,
625 &received_share,
626 ) else {
627 bail!("share verification failed");
628 };
629
630 received_shares.lock().push(received_share);
632
633 bail!("waiting for more shares");
634
635 #[allow(unreachable_code)]
636 Ok(())
637 }
638 },
639 ),
640 )
641 .await;
642
643 match request_result {
645 Err(_) => {
646 Ok(received_shares
648 .lock()
649 .clone()
650 .into_iter()
651 .map(VidShare::V1)
652 .collect())
653 },
654
655 Ok(Err(e)) => Err(e).with_context(|| "failed to request vid shares"),
657
658 Ok(Ok(_)) => bail!("this should not be possible"),
660 }
661 }
662 .boxed()
663 }
664}
665
666#[async_trait]
667impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> StateCertFetchingDataSource<SeqTypes>
668 for ApiState<N, P>
669{
670 async fn request_state_cert(
671 &self,
672 epoch: u64,
673 timeout: Duration,
674 ) -> Result<LightClientStateUpdateCertificateV2<SeqTypes>, StateCertFetchError> {
675 tracing::info!("fetching state certificate for epoch={epoch}");
676 let handle = self.consensus_handle().await;
677
678 let current_epoch = handle.current_epoch().await;
679
680 let highest_epoch = current_epoch.map(|e| e.u64() + 1);
683
684 if Some(epoch) > highest_epoch {
685 return Err(StateCertFetchError::Other(anyhow::anyhow!(
686 "requested state certificate for epoch {epoch} is beyond the highest possible \
687 epoch {highest_epoch:?}"
688 )));
689 }
690
691 let coordinator = handle.membership_coordinator().await;
693 if let Err(err) = coordinator.stake_table_for_epoch(Some(EpochNumber::new(epoch))) {
694 tracing::warn!(
695 "Failed to get membership for epoch {epoch}: {err:#}. Waiting for catchup"
696 );
697
698 coordinator
699 .wait_for_catchup(EpochNumber::new(epoch))
700 .await
701 .map_err(|e| {
702 StateCertFetchError::Other(
703 anyhow::Error::new(e)
704 .context(format!("failed to catch up for stake table epoch={epoch}")),
705 )
706 })?;
707 }
708
709 let membership = coordinator
710 .stake_table_for_epoch(Some(EpochNumber::new(epoch)))
711 .map_err(|e| {
712 StateCertFetchError::Other(
713 anyhow::Error::new(e)
714 .context(format!("failed to get stake table for epoch={epoch}")),
715 )
716 })?;
717
718 let stake_table = HSStakeTable::from_iter(membership.stake_table());
719
720 let state_catchup = self
721 .sequencer_context
722 .as_ref()
723 .get()
724 .await
725 .node_state()
726 .state_catchup
727 .clone();
728
729 let result = tokio::time::timeout(timeout, state_catchup.fetch_state_cert(epoch)).await;
730
731 match result {
732 Err(_) => Err(StateCertFetchError::FetchError(anyhow::anyhow!(
733 "timeout while fetching state cert for epoch {epoch}"
734 ))),
735 Ok(Ok(cert)) => {
736 validate_state_cert(
738 &cert,
739 &stake_table,
740 EpochNumber::new(epoch),
741 *coordinator.epoch_height(),
742 &handle.upgrade_lock().await,
743 )
744 .map_err(|e| {
745 StateCertFetchError::ValidationError(e.context(format!(
746 "state certificate validation failed for epoch={epoch}"
747 )))
748 })?;
749
750 tracing::info!("fetched and validated state certificate for epoch {epoch}");
751 Ok(cert)
752 },
753 Ok(Err(e)) => Err(StateCertFetchError::FetchError(
754 e.context(format!("failed to fetch state cert for epoch {epoch}")),
755 )),
756 }
757 }
758}
759
760#[async_trait]
762impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence> StateCertDataSource
763 for StorageState<N, P, D>
764{
765 async fn get_state_cert_by_epoch(
766 &self,
767 epoch: u64,
768 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
769 self.as_ref().get_state_cert_by_epoch(epoch).await
770 }
771
772 async fn insert_state_cert(
773 &self,
774 epoch: u64,
775 cert: LightClientStateUpdateCertificateV2<SeqTypes>,
776 ) -> anyhow::Result<()> {
777 self.as_ref().insert_state_cert(epoch, cert).await
778 }
779}
780
781#[async_trait]
782impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> StateCertDataSource for ApiState<N, P> {
783 async fn get_state_cert_by_epoch(
784 &self,
785 epoch: u64,
786 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
787 let storage = self.consensus_handle().await.storage().await;
788 storage.get_state_cert_by_epoch(epoch).await
789 }
790
791 async fn insert_state_cert(
792 &self,
793 epoch: u64,
794 cert: LightClientStateUpdateCertificateV2<SeqTypes>,
795 ) -> anyhow::Result<()> {
796 let storage = self.consensus_handle().await.storage().await;
797 storage.insert_state_cert(epoch, cert).await
798 }
799}
800
801impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> SubmitDataSource<N, P>
802 for ApiState<N, P>
803{
804 async fn submit(&self, tx: Transaction) -> anyhow::Result<()> {
805 let handle = self.consensus_handle().await;
806
807 let cf = handle
811 .decided_state()
812 .await
813 .and_then(|state| state.chain_config.resolve());
814
815 let cf = match cf {
819 Some(cf) => cf,
820 None => self.node_state().await.chain_config,
821 };
822
823 let max_block_size: u64 = cf.max_block_size.into();
824 let txn_size = tx.payload().len() as u64;
825
826 if txn_size > max_block_size {
828 bail!("transaction size ({txn_size}) is greater than max_block_size ({max_block_size})")
829 }
830
831 handle.submit_transaction(tx).await?;
832 Ok(())
833 }
834}
835
836impl<N, P, D> NodeStateDataSource for StorageState<N, P, D>
837where
838 N: ConnectedNetwork<PubKey>,
839 P: SequencerPersistence,
840 D: Sync,
841{
842 async fn node_state(&self) -> NodeState {
843 self.as_ref().node_state().await
844 }
845}
846
847impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence, D: CatchupStorage + Send + Sync>
848 data_source::DatabaseMetadataSource for StorageState<N, P, D>
849where
850 N: ConnectedNetwork<PubKey>,
851 P: SequencerPersistence,
852 D: data_source::DatabaseMetadataSource + Send + Sync,
853{
854 async fn get_table_sizes(&self) -> anyhow::Result<Vec<data_source::TableSize>> {
855 self.inner().get_table_sizes().await
856 }
857
858 async fn get_migration_status(&self) -> anyhow::Result<Vec<data_source::MigrationStatus>> {
859 self.inner().get_migration_status().await
860 }
861}
862
863impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence, D: CatchupStorage + Send + Sync>
864 data_source::PruningDataSource for StorageState<N, P, D>
865where
866 N: ConnectedNetwork<PubKey>,
867 P: SequencerPersistence,
868 D: data_source::PruningDataSource + Send + Sync,
869{
870 async fn get_oldest_block(
871 &self,
872 ) -> anyhow::Result<Option<hotshot_query_service::availability::BlockQueryData<crate::SeqTypes>>>
873 {
874 self.inner().get_oldest_block().await
875 }
876
877 async fn get_oldest_leaf(
878 &self,
879 ) -> anyhow::Result<Option<hotshot_query_service::availability::LeafQueryData<crate::SeqTypes>>>
880 {
881 self.inner().get_oldest_leaf().await
882 }
883}
884
885impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence, D: CatchupStorage + Send + Sync>
886 CatchupDataSource for StorageState<N, P, D>
887{
888 #[tracing::instrument(skip(self, instance))]
889 async fn get_accounts(
890 &self,
891 instance: &NodeState,
892 height: u64,
893 view: ViewNumber,
894 accounts: &[FeeAccount],
895 ) -> anyhow::Result<FeeMerkleTree> {
896 match self
898 .as_ref()
899 .get_accounts(instance, height, view, accounts)
900 .await
901 {
902 Ok(accounts) => return Ok(accounts),
903 Err(err) => {
904 tracing::info!("accounts not in memory, trying storage: {err:#}");
905 },
906 }
907
908 let (tree, leaf) = self
910 .inner()
911 .get_accounts(instance, height, view, accounts)
912 .await
913 .context("accounts not in memory, and could not fetch from storage")?;
914 let handle = self.as_ref().consensus_handle().await;
918 if let Err(err) = add_fee_accounts_to_state(&*handle, &view, accounts, &tree, leaf).await {
919 tracing::warn!(?view, "cannot update fetched account state: {err:#}");
920 }
921 tracing::info!(?view, "updated with fetched account state");
922
923 Ok(tree)
924 }
925
926 #[tracing::instrument(skip(self, instance))]
927 async fn get_frontier(
928 &self,
929 instance: &NodeState,
930 height: u64,
931 view: ViewNumber,
932 ) -> anyhow::Result<BlocksFrontier> {
933 match self.as_ref().get_frontier(instance, height, view).await {
935 Ok(frontier) => return Ok(frontier),
936 Err(err) => {
937 tracing::info!("frontier is not in memory, trying storage: {err:#}");
938 },
939 }
940
941 self.inner().get_frontier(instance, height, view).await
943 }
944
945 async fn get_chain_config(
946 &self,
947 commitment: Commitment<ChainConfig>,
948 ) -> anyhow::Result<ChainConfig> {
949 match self.as_ref().get_chain_config(commitment).await {
951 Ok(cf) => return Ok(cf),
952 Err(err) => {
953 tracing::info!("chain config is not in memory, trying storage: {err:#}");
954 },
955 }
956
957 self.inner().get_chain_config(commitment).await
959 }
960 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
961 match self.as_ref().get_leaf_chain(height).await {
963 Ok(cf) => return Ok(cf),
964 Err(err) => {
965 tracing::info!("leaf chain is not in memory, trying storage: {err:#}");
966 },
967 }
968
969 self.inner().get_leaf_chain(height).await
971 }
972
973 async fn get_cert2(
974 &self,
975 height: u64,
976 ) -> anyhow::Result<Option<espresso_types::Certificate2<SeqTypes>>> {
977 self.inner().load_cert2(height).await
978 }
979
980 #[tracing::instrument(skip(self, instance))]
981 async fn get_reward_accounts_v2(
982 &self,
983 instance: &NodeState,
984 height: u64,
985 view: ViewNumber,
986 accounts: &[RewardAccountV2],
987 ) -> anyhow::Result<RewardMerkleTreeV2> {
988 match self
990 .as_ref()
991 .get_reward_accounts_v2(instance, height, view, accounts)
992 .await
993 {
994 Ok(accounts) => return Ok(accounts),
995 Err(err) => {
996 tracing::info!("reward accounts not in memory, trying storage: {err:#}");
997 },
998 }
999
1000 let (tree, leaf) = self
1002 .inner()
1003 .get_reward_accounts_v2(instance, height, view, accounts)
1004 .await
1005 .context("accounts not in memory, and could not fetch from storage")?;
1006
1007 let handle = self.as_ref().consensus_handle().await;
1010 if let Err(err) =
1011 add_v2_reward_accounts_to_state(&*handle, &view, accounts, &tree, leaf).await
1012 {
1013 tracing::warn!(?view, "cannot update fetched account state: {err:#}");
1014 }
1015 tracing::info!(?view, "updated with fetched account state");
1016
1017 Ok(tree)
1018 }
1019
1020 #[tracing::instrument(skip(self, instance))]
1021 async fn get_reward_accounts_v1(
1022 &self,
1023 instance: &NodeState,
1024 height: u64,
1025 view: ViewNumber,
1026 accounts: &[RewardAccountV1],
1027 ) -> anyhow::Result<RewardMerkleTreeV1> {
1028 match self
1030 .as_ref()
1031 .get_reward_accounts_v1(instance, height, view, accounts)
1032 .await
1033 {
1034 Ok(accounts) => return Ok(accounts),
1035 Err(err) => {
1036 tracing::info!("reward accounts not in memory, trying storage: {err:#}");
1037 },
1038 }
1039
1040 let (tree, leaf) = self
1042 .inner()
1043 .get_reward_accounts_v1(instance, height, view, accounts)
1044 .await
1045 .context("accounts not in memory, and could not fetch from storage")?;
1046
1047 let handle = self.as_ref().consensus_handle().await;
1050 if let Err(err) =
1051 add_v1_reward_accounts_to_state(&*handle, &view, accounts, &tree, leaf).await
1052 {
1053 tracing::warn!(?view, "cannot update fetched account state: {err:#}");
1054 }
1055 tracing::info!(?view, "updated with fetched account state");
1056
1057 Ok(tree)
1058 }
1059
1060 async fn get_reward_merkle_tree_v2(
1061 &self,
1062 height: u64,
1063 view: ViewNumber,
1064 ) -> anyhow::Result<Vec<u8>> {
1065 self.as_ref().get_reward_merkle_tree_v2(height, view).await
1066 }
1067
1068 #[tracing::instrument(skip(self))]
1069 async fn get_state_cert(
1070 &self,
1071 epoch: u64,
1072 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1073 let storage = self.as_ref().consensus_handle().await.storage().await;
1074 storage
1075 .get_state_cert_by_epoch(epoch)
1076 .await?
1077 .context(format!("state cert for epoch {epoch} not found"))
1078 }
1079}
1080
1081impl<N, P> NodeStateDataSource for ApiState<N, P>
1082where
1083 N: ConnectedNetwork<PubKey>,
1084 P: SequencerPersistence,
1085{
1086 async fn node_state(&self) -> NodeState {
1087 self.sequencer_context.as_ref().get().await.node_state()
1088 }
1089}
1090
1091impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> CatchupDataSource for ApiState<N, P> {
1092 #[tracing::instrument(skip(self, _instance))]
1093 async fn get_accounts(
1094 &self,
1095 _instance: &NodeState,
1096 height: u64,
1097 view: ViewNumber,
1098 accounts: &[FeeAccount],
1099 ) -> anyhow::Result<FeeMerkleTree> {
1100 let state = self
1101 .consensus_handle()
1102 .await
1103 .state(view)
1104 .await
1105 .context(format!(
1106 "state not available for height {height}, view {view}"
1107 ))?;
1108 retain_accounts(&state.fee_merkle_tree, accounts.iter().copied())
1109 }
1110
1111 #[tracing::instrument(skip(self, _instance))]
1112 async fn get_frontier(
1113 &self,
1114 _instance: &NodeState,
1115 height: u64,
1116 view: ViewNumber,
1117 ) -> anyhow::Result<BlocksFrontier> {
1118 let state = self
1119 .consensus_handle()
1120 .await
1121 .state(view)
1122 .await
1123 .context(format!(
1124 "state not available for height {height}, view {view}"
1125 ))?;
1126 let tree = &state.block_merkle_tree;
1127 let frontier = tree.lookup(tree.num_leaves() - 1).expect_ok()?.1;
1128 Ok(frontier)
1129 }
1130
1131 async fn get_chain_config(
1132 &self,
1133 commitment: Commitment<ChainConfig>,
1134 ) -> anyhow::Result<ChainConfig> {
1135 let state = self
1136 .consensus_handle()
1137 .await
1138 .decided_state()
1139 .await
1140 .context("decided state not available")?;
1141 let chain_config = state.chain_config;
1142
1143 if chain_config.commit() == commitment {
1144 chain_config.resolve().context("chain config found")
1145 } else {
1146 bail!("chain config not found")
1147 }
1148 }
1149
1150 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
1151 let mut leaves = self.consensus_handle().await.undecided_leaves().await;
1154 leaves.sort_by_key(|l| l.view_number());
1155 let (position, mut last_leaf) = leaves
1156 .iter()
1157 .find_position(|l| l.height() == height)
1158 .context(format!("leaf chain not available for {height}"))?;
1159 let mut chain = vec![last_leaf.clone()];
1160 for leaf in leaves.iter().skip(position + 1) {
1161 if leaf.justify_qc().view_number() == last_leaf.view_number() {
1162 chain.push(leaf.clone());
1163 } else {
1164 continue;
1165 }
1166 if leaf.view_number() == last_leaf.view_number() + 1 {
1167 last_leaf = leaf;
1169 break;
1170 }
1171 last_leaf = leaf;
1172 }
1173 for leaf in leaves
1175 .iter()
1176 .skip_while(|l| l.view_number() <= last_leaf.view_number())
1177 {
1178 if leaf.justify_qc().view_number() == last_leaf.view_number() {
1179 chain.push(leaf.clone());
1180 return Ok(chain);
1181 }
1182 }
1183 bail!(format!("leaf chain not available for {height}"))
1184 }
1185
1186 #[tracing::instrument(skip(self, _instance))]
1187 async fn get_reward_accounts_v2(
1188 &self,
1189 _instance: &NodeState,
1190 height: u64,
1191 view: ViewNumber,
1192 accounts: &[RewardAccountV2],
1193 ) -> anyhow::Result<RewardMerkleTreeV2> {
1194 let state = self
1195 .consensus_handle()
1196 .await
1197 .state(view)
1198 .await
1199 .context(format!(
1200 "state not available for height {height}, view {view}"
1201 ))?;
1202
1203 retain_v2_reward_accounts(&state.reward_merkle_tree_v2, accounts.iter().copied())
1204 }
1205
1206 #[tracing::instrument(skip(self, _instance))]
1207 async fn get_reward_accounts_v1(
1208 &self,
1209 _instance: &NodeState,
1210 height: u64,
1211 view: ViewNumber,
1212 accounts: &[RewardAccountV1],
1213 ) -> anyhow::Result<RewardMerkleTreeV1> {
1214 let state = self
1215 .consensus_handle()
1216 .await
1217 .state(view)
1218 .await
1219 .context(format!(
1220 "state not available for height {height}, view {view}"
1221 ))?;
1222
1223 retain_v1_reward_accounts(&state.reward_merkle_tree_v1, accounts.iter().copied())
1224 }
1225
1226 async fn get_reward_merkle_tree_v2(
1227 &self,
1228 height: u64,
1229 view: ViewNumber,
1230 ) -> anyhow::Result<Vec<u8>> {
1231 let state = self
1232 .consensus_handle()
1233 .await
1234 .state(view)
1235 .await
1236 .context(format!(
1237 "state not available for height {height}, view {view}"
1238 ))?;
1239
1240 let tree_data = TryInto::<RewardMerkleTreeV2Data>::try_into(&state.reward_merkle_tree_v2)
1241 .inspect_err(
1242 |err| tracing::debug!(%err, height, %view, "cannot serve reward merkle tree"),
1243 )?;
1244 let merkle_tree_bytes = bincode::serialize(&tree_data)
1245 .context("Merkle tree serialization failed; this should never happen.")?;
1246
1247 Ok(merkle_tree_bytes)
1248 }
1249
1250 async fn get_state_cert(
1251 &self,
1252 epoch: u64,
1253 ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
1254 self.get_state_cert_by_epoch(epoch)
1255 .await?
1256 .context(format!("state cert not found for epoch {epoch}"))
1257 }
1258}
1259
1260impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence> HotShotConfigDataSource
1261 for StorageState<N, P, D>
1262{
1263 async fn get_config(&self) -> PublicNetworkConfig {
1264 self.as_ref().network_config().await.into()
1265 }
1266}
1267
1268impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> HotShotConfigDataSource
1269 for ApiState<N, P>
1270{
1271 async fn get_config(&self) -> PublicNetworkConfig {
1272 self.network_config().await.into()
1273 }
1274}
1275
1276impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence> NodeKeysDataSource
1277 for StorageState<N, P, D>
1278{
1279 async fn node_public_keys(&self) -> NodePublicKeys {
1280 self.as_ref().node_public_keys().await
1281 }
1282}
1283
1284impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> NodeKeysDataSource for ApiState<N, P> {
1285 async fn node_public_keys(&self) -> NodePublicKeys {
1286 let ctx = self.sequencer_context.as_ref().get().await.get_ref();
1287 let config = ctx.validator_config();
1288 let consensus_key = config.public_key;
1289 let eth_account = ctx
1290 .consensus_handle()
1291 .membership_coordinator()
1292 .await
1293 .membership()
1294 .latest_account(&consensus_key);
1295 NodePublicKeys {
1296 eth_account,
1297 consensus_key,
1298 state_ver_key: config.state_public_key.clone(),
1299 x25519_key: config.x25519_keypair.as_ref().map(|kp| kp.public_key()),
1300 }
1301 }
1302}
1303
1304#[async_trait]
1305impl<N: ConnectedNetwork<PubKey>, D: Sync, P: SequencerPersistence> StateSignatureDataSource<N>
1306 for StorageState<N, P, D>
1307{
1308 async fn get_state_signature(&self, height: u64) -> Option<LCV3StateSignatureRequestBody> {
1309 self.as_ref().get_state_signature(height).await
1310 }
1311}
1312
1313#[async_trait]
1314impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> StateSignatureDataSource<N>
1315 for ApiState<N, P>
1316{
1317 async fn get_state_signature(&self, height: u64) -> Option<LCV3StateSignatureRequestBody> {
1318 self.state_signer()
1319 .await
1320 .read()
1321 .await
1322 .get_state_signature(height)
1323 .await
1324 }
1325}
1326
1327#[derive(Serialize, Deserialize, Debug, Clone)]
1328pub struct RewardMerkleTreeV2Data {
1330 pub balances: Vec<(RewardAccountV2, RewardAmount)>,
1331}
1332
1333impl TryInto<RewardMerkleTreeV2Data> for &RewardMerkleTreeV2 {
1334 type Error = anyhow::Error;
1335 fn try_into(self) -> anyhow::Result<RewardMerkleTreeV2Data> {
1337 let num_leaves = self.num_leaves();
1338
1339 let balances: Vec<_> = self
1340 .iter()
1341 .map(|(account, balance)| (*account, *balance))
1342 .collect();
1343
1344 if balances.len() as u64 == num_leaves {
1345 Ok(RewardMerkleTreeV2Data { balances })
1346 } else {
1347 bail!(
1348 "RewardMerkleTreeV2 is incomplete, some accounts are missing. Balances length: \
1349 {}, num_leaves: {num_leaves}.",
1350 balances.len(),
1351 );
1352 }
1353 }
1354}
1355
1356pub(crate) trait RewardMerkleTreeDataSource: Send + Sync + Clone + 'static {
1357 fn load_v1_reward_account_proof(
1358 &self,
1359 _height: u64,
1360 _account: RewardAccountV1,
1361 ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV1>>;
1362
1363 fn save_and_gc_reward_tree_v2(
1364 &self,
1365 node_state: &NodeState,
1366 height: u64,
1367 version: Version,
1368 merkle_tree: &RewardMerkleTreeV2,
1369 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1370 async move {
1371 let tree_data = TryInto::<RewardMerkleTreeV2Data>::try_into(merkle_tree).inspect_err(
1374 |err| tracing::error!(%err, height, "cannot persist incomplete RewardMerkleTreeV2"),
1375 )?;
1376 let serialization =
1377 bincode::serialize(&tree_data).context("Merkle tree serialization failed")?;
1378 self.persist_tree(height, serialization).await?;
1379
1380 if cfg!(any(test, feature = "testing")) {
1382 return Ok(());
1383 }
1384
1385 let finalized_hotshot_height = match node_state.finalized_hotshot_height().await {
1386 Ok(h) => h,
1387 Err(err) => {
1388 tracing::warn!("failed to get finalized hotshot height: {err:#}");
1389 return Ok(());
1390 },
1391 };
1392
1393 let epoch_height = node_state
1408 .epoch_height
1409 .context("reward tree gc requires an epoch height")?;
1410 let epochs_to_retain = if version >= versions::EPOCH_REWARD_VERSION {
1416 5
1417 } else {
1418 1
1419 };
1420 let current_epoch = epoch_from_block_number(height, epoch_height);
1421 let epoch_start_block = current_epoch.saturating_sub(epochs_to_retain) * epoch_height;
1423
1424 let gc_height = epoch_start_block.min(finalized_hotshot_height);
1425
1426 if let Err(err) = self.garbage_collect(gc_height).await {
1427 tracing::info!(gc_height, "failed to garbage collect: {err:#}");
1428 }
1429
1430 Ok(())
1431 }
1432 }
1433
1434 fn persist_reward_proofs(
1435 &self,
1436 node_state: &NodeState,
1437 height: u64,
1438 version: Version,
1439 ) -> impl Send + Future<Output = anyhow::Result<()>>;
1440
1441 fn load_reward_merkle_tree_v2(
1442 &self,
1443 height: u64,
1444 ) -> impl Send + Future<Output = anyhow::Result<PermittedRewardMerkleTreeV2>> {
1445 async move {
1446 let tree_bytes = self.load_tree(height).await?;
1447
1448 let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&tree_bytes).context(
1449 "Failed to deserialize RewardMerkleTreeV2 for height {height} from storage; this \
1450 should never happen.",
1451 )?;
1452
1453 PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances)
1454 .await
1455 .context("Failed to reconstruct reward merkle tree from storage")
1456 }
1457 }
1458
1459 fn load_latest_reward_merkle_tree_v2(
1468 &self,
1469 height: u64,
1470 ) -> impl Send + Future<Output = anyhow::Result<PermittedRewardMerkleTreeV2>> {
1471 async move {
1472 let tree_bytes = self.load_latest_tree(height).await?;
1473
1474 let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&tree_bytes)
1475 .context("Failed to deserialize RewardMerkleTreeV2 from storage")?;
1476
1477 PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances)
1478 .await
1479 .context("Failed to reconstruct reward merkle tree from storage")
1480 }
1481 }
1482
1483 fn load_reward_account_proof_v2(
1484 &self,
1485 _height: u64,
1486 _account: RewardAccountV2,
1487 ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV2>> {
1488 async {
1489 bail!("load_reward_account_proof_v2 is not supported for this data source");
1490 }
1491 }
1492
1493 fn load_latest_reward_account_proof_v2(
1494 &self,
1495 account: RewardAccountV2,
1496 ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV2>> {
1497 async move {
1498 let serialized_account = bincode::serialize(&account).context(
1499 "Failed to serialize RewardAccountV2 for lookup; this should never happen.",
1500 )?;
1501 let proof_bytes = self.load_latest_proof(serialized_account).await?;
1502
1503 bincode::deserialize::<RewardAccountQueryDataV2>(&proof_bytes).context(
1504 "Failed to deserialize RewardAccountQueryDataV2 for account {account} from \
1505 storage; this should never happen.",
1506 )
1507 }
1508 }
1509
1510 fn persist_tree(
1511 &self,
1512 height: u64,
1513 merkle_tree: Vec<u8>,
1514 ) -> impl Send + Future<Output = anyhow::Result<()>>;
1515
1516 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
1517
1518 fn load_latest_tree(&self, height: u64)
1520 -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
1521
1522 fn persist_proofs(
1523 &self,
1524 height: u64,
1525 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
1526 ) -> impl Send + Future<Output = anyhow::Result<()>>;
1527
1528 fn load_proof(
1529 &self,
1530 height: u64,
1531 account: Vec<u8>,
1532 epoch_height: u64,
1533 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
1534
1535 fn load_latest_proof(
1536 &self,
1537 account: Vec<u8>,
1538 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
1539
1540 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool>;
1541
1542 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>>;
1544}
1545
1546impl RewardMerkleTreeDataSource for hotshot_query_service::data_source::MetricsDataSource {
1547 fn load_v1_reward_account_proof(
1548 &self,
1549 _height: u64,
1550 _account: RewardAccountV1,
1551 ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV1>> {
1552 async {
1553 bail!("reward merklized state is not supported for this data source");
1554 }
1555 }
1556
1557 fn persist_reward_proofs(
1558 &self,
1559 _node_state: &NodeState,
1560 _height: u64,
1561 _version: Version,
1562 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1563 async {
1564 bail!("reward merklized state is not supported for this data source");
1565 }
1566 }
1567
1568 fn persist_tree(
1569 &self,
1570 _height: u64,
1571 _merkle_tree: Vec<u8>,
1572 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1573 async move {
1574 bail!("reward merklized state is not supported for this data source");
1575 }
1576 }
1577
1578 fn load_tree(&self, _height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1579 async move {
1580 bail!("reward merklized state is not supported for this data source");
1581 }
1582 }
1583
1584 fn load_latest_tree(
1585 &self,
1586 _height: u64,
1587 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1588 async move {
1589 bail!("reward merklized state is not supported for this data source");
1590 }
1591 }
1592
1593 fn garbage_collect(&self, _height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
1594 async move {
1595 bail!("reward merklized state is not supported for this data source");
1596 }
1597 }
1598
1599 fn persist_proofs(
1600 &self,
1601 _height: u64,
1602 _proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
1603 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1604 async move {
1605 bail!("reward merklized state is not supported for this data source");
1606 }
1607 }
1608
1609 fn load_proof(
1610 &self,
1611 _height: u64,
1612 _account: Vec<u8>,
1613 _epoch_height: u64,
1614 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1615 async move {
1616 bail!("reward merklized state is not supported for this data source");
1617 }
1618 }
1619
1620 fn load_latest_proof(
1621 &self,
1622 _account: Vec<u8>,
1623 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1624 async move {
1625 bail!("reward merklized state is not supported for this data source");
1626 }
1627 }
1628
1629 fn proof_exists(&self, _height: u64) -> impl Send + Future<Output = bool> {
1630 async move { false }
1631 }
1632}
1633
1634impl<T, S> RewardMerkleTreeDataSource
1635 for hotshot_query_service::data_source::ExtensibleDataSource<T, S>
1636where
1637 T: RewardMerkleTreeDataSource,
1638 S: Send + Sync + Clone + NodeStateDataSource + 'static,
1639{
1640 async fn load_v1_reward_account_proof(
1641 &self,
1642 height: u64,
1643 account: RewardAccountV1,
1644 ) -> anyhow::Result<RewardAccountQueryDataV1> {
1645 self.inner()
1646 .load_v1_reward_account_proof(height, account)
1647 .await
1648 }
1649
1650 async fn load_reward_account_proof_v2(
1651 &self,
1652 height: u64,
1653 account: RewardAccountV2,
1654 ) -> anyhow::Result<RewardAccountQueryDataV2> {
1655 let epoch_height = self
1656 .as_ref()
1657 .node_state()
1658 .await
1659 .epoch_height
1660 .context("epoch height not found")?;
1661 let serialized_account = bincode::serialize(&account)
1662 .context("Failed to serialize RewardAccountV2 for lookup; this should never happen.")?;
1663 let proof_bytes = self
1664 .inner()
1665 .load_proof(height, serialized_account, epoch_height)
1666 .await?;
1667
1668 bincode::deserialize::<RewardAccountQueryDataV2>(&proof_bytes)
1669 .context("Failed to deserialize RewardAccountQueryDataV2 from storage")
1670 }
1671
1672 fn persist_tree(
1673 &self,
1674 height: u64,
1675 merkle_tree: Vec<u8>,
1676 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1677 async move { self.inner().persist_tree(height, merkle_tree).await }
1678 }
1679
1680 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1681 async move { self.inner().load_tree(height).await }
1682 }
1683
1684 fn load_latest_tree(
1685 &self,
1686 height: u64,
1687 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1688 async move { self.inner().load_latest_tree(height).await }
1689 }
1690
1691 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
1692 async move { self.inner().garbage_collect(height).await }
1693 }
1694
1695 fn persist_proofs(
1696 &self,
1697 height: u64,
1698 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
1699 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1700 async move { self.inner().persist_proofs(height, proofs).await }
1701 }
1702
1703 fn load_proof(
1704 &self,
1705 height: u64,
1706 account: Vec<u8>,
1707 epoch_height: u64,
1708 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1709 async move { self.inner().load_proof(height, account, epoch_height).await }
1710 }
1711
1712 fn load_latest_proof(
1713 &self,
1714 account: Vec<u8>,
1715 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1716 async move { self.inner().load_latest_proof(account).await }
1717 }
1718
1719 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
1720 async move { self.inner().proof_exists(height).await }
1721 }
1722
1723 fn persist_reward_proofs(
1724 &self,
1725 node_state: &NodeState,
1726 height: u64,
1727 version: Version,
1728 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1729 async move {
1730 self.inner()
1731 .persist_reward_proofs(node_state, height, version)
1732 .await
1733 }
1734 }
1735}
1736
1737impl<D> RewardMerkleTreeDataSource for Arc<D>
1739where
1740 D: RewardMerkleTreeDataSource,
1741{
1742 async fn load_v1_reward_account_proof(
1743 &self,
1744 height: u64,
1745 account: RewardAccountV1,
1746 ) -> anyhow::Result<RewardAccountQueryDataV1> {
1747 (**self).load_v1_reward_account_proof(height, account).await
1748 }
1749
1750 fn persist_tree(
1751 &self,
1752 height: u64,
1753 merkle_tree: Vec<u8>,
1754 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1755 async move { (**self).persist_tree(height, merkle_tree).await }
1756 }
1757
1758 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1759 async move { (**self).load_tree(height).await }
1760 }
1761
1762 fn load_latest_tree(
1763 &self,
1764 height: u64,
1765 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1766 async move { (**self).load_latest_tree(height).await }
1767 }
1768
1769 fn load_reward_merkle_tree_v2(
1770 &self,
1771 height: u64,
1772 ) -> impl Send + Future<Output = anyhow::Result<PermittedRewardMerkleTreeV2>> {
1773 async move { (**self).load_reward_merkle_tree_v2(height).await }
1774 }
1775
1776 fn load_reward_account_proof_v2(
1777 &self,
1778 height: u64,
1779 account: RewardAccountV2,
1780 ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV2>> {
1781 async move { (**self).load_reward_account_proof_v2(height, account).await }
1782 }
1783
1784 fn persist_proofs(
1785 &self,
1786 height: u64,
1787 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
1788 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1789 async move { (**self).persist_proofs(height, proofs).await }
1790 }
1791
1792 fn persist_reward_proofs(
1793 &self,
1794 node_state: &NodeState,
1795 height: u64,
1796 version: Version,
1797 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1798 async move {
1799 (**self)
1800 .persist_reward_proofs(node_state, height, version)
1801 .await
1802 }
1803 }
1804
1805 fn load_proof(
1806 &self,
1807 height: u64,
1808 account: Vec<u8>,
1809 epoch_height: u64,
1810 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1811 async move { (**self).load_proof(height, account, epoch_height).await }
1812 }
1813
1814 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
1815 async move { (**self).proof_exists(height).await }
1816 }
1817
1818 fn load_latest_proof(
1819 &self,
1820 account: Vec<u8>,
1821 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1822 async move { (**self).load_latest_proof(account).await }
1823 }
1824
1825 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
1826 async move { (**self).garbage_collect(height).await }
1827 }
1828}
1829
1830#[derive(Debug)]
1839struct LightClientProvider {
1840 light_client: BoxLazy<LightClient<SqliteStorage, FallbackClient<QueryServiceClient>>>,
1841}
1842
1843impl LightClientProvider {
1844 pub async fn new<N, P>(
1845 peers: impl IntoIterator<Item = Url>,
1846 state: ApiState<N, P>,
1847 opt: LightClientOptions,
1848 db_opt: LightClientSqliteOptions,
1849 ) -> anyhow::Result<Self>
1850 where
1851 N: ConnectedNetwork<PubKey>,
1852 P: SequencerPersistence,
1853 {
1854 let db = db_opt
1855 .connect()
1856 .await
1857 .context("creating SQLite database for light client")?;
1858 let client = FallbackClient::new(peers.into_iter().map(QueryServiceClient::new).collect())?;
1859 let init_light_client = async move {
1860 let config = state.network_config().await;
1861 let chain_id = state.node_state().await.genesis_chain_config.chain_id;
1862 let epoch_height = config.config.epoch_height;
1863 let first_epoch =
1864 epoch_from_block_number(config.config.epoch_start_block, epoch_height);
1865
1866 let genesis = Genesis {
1867 epoch_height,
1868
1869 first_epoch_with_dynamic_stake_table: EpochNumber::new(first_epoch + 2),
1872
1873 stake_table: config
1874 .config
1875 .known_nodes_with_stake
1876 .into_iter()
1877 .map(|peer| peer.stake_table_entry)
1878 .collect(),
1879
1880 chain_id,
1881 };
1882 LightClient::from_genesis_with_options(db, client, genesis, opt)
1883 };
1884 Ok(Self {
1885 light_client: Arc::pin(Lazy::from_future(init_light_client.boxed())),
1886 })
1887 }
1888}
1889
1890#[async_trait]
1891impl<T> Provider<SeqTypes, T> for LightClientProvider
1892where
1893 T: fetching::Request<SeqTypes> + 'static,
1894 LightClient<SqliteStorage, FallbackClient<QueryServiceClient>>: Provider<SeqTypes, T>,
1895{
1896 async fn fetch(&self, req: T) -> Option<T::Response> {
1897 self.light_client.as_ref().get().await.fetch(req).await
1898 }
1899}
1900
1901#[cfg(any(test, feature = "testing"))]
1902pub mod test_helpers {
1903 use std::{
1904 cmp::max,
1905 collections::HashSet,
1906 sync::atomic::{AtomicU32, Ordering},
1907 time::Duration,
1908 };
1909
1910 use alloy::{
1911 network::EthereumWallet,
1912 primitives::{Address, U256, utils::parse_ether},
1913 providers::{Provider, ProviderBuilder, ext::AnvilApi},
1914 signers::local::PrivateKeySigner,
1915 };
1916 use committable::{Commitment, Committable};
1917 use espresso_contract_deployer::{
1918 Contract, Contracts, DEFAULT_EXIT_ESCROW_PERIOD_SECONDS, builder::DeployerArgsBuilder,
1919 network_config::light_client_genesis_from_stake_table,
1920 };
1921 use espresso_types::{
1922 MOCK_SEQUENCER_VERSIONS, NamespaceId, ValidatedState,
1923 v0::traits::{NullEventConsumer, PersistenceOptions, SequencerPersistence, StateCatchup},
1924 };
1925 use futures::{
1926 future::{FutureExt, join_all},
1927 stream::{Stream, StreamExt},
1928 };
1929 use hotshot::types::{Event, EventType};
1930 use hotshot_contract_adapter::stake_table::StakeTableContractVersion;
1931 use hotshot_types::{
1932 event::LeafInfo, light_client::LCV3StateSignatureRequestBody,
1933 new_protocol::CoordinatorEvent, traits::metrics::NoMetrics,
1934 };
1935 use http_client::{Client, error::ClientErr};
1936 use itertools::izip;
1937 use jf_merkle_tree_compat::{MerkleCommitment, MerkleTreeScheme};
1938 use staking_cli::{
1939 Transaction as StakingTransaction,
1940 demo::{DelegationConfig, StakingTransactions},
1941 };
1942 use tempfile::TempDir;
1943 use test_utils::reserve_tcp_port;
1944 use tokio::time::sleep;
1945 use vbs::version::StaticVersion;
1946 use versions::{EPOCH_VERSION, Upgrade};
1947
1948 use super::*;
1949 use crate::{
1950 catchup::NullStateCatchup,
1951 network,
1952 persistence::no_storage,
1953 testing::{
1954 TestConfig, TestConfigBuilder, deploy_stake_table, run_legacy_builder,
1955 wait_for_decide_on_handle, wait_for_epochs,
1956 },
1957 };
1958
1959 pub const STAKE_TABLE_CAPACITY_FOR_TEST: usize = 10;
1960
1961 pub struct TestNetwork<P: PersistenceOptions, const NUM_NODES: usize> {
1962 pub server: SequencerContext<network::Memory, P::Persistence>,
1963 pub peers: Vec<SequencerContext<network::Memory, P::Persistence>>,
1964 pub cfg: TestConfig<{ NUM_NODES }>,
1965 pub temp_dir: Option<TempDir>,
1967 pub contracts: Option<Contracts>,
1968 deferred: Vec<usize>,
1970 }
1971
1972 pub struct TestNetworkConfig<const NUM_NODES: usize, P, C>
1973 where
1974 P: PersistenceOptions,
1975 C: StateCatchup + 'static,
1976 {
1977 state: [ValidatedState; NUM_NODES],
1978 persistence: [P; NUM_NODES],
1979 catchup: [C; NUM_NODES],
1980 network_config: TestConfig<{ NUM_NODES }>,
1981 api_config: Options,
1982 contracts: Option<Contracts>,
1983 deferred_start: Vec<usize>,
1984 }
1985
1986 impl<const NUM_NODES: usize, P, C> TestNetworkConfig<{ NUM_NODES }, P, C>
1987 where
1988 P: PersistenceOptions,
1989 C: StateCatchup + 'static,
1990 {
1991 pub fn states(&self) -> [ValidatedState; NUM_NODES] {
1992 self.state.clone()
1993 }
1994 }
1995
1996 #[derive(Clone)]
1997 pub struct TestNetworkConfigBuilder<const NUM_NODES: usize, P, C>
1998 where
1999 P: PersistenceOptions,
2000 C: StateCatchup + 'static,
2001 {
2002 state: [ValidatedState; NUM_NODES],
2003 persistence: Option<[P; NUM_NODES]>,
2004 catchup: Option<[C; NUM_NODES]>,
2005 api_config: Option<Options>,
2006 network_config: Option<TestConfig<{ NUM_NODES }>>,
2007 contracts: Option<Contracts>,
2008 initial_token_supply: Option<U256>,
2009 deferred_start: Vec<usize>,
2010 }
2011
2012 impl Default for TestNetworkConfigBuilder<5, no_storage::Options, NullStateCatchup> {
2013 fn default() -> Self {
2014 TestNetworkConfigBuilder {
2015 state: std::array::from_fn(|_| ValidatedState::default()),
2016 persistence: Some([no_storage::Options; 5]),
2017 catchup: Some(std::array::from_fn(|_| NullStateCatchup::default())),
2018 network_config: None,
2019 api_config: None,
2020 contracts: None,
2021 initial_token_supply: None,
2022 deferred_start: Vec::new(),
2023 }
2024 }
2025 }
2026
2027 impl<const NUM_NODES: usize>
2028 TestNetworkConfigBuilder<{ NUM_NODES }, no_storage::Options, NullStateCatchup>
2029 {
2030 pub fn with_num_nodes()
2031 -> TestNetworkConfigBuilder<{ NUM_NODES }, no_storage::Options, NullStateCatchup> {
2032 TestNetworkConfigBuilder {
2033 state: std::array::from_fn(|_| ValidatedState::default()),
2034 persistence: Some([no_storage::Options; { NUM_NODES }]),
2035 catchup: Some(std::array::from_fn(|_| NullStateCatchup::default())),
2036 network_config: None,
2037 api_config: None,
2038 contracts: None,
2039 initial_token_supply: None,
2040 deferred_start: Vec::new(),
2041 }
2042 }
2043 }
2044
2045 impl<const NUM_NODES: usize, P, C> TestNetworkConfigBuilder<{ NUM_NODES }, P, C>
2046 where
2047 P: PersistenceOptions,
2048 C: StateCatchup + 'static,
2049 {
2050 pub fn states(mut self, state: [ValidatedState; NUM_NODES]) -> Self {
2051 self.state = state;
2052 self
2053 }
2054
2055 pub fn initial_token_supply(mut self, supply: U256) -> Self {
2056 self.initial_token_supply = Some(supply);
2057 self
2058 }
2059
2060 pub fn persistences<NP: PersistenceOptions>(
2061 self,
2062 persistence: [NP; NUM_NODES],
2063 ) -> TestNetworkConfigBuilder<{ NUM_NODES }, NP, C> {
2064 TestNetworkConfigBuilder {
2065 state: self.state,
2066 catchup: self.catchup,
2067 network_config: self.network_config,
2068 api_config: self.api_config,
2069 persistence: Some(persistence),
2070 contracts: self.contracts,
2071 initial_token_supply: self.initial_token_supply,
2072 deferred_start: self.deferred_start,
2073 }
2074 }
2075
2076 pub fn api_config(mut self, api_config: Options) -> Self {
2077 self.api_config = Some(api_config);
2078 self
2079 }
2080
2081 pub fn catchups<NC: StateCatchup + 'static>(
2082 self,
2083 catchup: [NC; NUM_NODES],
2084 ) -> TestNetworkConfigBuilder<{ NUM_NODES }, P, NC> {
2085 TestNetworkConfigBuilder {
2086 state: self.state,
2087 catchup: Some(catchup),
2088 network_config: self.network_config,
2089 api_config: self.api_config,
2090 persistence: self.persistence,
2091 contracts: self.contracts,
2092 initial_token_supply: self.initial_token_supply,
2093 deferred_start: self.deferred_start,
2094 }
2095 }
2096
2097 pub fn deferred_start(mut self, indices: &[usize]) -> Self {
2100 self.deferred_start = indices.to_vec();
2101 self
2102 }
2103
2104 pub fn network_config(mut self, network_config: TestConfig<{ NUM_NODES }>) -> Self {
2105 self.network_config = Some(network_config);
2106 self
2107 }
2108
2109 pub fn contracts(mut self, contracts: Contracts) -> Self {
2110 self.contracts = Some(contracts);
2111 self
2112 }
2113
2114 pub async fn pos_hook(
2117 self,
2118 delegation_config: DelegationConfig,
2119 stake_table_version: StakeTableContractVersion,
2120 upgrade: Upgrade,
2121 ) -> anyhow::Result<Self> {
2122 let registered: Vec<usize> = (0..NUM_NODES).collect();
2123 self.pos_hook_with_registered(
2124 delegation_config,
2125 stake_table_version,
2126 upgrade,
2127 ®istered,
2128 )
2129 .await
2130 }
2131
2132 pub async fn pos_hook_with_registered(
2137 self,
2138 delegation_config: DelegationConfig,
2139 stake_table_version: StakeTableContractVersion,
2140 upgrade: Upgrade,
2141 registered: &[usize],
2142 ) -> anyhow::Result<Self> {
2143 if upgrade.base < EPOCH_VERSION && upgrade.target < EPOCH_VERSION {
2144 panic!("given version does not require pos deployment");
2145 };
2146
2147 let network_config = self
2148 .network_config
2149 .as_ref()
2150 .expect("network_config is required");
2151
2152 let l1_url = network_config.l1_url();
2153 let signer = network_config.signer();
2154 let deployer = ProviderBuilder::new()
2155 .wallet(EthereumWallet::from(signer.clone()))
2156 .connect_http(l1_url.clone());
2157
2158 let blocks_per_epoch = network_config.hotshot_config().epoch_height;
2159 let epoch_start_block = network_config.hotshot_config().epoch_start_block;
2160 let (genesis_state, genesis_stake) = light_client_genesis_from_stake_table(
2161 &network_config.hotshot_config().hotshot_stake_table(),
2162 STAKE_TABLE_CAPACITY_FOR_TEST,
2163 )
2164 .unwrap();
2165
2166 let mut contracts = Contracts::new();
2167 let args = DeployerArgsBuilder::default()
2168 .deployer(deployer.clone())
2169 .rpc_url(l1_url.clone())
2170 .mock_light_client(true)
2171 .genesis_lc_state(genesis_state)
2172 .genesis_st_state(genesis_stake)
2173 .blocks_per_epoch(blocks_per_epoch)
2174 .epoch_start_block(epoch_start_block)
2175 .exit_escrow_period(U256::from(max(
2176 blocks_per_epoch * 15 + 100,
2177 DEFAULT_EXIT_ESCROW_PERIOD_SECONDS,
2178 )))
2179 .multisig_pauser(signer.address())
2180 .token_name("Espresso".to_string())
2181 .token_symbol("ESP".to_string())
2182 .initial_token_supply(self.initial_token_supply.unwrap_or(U256::from(100000u64)))
2183 .ops_timelock_delay(U256::from(0))
2184 .ops_timelock_admin(signer.address())
2185 .ops_timelock_proposers(vec![signer.address()])
2186 .ops_timelock_executors(vec![signer.address()])
2187 .safe_exit_timelock_delay(U256::from(10))
2188 .safe_exit_timelock_admin(signer.address())
2189 .safe_exit_timelock_proposers(vec![signer.address()])
2190 .safe_exit_timelock_executors(vec![signer.address()])
2191 .build()
2192 .unwrap();
2193
2194 deploy_stake_table(&args, stake_table_version, &mut contracts)
2195 .await
2196 .context("failed to deploy contracts")?;
2197
2198 let stake_table_address = contracts
2199 .address(Contract::StakeTableProxy)
2200 .expect("StakeTableProxy address not found");
2201
2202 StakingTransactions::create(
2203 l1_url.clone(),
2204 &deployer,
2205 stake_table_address,
2206 network_config.staking_key_sets(registered),
2207 None,
2208 delegation_config,
2209 )
2210 .await
2211 .expect("stake table setup failed")
2212 .apply_all()
2213 .await
2214 .expect("send all txns failed");
2215
2216 if let Some(anvil) = network_config.anvil() {
2221 anvil
2222 .anvil_set_interval_mining(1)
2223 .await
2224 .expect("interval mining");
2225 }
2226
2227 let state = self.state[0].clone();
2231 let chain_config = if let Some(cf) = state.chain_config.resolve() {
2232 ChainConfig {
2233 base_fee: 0.into(),
2234 stake_table_contract: Some(stake_table_address),
2235 ..cf
2236 }
2237 } else {
2238 ChainConfig {
2239 base_fee: 0.into(),
2240 stake_table_contract: Some(stake_table_address),
2241 ..Default::default()
2242 }
2243 };
2244
2245 let state = ValidatedState {
2246 chain_config: chain_config.into(),
2247 ..state
2248 };
2249 Ok(self
2250 .states(std::array::from_fn(|_| state.clone()))
2251 .contracts(contracts))
2252 }
2253
2254 pub fn build(self) -> TestNetworkConfig<{ NUM_NODES }, P, C> {
2255 TestNetworkConfig {
2256 state: self.state,
2257 persistence: self.persistence.unwrap(),
2258 catchup: self.catchup.unwrap(),
2259 network_config: self.network_config.unwrap(),
2260 api_config: self.api_config.unwrap(),
2261 contracts: self.contracts,
2262 deferred_start: self.deferred_start,
2263 }
2264 }
2265 }
2266
2267 impl<P: PersistenceOptions, const NUM_NODES: usize> TestNetwork<P, { NUM_NODES }> {
2268 pub async fn new<C: StateCatchup + 'static>(
2269 cfg: TestNetworkConfig<{ NUM_NODES }, P, C>,
2270 upgrade: versions::Upgrade,
2271 ) -> Self {
2272 let mut cfg = cfg;
2273 let mut builder_tasks = Vec::new();
2274
2275 let chain_config = cfg.state[0].chain_config.resolve();
2276 if chain_config.is_none() {
2277 tracing::warn!("Chain config is not set, using default max_block_size");
2278 }
2279 let (task, builder_url) = run_legacy_builder::<{ NUM_NODES }>(
2280 cfg.network_config.builder_port(),
2281 chain_config.map(|c| *c.max_block_size),
2282 )
2283 .await;
2284 builder_tasks.push(task);
2285 cfg.network_config
2286 .set_builder_urls(vec1::vec1![builder_url.clone()]);
2287
2288 let mut opt = cfg.api_config.clone();
2290 let temp_dir = if opt.storage_fs.is_none() && opt.storage_sql.is_none() {
2291 let temp_dir = tempfile::tempdir().unwrap();
2292 opt = opt.query_fs(
2293 Default::default(),
2294 crate::persistence::fs::Options::new(temp_dir.path().to_path_buf()),
2295 );
2296 Some(temp_dir)
2297 } else {
2298 None
2299 };
2300
2301 let deferred = cfg.deferred_start.clone();
2302 assert!(
2303 deferred.len() < NUM_NODES,
2304 "node 0 runs the API server and cannot be deferred"
2305 );
2306 assert_eq!(
2307 deferred,
2308 (NUM_NODES - deferred.len()..NUM_NODES).collect::<Vec<_>>(),
2309 "deferred_start must be the trailing indices so `node(i)` stays aligned"
2310 );
2311
2312 let mut nodes = join_all(
2313 izip!(cfg.state, cfg.persistence, cfg.catchup)
2314 .enumerate()
2315 .filter(|(i, _)| !deferred.contains(i))
2316 .map(|(i, (state, persistence, state_peers))| {
2317 let opt = opt.clone();
2318 let cfg = &cfg.network_config;
2319 let upgrades_map = cfg.upgrades();
2320 async move {
2321 if i == 0 {
2322 opt.serve(|metrics, consumer, storage| {
2323 let cfg = cfg.clone();
2324 async move {
2325 Ok(cfg
2326 .init_node(
2327 0,
2328 state,
2329 persistence,
2330 Some(state_peers),
2331 storage,
2332 &*metrics,
2333 STAKE_TABLE_CAPACITY_FOR_TEST,
2334 consumer,
2335 upgrade,
2336 upgrades_map,
2337 )
2338 .await)
2339 }
2340 .boxed()
2341 })
2342 .await
2343 .unwrap()
2344 } else {
2345 cfg.init_node(
2346 i,
2347 state,
2348 persistence,
2349 Some(state_peers),
2350 None,
2351 &NoMetrics,
2352 STAKE_TABLE_CAPACITY_FOR_TEST,
2353 NullEventConsumer,
2354 upgrade,
2355 upgrades_map,
2356 )
2357 .await
2358 }
2359 }
2360 .boxed()
2361 }),
2362 )
2363 .await;
2364
2365 let handle_0 = &nodes[0];
2366
2367 for builder_task in builder_tasks {
2369 builder_task.start(Box::new(
2370 handle_0
2371 .consensus_handle()
2372 .legacy_consensus()
2373 .read()
2374 .await
2375 .event_stream(),
2376 ));
2377 }
2378
2379 for ctx in &nodes {
2380 ctx.start_consensus().await;
2381 }
2382
2383 let server = nodes.remove(0);
2384 let peers = nodes;
2385
2386 Self {
2387 server,
2388 peers,
2389 cfg: cfg.network_config,
2390 temp_dir,
2391 contracts: cfg.contracts,
2392 deferred,
2393 }
2394 }
2395
2396 pub async fn start_deferred_node<C: StateCatchup + 'static>(
2400 &mut self,
2401 i: usize,
2402 state: ValidatedState,
2403 persistence: P,
2404 catchup: C,
2405 upgrade: versions::Upgrade,
2406 ) -> &SequencerContext<network::Memory, P::Persistence> {
2407 assert_eq!(
2408 self.deferred.first(),
2409 Some(&i),
2410 "deferred nodes must be started in ascending index order"
2411 );
2412 self.deferred.remove(0);
2413
2414 let ctx = self
2415 .init_and_start(i, state, persistence, catchup, upgrade)
2416 .await;
2417 self.peers.push(ctx);
2418 self.peers.last().unwrap()
2419 }
2420
2421 pub async fn restart_node<C: StateCatchup + 'static>(
2427 &mut self,
2428 i: usize,
2429 state: ValidatedState,
2430 persistence: P,
2431 catchup: C,
2432 upgrade: versions::Upgrade,
2433 ) -> &SequencerContext<network::Memory, P::Persistence> {
2434 assert_ne!(i, 0, "node 0 runs the API server and cannot be restarted");
2435 assert!(
2436 !self.deferred.contains(&i),
2437 "node {i} was deferred and has not been started yet"
2438 );
2439 self.peers[i - 1].shut_down().await;
2440 let addr = self.cfg.coordinator_addr(i).to_string();
2445 timeout(Duration::from_secs(60), async {
2446 while std::net::TcpListener::bind(&addr).is_err() {
2447 sleep(Duration::from_millis(100)).await;
2448 }
2449 })
2450 .await
2451 .expect("shut-down node did not release its coordinator port");
2452
2453 let ctx = self
2454 .init_and_start(i, state, persistence, catchup, upgrade)
2455 .await;
2456 self.peers[i - 1] = ctx;
2457 &self.peers[i - 1]
2458 }
2459
2460 async fn init_and_start<C: StateCatchup + 'static>(
2463 &self,
2464 i: usize,
2465 state: ValidatedState,
2466 persistence: P,
2467 catchup: C,
2468 upgrade: versions::Upgrade,
2469 ) -> SequencerContext<network::Memory, P::Persistence> {
2470 let ctx = self
2471 .cfg
2472 .init_node(
2473 i,
2474 state,
2475 persistence,
2476 Some(catchup),
2477 None,
2478 &NoMetrics,
2479 STAKE_TABLE_CAPACITY_FOR_TEST,
2480 NullEventConsumer,
2481 upgrade,
2482 self.cfg.upgrades(),
2483 )
2484 .await;
2485 ctx.start_consensus().await;
2486 ctx
2487 }
2488
2489 pub async fn stop_consensus(&mut self) {
2490 self.server.shutdown_consensus().await;
2491
2492 for ctx in &mut self.peers {
2493 ctx.shutdown_consensus().await;
2494 }
2495 }
2496
2497 pub fn node(&self, i: usize) -> &SequencerContext<network::Memory, P::Persistence> {
2499 if i == 0 {
2500 &self.server
2501 } else {
2502 &self.peers[i - 1]
2503 }
2504 }
2505 }
2506
2507 pub async fn register_validators<const NUM_NODES: usize>(
2510 cfg: &TestConfig<NUM_NODES>,
2511 stake_table: Address,
2512 indices: &[usize],
2513 delegation_config: DelegationConfig,
2514 ) -> anyhow::Result<()> {
2515 let deployer = ProviderBuilder::new()
2516 .wallet(EthereumWallet::from(cfg.signer()))
2517 .connect_http(cfg.l1_url());
2518 StakingTransactions::create(
2519 cfg.l1_url(),
2520 &deployer,
2521 stake_table,
2522 cfg.staking_key_sets(indices),
2523 None,
2524 delegation_config,
2525 )
2526 .await?
2527 .apply_all()
2528 .await?;
2529 Ok(())
2530 }
2531
2532 pub async fn deregister_validators<const NUM_NODES: usize>(
2535 cfg: &TestConfig<NUM_NODES>,
2536 stake_table: Address,
2537 indices: &[usize],
2538 ) -> anyhow::Result<()> {
2539 let providers = cfg.validator_providers();
2540 for &i in indices {
2541 let (address, provider) = &providers[i];
2542 let receipt = StakingTransaction::DeregisterValidator { stake_table }
2543 .send(provider)
2544 .await?
2545 .get_receipt()
2546 .await?;
2547 anyhow::ensure!(
2548 receipt.status(),
2549 "deregistration of validator {i} ({address}) reverted"
2550 );
2551 }
2552 Ok(())
2553 }
2554
2555 pub async fn delegate_new<const NUM_NODES: usize>(
2559 cfg: &TestConfig<NUM_NODES>,
2560 token: Address,
2561 stake_table: Address,
2562 validator: Address,
2563 amount: U256,
2564 ) -> anyhow::Result<impl Provider + Clone + use<NUM_NODES>> {
2565 let deployer = ProviderBuilder::new()
2566 .wallet(EthereumWallet::from(cfg.signer()))
2567 .connect_http(cfg.l1_url());
2568 let signer = PrivateKeySigner::random();
2569 let delegator = signer.address();
2570 let provider = ProviderBuilder::new()
2571 .wallet(EthereumWallet::from(signer))
2572 .connect_http(cfg.l1_url());
2573
2574 deployer
2575 .anvil_set_balance(delegator, parse_ether("10").unwrap())
2576 .await?;
2577 let funding = StakingTransaction::Transfer {
2578 token,
2579 to: delegator,
2580 amount,
2581 }
2582 .send(&deployer)
2583 .await?
2584 .get_receipt()
2585 .await?;
2586 anyhow::ensure!(funding.status(), "ESP transfer to delegator reverted");
2587
2588 for tx in [
2589 StakingTransaction::Approve {
2590 token,
2591 spender: stake_table,
2592 amount,
2593 },
2594 StakingTransaction::Delegate {
2595 stake_table,
2596 validator,
2597 amount,
2598 },
2599 ] {
2600 let receipt = tx.send(&provider).await?.get_receipt().await?;
2601 anyhow::ensure!(receipt.status(), "delegator transaction reverted");
2602 }
2603 Ok(provider)
2604 }
2605
2606 pub async fn wait_for_committee(
2611 client: &Client<ClientErr, SequencerApiVersion>,
2612 events: &mut (impl Stream<Item = CoordinatorEvent<SeqTypes>> + Unpin),
2613 epoch_height: u64,
2614 start_epoch: u64,
2615 max_epochs: u64,
2616 pred: impl Fn(&AuthenticatedValidatorMap) -> bool,
2617 ) -> (u64, AuthenticatedValidatorMap) {
2618 let mut last = None;
2619 for epoch in start_epoch..start_epoch + max_epochs {
2620 wait_for_epochs(events, epoch_height, epoch).await;
2621 let validators = client
2622 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{epoch}"))
2623 .send()
2624 .await
2625 .expect("validators for a decided epoch");
2626 if pred(&validators) {
2627 return (epoch, validators);
2628 }
2629 last = Some((epoch, validators));
2630 }
2631 let last =
2632 last.map(|(epoch, validators)| (epoch, validators.keys().copied().collect::<Vec<_>>()));
2633 panic!(
2634 "committee predicate not satisfied within {max_epochs} epochs starting at \
2635 {start_epoch}; last committee: {last:?}"
2636 );
2637 }
2638
2639 pub fn staking_addresses<const NUM_NODES: usize>(
2641 cfg: &TestConfig<NUM_NODES>,
2642 indices: &[usize],
2643 ) -> HashSet<Address> {
2644 cfg.staking_key_sets(indices)
2645 .iter()
2646 .map(|keys| keys.signer.address())
2647 .collect()
2648 }
2649
2650 pub fn committee_is(expected: HashSet<Address>) -> impl Fn(&AuthenticatedValidatorMap) -> bool {
2653 move |validators| validators.keys().copied().collect::<HashSet<_>>() == expected
2654 }
2655
2656 pub async fn assert_node_live<P: SequencerPersistence>(
2663 node: &SequencerContext<network::Memory, P>,
2664 epoch_height: u64,
2665 epochs_ahead: u64,
2666 ) {
2667 assert!(epochs_ahead > 0, "epochs_ahead must be at least 1");
2668 let mut events = node.event_stream();
2669 let leaf = node.decided_leaf().await;
2670 let current = leaf
2671 .epoch(epoch_height)
2672 .map(|epoch| epoch.u64())
2673 .unwrap_or_default();
2674 wait_for_epochs(&mut events, epoch_height, current + epochs_ahead - 1).await;
2677
2678 if node.decided_leaf().await.block_header().version() < versions::NEW_PROTOCOL_VERSION {
2679 tracing::info!("legacy version: skipping transaction-inclusion liveness check");
2680 return;
2681 }
2682 static NAMESPACE_COUNTER: AtomicU32 = AtomicU32::new(10_101);
2686 let namespace = NamespaceId::from(NAMESPACE_COUNTER.fetch_add(1, Ordering::Relaxed));
2687 let tx = Transaction::new(namespace, vec![7; 8]);
2688 node.submit_transaction(tx)
2689 .await
2690 .expect("live node accepts transactions");
2691 tokio::time::timeout(Duration::from_secs(120), async {
2692 loop {
2693 let leaf = match events.next().await.unwrap() {
2694 CoordinatorEvent::LegacyEvent(Event {
2695 event: EventType::Decide { leaf_chain, .. },
2696 ..
2697 }) => leaf_chain[0].leaf.clone(),
2698 CoordinatorEvent::NewDecide { leaf_infos, .. } => leaf_infos[0].leaf.clone(),
2699 _ => continue,
2700 };
2701 if leaf
2702 .block_header()
2703 .ns_table()
2704 .find_ns_id(&namespace)
2705 .is_some()
2706 {
2707 tracing::info!(height = leaf.height(), "transaction namespace sequenced");
2708 return;
2709 }
2710 }
2711 })
2712 .await
2713 .expect("submitted transaction was not sequenced in time");
2714 }
2715
2716 pub async fn assert_nodes_agree<P: SequencerPersistence>(
2719 nodes: &[&SequencerContext<network::Memory, P>],
2720 min_height: u64,
2721 ) {
2722 let leaves = join_all(nodes.iter().map(|node| node.decided_leaf())).await;
2723 let mut by_height: std::collections::BTreeMap<u64, Commitment<Leaf2>> = Default::default();
2724 for (i, leaf) in leaves.iter().enumerate() {
2725 assert!(
2726 leaf.height() >= min_height,
2727 "node {i} decided height {} is below {min_height}",
2728 leaf.height()
2729 );
2730 if let Some(other) = by_height.insert(leaf.height(), leaf.commit()) {
2731 assert_eq!(
2732 other,
2733 leaf.commit(),
2734 "decided-leaf divergence at height {}",
2735 leaf.height()
2736 );
2737 }
2738 }
2739 }
2740
2741 pub async fn status_test_helper(opt: impl FnOnce(Options) -> Options) {
2749 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
2750 let url = format!("http://localhost:{port}").parse().unwrap();
2751 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
2752
2753 let options = opt(Options::with_port(port));
2754 let network_config = TestConfigBuilder::default().build();
2755 let config = TestNetworkConfigBuilder::default()
2756 .api_config(options)
2757 .network_config(network_config)
2758 .build();
2759 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
2760 client.connect(None).await;
2761
2762 while client
2766 .get::<u64>("status/block-height")
2767 .send()
2768 .await
2769 .unwrap()
2770 <= 1
2771 {
2772 sleep(Duration::from_secs(1)).await;
2773 }
2774 let success_rate = client
2775 .get::<f64>("status/success-rate")
2776 .send()
2777 .await
2778 .unwrap();
2779 assert!(success_rate.is_finite(), "{success_rate}");
2782 assert!(success_rate > 0.0, "{success_rate}");
2784
2785 let keys: NodePublicKeys = client.get("status/keys").send().await.unwrap();
2786 let expected = network.server.validator_config();
2787 assert_eq!(keys.consensus_key, expected.public_key);
2788 assert_eq!(keys.state_ver_key, expected.state_public_key);
2789 assert_eq!(
2790 keys.x25519_key,
2791 expected.x25519_keypair.as_ref().map(|kp| kp.public_key())
2792 );
2793 assert_eq!(keys.eth_account, None);
2794
2795 let json: serde_json::Value = client.get("status/keys").send().await.unwrap();
2796 let bls = json["consensus_key"].as_str().unwrap();
2797 assert!(bls.starts_with("BLS_VER_KEY~"), "{bls}");
2798 let schnorr = json["state_ver_key"].as_str().unwrap();
2799 assert!(schnorr.starts_with("SCHNORR_VER_KEY~"), "{schnorr}");
2800 let x25519 = json["x25519_key"].as_str().unwrap();
2801 assert!(x25519.starts_with("X25519_PK~"), "{x25519}");
2802 }
2803
2804 pub async fn submit_test_helper(opt: impl FnOnce(Options) -> Options) {
2812 let txn = Transaction::new(NamespaceId::from(1_u32), vec![1, 2, 3, 4]);
2813
2814 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
2815
2816 let url = format!("http://localhost:{port}").parse().unwrap();
2817 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
2818
2819 let options = opt(Options::with_port(port).submit(Default::default()));
2820 let network_config = TestConfigBuilder::default().build();
2821 let config = TestNetworkConfigBuilder::default()
2822 .api_config(options)
2823 .network_config(network_config)
2824 .build();
2825 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
2826 let mut events = network.server.event_stream();
2827
2828 client.connect(None).await;
2829
2830 let hash = client
2831 .post("submit/submit")
2832 .body_json(&txn)
2833 .unwrap()
2834 .send()
2835 .await
2836 .unwrap();
2837 assert_eq!(txn.commit(), hash);
2838
2839 wait_for_decide_on_handle(&mut events, &txn).await;
2841 }
2842
2843 pub async fn state_signature_test_helper(opt: impl FnOnce(Options) -> Options) {
2845 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
2846
2847 let url = format!("http://localhost:{port}").parse().unwrap();
2848
2849 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
2850
2851 let options = opt(Options::with_port(port));
2852 let network_config = TestConfigBuilder::default().build();
2853 let config = TestNetworkConfigBuilder::default()
2854 .api_config(options)
2855 .network_config(network_config)
2856 .build();
2857 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
2858
2859 let mut height: u64;
2860 loop {
2863 height = network.server.decided_leaf().await.height();
2864 sleep(std::time::Duration::from_secs(1)).await;
2865 if height >= 2 {
2866 break;
2867 }
2868 }
2869 client
2871 .get::<LCV3StateSignatureRequestBody>(&format!("state-signature/block/{height}"))
2872 .send()
2873 .await
2874 .unwrap();
2875 }
2876
2877 pub async fn catchup_test_helper(opt: impl FnOnce(Options) -> Options) {
2885 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
2886 let url = format!("http://localhost:{port}").parse().unwrap();
2887 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
2888
2889 let options = opt(Options::with_port(port));
2890 let network_config = TestConfigBuilder::default().build();
2891 let config = TestNetworkConfigBuilder::default()
2892 .api_config(options)
2893 .network_config(network_config)
2894 .build();
2895 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
2896 client.connect(None).await;
2897
2898 let mut events = network.server.event_stream();
2900 loop {
2901 if let CoordinatorEvent::LegacyEvent(Event {
2902 event: EventType::Decide { leaf_chain, .. },
2903 ..
2904 }) = events.next().await.unwrap()
2905 && leaf_chain
2906 .iter()
2907 .any(|LeafInfo { leaf, .. }| leaf.block_header().height() > 2)
2908 {
2909 break;
2910 }
2911 }
2912
2913 {
2916 network.server.shutdown_consensus().await;
2917 }
2918
2919 let leaf = network.server.decided_leaf().await;
2921 let height = leaf.height() + 1;
2922 let view = leaf.view_number() + 1;
2923 let res = client
2924 .get::<AccountQueryData>(&format!(
2925 "catchup/{height}/{}/account/{:x}",
2926 view.u64(),
2927 Address::default()
2928 ))
2929 .send()
2930 .await
2931 .unwrap();
2932 assert_eq!(res.balance, U256::ZERO);
2933 assert_eq!(
2934 res.proof
2935 .verify(
2936 &network
2937 .server
2938 .state(view)
2939 .await
2940 .unwrap()
2941 .fee_merkle_tree
2942 .commitment()
2943 )
2944 .unwrap(),
2945 U256::ZERO,
2946 );
2947
2948 let res = client
2950 .get::<BlocksFrontier>(&format!("catchup/{height}/{}/blocks", view.u64()))
2951 .send()
2952 .await
2953 .unwrap();
2954 let root = &network
2955 .server
2956 .state(view)
2957 .await
2958 .unwrap()
2959 .block_merkle_tree
2960 .commitment();
2961 BlockMerkleTree::verify(root, root.size() - 1, res)
2962 .unwrap()
2963 .unwrap();
2964 }
2965}
2966
2967#[cfg(test)]
2968mod api_tests {
2969 use std::{fmt::Debug, marker::PhantomData};
2970
2971 use committable::Committable;
2972 use data_source::testing::TestableSequencerDataSource;
2973 use espresso_types::{
2974 Header, Leaf2, MOCK_SEQUENCER_VERSIONS, NamespaceId, NamespaceProofQueryData,
2975 ValidatedState,
2976 traits::{EventConsumer, PersistenceOptions},
2977 };
2978 use futures::{future, stream::StreamExt};
2979 use hotshot_example_types::node_types::TEST_VERSIONS;
2980 use hotshot_query_service::availability::{
2981 AvailabilityDataSource, BlockQueryData, VidCommonQueryData,
2982 };
2983 use hotshot_types::{
2984 data::{
2985 DaProposal2, EpochNumber, QuorumProposal2, QuorumProposalWrapper, VidCommitment,
2986 VidDisperseShare, ns_table::parse_ns_table, vid_disperse::AvidMDisperseShare,
2987 },
2988 event::LeafInfo,
2989 message::Proposal,
2990 simple_certificate::{CertificatePair, QuorumCertificate2},
2991 traits::{EncodeBytes, signature_key::SignatureKey},
2992 utils::EpochTransitionIndicator,
2993 vid::avidm::{AvidMScheme, init_avidm_param},
2994 };
2995 use http_client::{Client, error::ClientErr};
2996 use test_helpers::{
2997 TestNetwork, TestNetworkConfigBuilder, catchup_test_helper, state_signature_test_helper,
2998 status_test_helper, submit_test_helper,
2999 };
3000 use test_utils::reserve_tcp_port;
3001 use vbs::version::StaticVersion;
3002
3003 use super::{update::ApiEventConsumer, *};
3004 use crate::{
3005 network,
3006 persistence::no_storage::NoStorage,
3007 testing::{TestConfigBuilder, wait_for_decide_on_handle},
3008 };
3009
3010 #[rstest_reuse::template]
3011 #[rstest::rstest]
3012 #[case(PhantomData::<crate::api::sql::DataSource>)]
3013 #[case(PhantomData::<crate::api::fs::DataSource>)]
3014 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3015 pub fn testable_sequencer_data_source<D: TestableSequencerDataSource>(
3016 #[case] _d: PhantomData<D>,
3017 ) {
3018 }
3019
3020 #[rstest_reuse::apply(testable_sequencer_data_source)]
3021 pub(crate) async fn submit_test_with_query_module<D: TestableSequencerDataSource>(
3022 _d: PhantomData<D>,
3023 ) {
3024 let storage = D::create_storage().await;
3025 submit_test_helper(|opt| D::options(&storage, opt)).await
3026 }
3027
3028 #[rstest_reuse::apply(testable_sequencer_data_source)]
3029 pub(crate) async fn status_test_with_query_module<D: TestableSequencerDataSource>(
3030 _d: PhantomData<D>,
3031 ) {
3032 let storage = D::create_storage().await;
3033 status_test_helper(|opt| D::options(&storage, opt)).await
3034 }
3035
3036 #[rstest_reuse::apply(testable_sequencer_data_source)]
3037 pub(crate) async fn state_signature_test_with_query_module<D: TestableSequencerDataSource>(
3038 _d: PhantomData<D>,
3039 ) {
3040 let storage = D::create_storage().await;
3041 state_signature_test_helper(|opt| D::options(&storage, opt)).await
3042 }
3043
3044 #[rstest_reuse::apply(testable_sequencer_data_source)]
3045 pub(crate) async fn test_namespace_query<D: TestableSequencerDataSource>(_d: PhantomData<D>) {
3046 let ns_id = NamespaceId::from(42_u32);
3048 let txn = Transaction::new(ns_id, vec![1, 2, 3, 4]);
3049
3050 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3052 let storage = D::create_storage().await;
3053 let network_config = TestConfigBuilder::default().build();
3054 let config = TestNetworkConfigBuilder::default()
3055 .api_config(D::options(&storage, Options::with_port(port)).submit(Default::default()))
3056 .network_config(network_config)
3057 .build();
3058 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3059 let mut events = network.server.event_stream();
3060
3061 let client: Client<ClientErr, StaticVersion<0, 1>> =
3063 Client::new(format!("http://localhost:{port}").parse().unwrap());
3064 client.connect(None).await;
3065
3066 let hash = client
3067 .post("submit/submit")
3068 .body_json(&txn)
3069 .unwrap()
3070 .send()
3071 .await
3072 .unwrap();
3073 assert_eq!(txn.commit(), hash);
3074
3075 let block_height = wait_for_decide_on_handle(&mut events, &txn).await.0 as usize;
3077 tracing::info!(block_height, "transaction sequenced");
3078
3079 let txn2 = Transaction::new(ns_id, vec![5, 6, 7, 8]);
3081 client
3082 .post::<Commitment<Transaction>>("submit/submit")
3083 .body_json(&txn2)
3084 .unwrap()
3085 .send()
3086 .await
3087 .unwrap();
3088 let block_height2 = wait_for_decide_on_handle(&mut events, &txn2).await.0 as usize;
3089 tracing::info!(block_height2, "transaction sequenced");
3090
3091 client
3093 .socket(&format!("availability/stream/blocks/{block_height2}"))
3094 .subscribe::<BlockQueryData<SeqTypes>>()
3095 .await
3096 .unwrap()
3097 .next()
3098 .await
3099 .unwrap()
3100 .unwrap();
3101
3102 let mut found_txn = false;
3103 let mut found_empty_block = false;
3104 for block_num in 0..=block_height {
3105 let header: Header = client
3106 .get(&format!("availability/header/{block_num}"))
3107 .send()
3108 .await
3109 .unwrap();
3110 let ns_query_res: NamespaceProofQueryData = client
3111 .get(&format!("availability/block/{block_num}/namespace/{ns_id}"))
3112 .send()
3113 .await
3114 .unwrap();
3115
3116 assert_eq!(
3118 ns_query_res,
3119 client
3120 .get(&format!(
3121 "availability/block/hash/{}/namespace/{ns_id}",
3122 header.commit()
3123 ))
3124 .send()
3125 .await
3126 .unwrap()
3127 );
3128 assert_eq!(
3129 ns_query_res,
3130 client
3131 .get(&format!(
3132 "availability/block/payload-hash/{}/namespace/{ns_id}",
3133 header.payload_commitment()
3134 ))
3135 .send()
3136 .await
3137 .unwrap()
3138 );
3139
3140 if let Some(ns_proof) = ns_query_res.proof {
3142 let vid_common: VidCommonQueryData<SeqTypes> = client
3143 .get(&format!("availability/vid/common/{block_num}"))
3144 .send()
3145 .await
3146 .unwrap();
3147 ns_proof
3148 .verify(
3149 header.ns_table(),
3150 &header.payload_commitment(),
3151 vid_common.common(),
3152 )
3153 .unwrap();
3154 } else {
3155 assert!(header.ns_table().find_ns_id(&ns_id).is_none());
3157 assert!(ns_query_res.transactions.is_empty());
3158 }
3159
3160 found_empty_block = found_empty_block || ns_query_res.transactions.is_empty();
3161
3162 for txn in ns_query_res.transactions {
3163 if txn.commit() == hash {
3164 found_txn = true;
3166 }
3167 }
3168 }
3169 assert!(found_txn);
3170 assert!(found_empty_block);
3171
3172 let ns_proofs: Vec<NamespaceProofQueryData> = client
3174 .get(&format!(
3175 "availability/block/{block_height}/{}/namespace/{ns_id}",
3176 block_height2 + 1
3177 ))
3178 .send()
3179 .await
3180 .unwrap();
3181 assert_eq!(ns_proofs.len(), block_height2 + 1 - block_height);
3182 assert_eq!(&ns_proofs[0].transactions, std::slice::from_ref(&txn));
3183 assert_eq!(
3184 &ns_proofs[ns_proofs.len() - 1].transactions,
3185 std::slice::from_ref(&txn2)
3186 );
3187 for proof in &ns_proofs[1..ns_proofs.len() - 1] {
3188 assert_eq!(proof.transactions, &[]);
3189 }
3190 }
3191
3192 #[rstest_reuse::apply(testable_sequencer_data_source)]
3193 pub(crate) async fn catchup_test_with_query_module<D: TestableSequencerDataSource>(
3194 _d: PhantomData<D>,
3195 ) {
3196 let storage = D::create_storage().await;
3197 catchup_test_helper(|opt| D::options(&storage, opt)).await
3198 }
3199
3200 #[rstest_reuse::apply(testable_sequencer_data_source)]
3201 pub async fn test_non_consecutive_decide_with_failing_event_consumer<D>(_d: PhantomData<D>)
3202 where
3203 D: TestableSequencerDataSource + Debug + 'static,
3204 {
3205 use hotshot_types::new_protocol::CoordinatorEvent;
3206
3207 #[derive(Clone, Copy, Debug)]
3208 struct FailConsumer;
3209
3210 #[async_trait]
3211 impl EventConsumer for FailConsumer {
3212 async fn handle_event(&self, _: &CoordinatorEvent<SeqTypes>) -> anyhow::Result<()> {
3213 bail!("mock error injection");
3214 }
3215 }
3216
3217 let (pubkey, privkey) = PubKey::generated_from_seed_indexed([0; 32], 1);
3218
3219 let storage = D::create_storage().await;
3220 let persistence = D::persistence_options(&storage).create().await.unwrap();
3221 let data_source: Arc<StorageState<network::Memory, NoStorage, _>> =
3222 Arc::new(StorageState::new(
3223 D::create(D::persistence_options(&storage), Default::default(), false)
3224 .await
3225 .unwrap(),
3226 ApiState::new(future::pending()),
3227 ));
3228
3229 let mut chain1 = vec![];
3231
3232 let genesis = Leaf2::genesis(
3233 &Default::default(),
3234 &NodeState::mock(),
3235 TEST_VERSIONS.test.base,
3236 )
3237 .await;
3238 let payload = genesis.block_payload().unwrap();
3239 let payload_bytes_arc = payload.encode();
3240
3241 let avidm_param = init_avidm_param(2).unwrap();
3242 let weights = vec![1u32; 2];
3243
3244 let ns_table = parse_ns_table(payload.byte_len().as_usize(), &payload.ns_table().encode());
3245 let (payload_commitment, shares) =
3246 AvidMScheme::ns_disperse(&avidm_param, &weights, &payload_bytes_arc, ns_table).unwrap();
3247
3248 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
3249 proposal: QuorumProposal2::<SeqTypes> {
3250 block_header: genesis.block_header().clone(),
3251 view_number: ViewNumber::genesis(),
3252 justify_qc: QuorumCertificate2::genesis(
3253 &ValidatedState::default(),
3254 &NodeState::mock(),
3255 MOCK_SEQUENCER_VERSIONS,
3256 )
3257 .await,
3258 upgrade_certificate: None,
3259 view_change_evidence: None,
3260 next_drb_result: None,
3261 next_epoch_justify_qc: None,
3262 epoch: None,
3263 state_cert: None,
3264 },
3265 };
3266 let mut qc = QuorumCertificate2::genesis(
3267 &ValidatedState::default(),
3268 &NodeState::mock(),
3269 MOCK_SEQUENCER_VERSIONS,
3270 )
3271 .await;
3272
3273 let mut justify_qc = qc.clone();
3274 for i in 0..5 {
3275 *quorum_proposal.proposal.block_header.height_mut() = i;
3276 quorum_proposal.proposal.view_number = ViewNumber::new(i);
3277 quorum_proposal.proposal.justify_qc = justify_qc;
3278 let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
3279 qc.view_number = leaf.view_number();
3280 qc.data.leaf_commit = Committable::commit(&leaf);
3281 justify_qc = qc.clone();
3282 chain1.push((leaf.clone(), CertificatePair::non_epoch_change(qc.clone())));
3283
3284 let quorum_proposal_signature =
3286 PubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
3287 .expect("Failed to sign quorum_proposal");
3288 persistence
3289 .append_quorum_proposal2(&Proposal {
3290 data: quorum_proposal.clone(),
3291 signature: quorum_proposal_signature,
3292 _pd: Default::default(),
3293 })
3294 .await
3295 .unwrap();
3296
3297 let share: VidDisperseShare<SeqTypes> = AvidMDisperseShare {
3299 view_number: leaf.view_number(),
3300 payload_commitment,
3301 share: shares[0].clone(),
3302 recipient_key: pubkey,
3303 epoch: Some(EpochNumber::new(0)),
3304 target_epoch: Some(EpochNumber::new(0)),
3305 common: avidm_param.clone(),
3306 }
3307 .into();
3308
3309 persistence
3310 .append_vid(&share.to_proposal(&privkey).unwrap())
3311 .await
3312 .unwrap();
3313
3314 let block_payload_signature =
3316 PubKey::sign(&privkey, &payload_bytes_arc).expect("Failed to sign block payload");
3317 let da_proposal_inner = DaProposal2::<SeqTypes> {
3318 encoded_transactions: payload_bytes_arc.clone(),
3319 metadata: payload.ns_table().clone(),
3320 view_number: leaf.view_number(),
3321 epoch: Some(EpochNumber::new(0)),
3322 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
3323 };
3324 let da_proposal = Proposal {
3325 data: da_proposal_inner,
3326 signature: block_payload_signature,
3327 _pd: Default::default(),
3328 };
3329 persistence
3330 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
3331 .await
3332 .unwrap();
3333 }
3334 let mut chain2 = chain1.split_off(2);
3336 chain2.remove(0);
3338
3339 let leaf_chain = chain1
3341 .iter()
3342 .map(|(leaf, qc)| (leaf_info(leaf.clone()), qc.clone()))
3343 .collect::<Vec<_>>();
3344 tracing::info!("decide with event handling failure");
3345 persistence
3346 .append_decided_leaves(
3347 ViewNumber::new(1),
3348 leaf_chain.iter().map(|(leaf, qc)| (leaf, qc.clone())),
3349 None,
3350 &FailConsumer,
3351 )
3352 .await
3353 .unwrap();
3354
3355 let consumer = ApiEventConsumer::from(data_source.clone());
3358 let leaf_chain = chain2
3359 .iter()
3360 .map(|(leaf, qc)| (leaf_info(leaf.clone()), qc.clone()))
3361 .collect::<Vec<_>>();
3362 tracing::info!("decide successfully");
3363 persistence
3364 .append_decided_leaves(
3365 ViewNumber::new(4),
3366 leaf_chain.iter().map(|(leaf, qc)| (leaf, qc.clone())),
3367 None,
3368 &consumer,
3369 )
3370 .await
3371 .unwrap();
3372
3373 for (leaf, cert) in chain1.iter().chain(&chain2) {
3376 tracing::info!(height = leaf.height(), "check archive");
3377 let qd = data_source.get_leaf(leaf.height() as usize).await.await;
3378 let stored_leaf: Leaf2 = qd.leaf().clone();
3379 let stored_qc = qd.qc().clone();
3380 assert_eq!(&stored_leaf, leaf);
3381 assert_eq!(&stored_qc, cert.qc());
3382
3383 data_source
3384 .get_block(leaf.height() as usize)
3385 .await
3386 .try_resolve()
3387 .ok()
3388 .unwrap();
3389 data_source
3390 .get_vid_common(leaf.height() as usize)
3391 .await
3392 .try_resolve()
3393 .ok()
3394 .unwrap();
3395
3396 assert!(
3398 persistence
3399 .load_da_proposal(leaf.view_number())
3400 .await
3401 .unwrap()
3402 .is_none()
3403 );
3404 assert!(
3405 persistence
3406 .load_vid_share(leaf.view_number())
3407 .await
3408 .unwrap()
3409 .is_none()
3410 );
3411 assert!(
3412 persistence
3413 .load_quorum_proposal(leaf.view_number())
3414 .await
3415 .is_err()
3416 );
3417 }
3418
3419 assert!(
3421 persistence
3422 .load_da_proposal(ViewNumber::new(2))
3423 .await
3424 .unwrap()
3425 .is_some()
3426 );
3427 assert!(
3428 persistence
3429 .load_vid_share(ViewNumber::new(2))
3430 .await
3431 .unwrap()
3432 .is_some()
3433 );
3434 persistence
3435 .load_quorum_proposal(ViewNumber::new(2))
3436 .await
3437 .unwrap();
3438 }
3439
3440 #[rstest_reuse::apply(testable_sequencer_data_source)]
3441 pub async fn test_decide_missing_data<D>(_d: PhantomData<D>)
3442 where
3443 D: TestableSequencerDataSource + Debug + 'static,
3444 {
3445 use ark_serialize::CanonicalDeserialize;
3446
3447 let storage = D::create_storage().await;
3448 let persistence = D::persistence_options(&storage).create().await.unwrap();
3449 let data_source: Arc<StorageState<network::Memory, NoStorage, _>> =
3450 Arc::new(StorageState::new(
3451 D::create(D::persistence_options(&storage), Default::default(), false)
3452 .await
3453 .unwrap(),
3454 ApiState::new(future::pending()),
3455 ));
3456 let consumer = ApiEventConsumer::from(data_source.clone());
3457
3458 let mut qc = QuorumCertificate2::genesis(
3459 &ValidatedState::default(),
3460 &NodeState::mock(),
3461 MOCK_SEQUENCER_VERSIONS,
3462 )
3463 .await;
3464 let leaf = Leaf2::genesis(
3465 &ValidatedState::default(),
3466 &NodeState::mock(),
3467 TEST_VERSIONS.test.base,
3468 )
3469 .await;
3470
3471 tracing::info!(?leaf, ?qc, "decide genesis leaf");
3475 persistence
3476 .append_decided_leaves(
3477 leaf.view_number(),
3478 [(
3479 &leaf_info(leaf.clone()),
3480 CertificatePair::non_epoch_change(qc.clone()),
3481 )],
3482 None,
3483 &consumer,
3484 )
3485 .await
3486 .unwrap();
3487
3488 let mut block_header = leaf.block_header().clone();
3492 *block_header.height_mut() += 1;
3493 *block_header.payload_commitment_mut() = VidCommitment::V1(
3494 CanonicalDeserialize::deserialize_uncompressed_unchecked([1u8; 32].as_slice()).unwrap(),
3495 );
3496 let qp = QuorumProposalWrapper {
3497 proposal: QuorumProposal2 {
3498 block_header,
3499 view_number: leaf.view_number() + 1,
3500 justify_qc: qc.clone(),
3501 upgrade_certificate: None,
3502 view_change_evidence: None,
3503 next_drb_result: None,
3504 next_epoch_justify_qc: None,
3505 epoch: None,
3506 state_cert: None,
3507 },
3508 };
3509
3510 let leaf = Leaf2::from_quorum_proposal(&qp);
3511 qc.view_number = leaf.view_number();
3512 qc.data.leaf_commit = Committable::commit(&leaf);
3513
3514 tracing::info!(?leaf, ?qc, "append leaf 1");
3516 persistence
3517 .append_decided_leaves(
3518 leaf.view_number(),
3519 [(
3520 &leaf_info(leaf.clone()),
3521 CertificatePair::non_epoch_change(qc),
3522 )],
3523 None,
3524 &consumer,
3525 )
3526 .await
3527 .unwrap();
3528
3529 assert_eq!(leaf, data_source.get_leaf(1).await.await.leaf().clone());
3531 assert!(data_source.get_vid_common(1).await.is_pending());
3532 assert!(data_source.get_block(1).await.is_pending());
3533 }
3534
3535 fn leaf_info(leaf: Leaf2) -> LeafInfo<SeqTypes> {
3536 LeafInfo {
3537 leaf,
3538 vid_share: None,
3539 state: Default::default(),
3540 delta: None,
3541 state_cert: None,
3542 }
3543 }
3544}
3545
3546#[cfg(test)]
3547mod test {
3548 use std::{
3549 collections::{HashMap, HashSet},
3550 time::{Duration, Instant},
3551 };
3552
3553 use ::light_client::{
3554 consensus::{
3555 header::HeaderProof,
3556 leaf::{FinalityProof, LeafProof, LeafProofHint},
3557 payload::PayloadProof,
3558 },
3559 testing::{EpochChangeQuorum, LEGACY_VERSION},
3560 };
3561 use alloy::{
3562 eips::BlockId,
3563 network::EthereumWallet,
3564 primitives::{Address, U256},
3565 providers::{ProviderBuilder, ext::AnvilApi},
3566 };
3567 use async_lock::Mutex;
3568 use committable::{Commitment, Committable};
3569 use espresso_contract_deployer::{
3570 Contract, Contracts, builder::DeployerArgsBuilder,
3571 network_config::light_client_genesis_from_stake_table, upgrade_stake_table_v2,
3572 upgrade_stake_table_v3,
3573 };
3574 use espresso_types::{
3575 FeeAmount, Header, L1Client, L1ClientOptions, MOCK_SEQUENCER_VERSIONS, NamespaceId,
3576 NamespaceProofQueryData, NsProof, RegisteredValidatorMap, RewardDistributor,
3577 StakeTableState, StateCertQueryDataV1, StateCertQueryDataV2, ValidatedState,
3578 ValidatorLeaderCounts,
3579 config::PublicHotShotConfig,
3580 traits::{NullEventConsumer, PersistenceOptions},
3581 v0_3::{COMMISSION_BASIS_POINTS, Fetcher, RewardAmount, RewardMerkleProofV1},
3582 v0_4::{RewardAccountV2, RewardMerkleProofV2},
3583 validators_from_l1_events,
3584 };
3585 use futures::{
3586 future::{self, join_all, try_join_all},
3587 stream::{StreamExt, TryStreamExt},
3588 try_join,
3589 };
3590 use hotshot::types::{Event, EventType};
3591 use hotshot_contract_adapter::{
3592 reward::RewardClaimInput,
3593 sol_types::{EspToken, StakeTableV3},
3594 stake_table::StakeTableContractVersion,
3595 };
3596 use hotshot_query_service::{
3597 availability::{
3598 BlockQueryData, BlockSummaryQueryData, LeafQueryData, TransactionQueryData,
3599 VidCommonQueryData,
3600 },
3601 data_source::{
3602 VersionedDataSource,
3603 sql::Config,
3604 storage::{SqlStorage, StorageConnectionType},
3605 },
3606 explorer::TransactionSummariesResponse,
3607 types::HeightIndexed,
3608 };
3609 use hotshot_types::{
3610 ValidatorConfig,
3611 addr::NetAddr,
3612 data::EpochNumber,
3613 event::LeafInfo,
3614 new_protocol::CoordinatorEvent,
3615 traits::{block_contents::BlockHeader, election::Membership, metrics::NoMetrics},
3616 utils::epoch_from_block_number,
3617 x25519,
3618 };
3619 use http_client::{
3620 Client, StatusCode,
3621 error::ClientErr,
3622 healthcheck::{AppHealth, HealthStatus},
3623 };
3624 use jf_merkle_tree_compat::{
3625 MerkleTreeScheme,
3626 prelude::{MerkleProof, Sha3Node},
3627 };
3628 use pretty_assertions::assert_matches;
3629 use rand::seq::SliceRandom;
3630 use rstest::rstest;
3631 use staking_cli::{
3632 Transaction as StakingTransaction, demo::DelegationConfig, fetch_commission,
3633 update_commission, update_network_config,
3634 };
3635 use test_helpers::{
3636 TestNetwork, TestNetworkConfigBuilder, catchup_test_helper, state_signature_test_helper,
3637 status_test_helper, submit_test_helper,
3638 };
3639 use test_utils::reserve_tcp_port;
3640 use tokio::time::sleep;
3641 use vbs::version::StaticVersion;
3642 use versions::{
3643 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, FEE_VERSION,
3644 NEW_PROTOCOL_VERSION, Upgrade, version,
3645 };
3646
3647 use self::{
3648 data_source::testing::TestableSequencerDataSource, options::HotshotEvents,
3649 sql::DataSource as SqlDataSource,
3650 };
3651 use super::*;
3652
3653 async fn wait_until_block_height(
3654 client: &Client<ClientErr, StaticVersion<0, 1>>,
3655 endpoint: &str,
3656 height: u64,
3657 ) {
3658 for _retry in 0.. {
3659 let bh = client
3660 .get::<u64>(endpoint)
3661 .send()
3662 .await
3663 .expect("block height not found");
3664
3665 if bh >= height {
3666 return;
3667 }
3668 sleep(Duration::from_secs(3)).await;
3669 }
3670 }
3671 use crate::{
3672 api::{
3673 options::Query,
3674 sql::{impl_testable_data_source::tmp_options, reconstruct_state},
3675 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
3676 },
3677 catchup::{NullStateCatchup, StatePeers},
3678 persistence,
3679 persistence::no_storage,
3680 testing::{TestConfig, TestConfigBuilder, wait_for_decide_on_handle, wait_for_epochs},
3681 };
3682
3683 const POS_V3: Upgrade = Upgrade::trivial(version(0, 3));
3684 const POS_V4: Upgrade = Upgrade::trivial(version(0, 4));
3685
3686 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3687 async fn test_healthcheck() {
3688 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3689 let url = format!("http://localhost:{port}").parse().unwrap();
3690 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
3691 let options = Options::with_port(port);
3692 let network_config = TestConfigBuilder::default().build();
3693 let config = TestNetworkConfigBuilder::<5, _, NullStateCatchup>::default()
3694 .api_config(options)
3695 .network_config(network_config)
3696 .build();
3697 let _network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3698
3699 client.connect(None).await;
3700 let health = client.get::<AppHealth>("healthcheck").send().await.unwrap();
3701 assert_eq!(health.status, HealthStatus::Available);
3702 }
3703
3704 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3705 async fn status_test_without_query_module() {
3706 status_test_helper(|opt| opt).await
3707 }
3708
3709 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3710 async fn submit_test_without_query_module() {
3711 submit_test_helper(|opt| opt).await
3712 }
3713
3714 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3715 async fn state_signature_test_without_query_module() {
3716 state_signature_test_helper(|opt| opt).await
3717 }
3718
3719 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3720 async fn catchup_test_without_query_module() {
3721 catchup_test_helper(|opt| opt).await
3722 }
3723
3724 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3725 async fn test_leaf_only_data_source() {
3726 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3727
3728 let storage = SqlDataSource::create_storage().await;
3729 let options =
3730 SqlDataSource::leaf_only_ds_options(&storage, Options::with_port(port)).unwrap();
3731
3732 let network_config = TestConfigBuilder::default().build();
3733 let config = TestNetworkConfigBuilder::default()
3734 .api_config(options)
3735 .network_config(network_config)
3736 .build();
3737 let _network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3738 let url = format!("http://localhost:{port}").parse().unwrap();
3739 let client: Client<ClientErr, SequencerApiVersion> = Client::new(url);
3740
3741 tracing::info!("waiting for blocks");
3742 client.connect(Some(Duration::from_secs(15))).await;
3743 let account = TestConfig::<5>::builder_key().fee_account();
3746
3747 let _headers = client
3748 .socket("availability/stream/headers/0")
3749 .subscribe::<Header>()
3750 .await
3751 .unwrap()
3752 .take(10)
3753 .try_collect::<Vec<_>>()
3754 .await
3755 .unwrap();
3756
3757 for i in 1..5 {
3758 let leaf = client
3759 .get::<LeafQueryData<SeqTypes>>(&format!("availability/leaf/{i}"))
3760 .send()
3761 .await
3762 .unwrap();
3763
3764 assert_eq!(leaf.height(), i);
3765
3766 let header = client
3767 .get::<Header>(&format!("availability/header/{i}"))
3768 .send()
3769 .await
3770 .unwrap();
3771
3772 assert_eq!(header.height(), i);
3773
3774 let vid = client
3775 .get::<VidCommonQueryData<SeqTypes>>(&format!("availability/vid/common/{i}"))
3776 .send()
3777 .await
3778 .unwrap();
3779
3780 assert_eq!(vid.height(), i);
3781
3782 client
3783 .get::<MerkleProof<Commitment<Header>, u64, Sha3Node, 3>>(&format!(
3784 "block-state/{i}/{}",
3785 i - 1
3786 ))
3787 .send()
3788 .await
3789 .unwrap();
3790
3791 client
3792 .get::<MerkleProof<FeeAmount, FeeAccount, Sha3Node, 256>>(&format!(
3793 "fee-state/{}/{}",
3794 i + 1,
3795 account
3796 ))
3797 .send()
3798 .await
3799 .unwrap();
3800 }
3801
3802 client
3805 .get::<BlockQueryData<SeqTypes>>("availability/block/1")
3806 .send()
3807 .await
3808 .unwrap_err();
3809 }
3810
3811 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3812 async fn test_database_metadata_endpoints() {
3813 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3814
3815 let storage = SqlDataSource::create_storage().await;
3816 let options = SqlDataSource::options(&storage, Options::with_port(port));
3817
3818 let network_config = TestConfigBuilder::default().build();
3819 let config = TestNetworkConfigBuilder::default()
3820 .api_config(options)
3821 .network_config(network_config)
3822 .build();
3823 let _network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3824 let url = format!("http://localhost:{port}").parse().unwrap();
3825 let client: Client<ClientErr, SequencerApiVersion> = Client::new(url);
3826 client.connect(Some(Duration::from_secs(15))).await;
3827
3828 let table_sizes = client
3829 .get::<Vec<data_source::TableSize>>("database/table-sizes")
3830 .send()
3831 .await
3832 .unwrap();
3833 assert!(!table_sizes.is_empty());
3834
3835 let migration_status = client
3838 .get::<Vec<data_source::MigrationStatus>>("database/migration-status")
3839 .send()
3840 .await
3841 .unwrap();
3842 assert!(migration_status.iter().all(|m| !m.name.is_empty()));
3843 }
3844
3845 async fn run_catchup_test(url_suffix: &str) {
3846 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3848 const NUM_NODES: usize = 5;
3849
3850 let url: url::Url = format!("http://localhost:{port}{url_suffix}")
3851 .parse()
3852 .unwrap();
3853
3854 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
3855 .api_config(Options::with_port(port))
3856 .network_config(TestConfigBuilder::default().build())
3857 .catchups(std::array::from_fn(|_| {
3858 StatePeers::<StaticVersion<0, 1>>::from_urls(
3859 vec![url.clone()],
3860 Default::default(),
3861 Duration::from_secs(2),
3862 &NoMetrics,
3863 )
3864 }))
3865 .build();
3866 let mut network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3867
3868 let mut events = network.peers[0].event_stream();
3870 loop {
3871 let event = events.next().await.unwrap();
3872 let CoordinatorEvent::LegacyEvent(Event {
3873 event: EventType::Decide { leaf_chain, .. },
3874 ..
3875 }) = event
3876 else {
3877 continue;
3878 };
3879 if leaf_chain[0].leaf.height() > 0 {
3880 break;
3881 }
3882 }
3883
3884 tracing::info!("shutting down node");
3889 network.peers.remove(0);
3890
3891 network
3893 .server
3894 .event_stream()
3895 .filter(|event| {
3896 future::ready(matches!(
3897 event,
3898 CoordinatorEvent::LegacyEvent(Event {
3899 event: EventType::Decide { .. },
3900 ..
3901 })
3902 ))
3903 })
3904 .take(3)
3905 .collect::<Vec<_>>()
3906 .await;
3907
3908 tracing::info!("restarting node");
3909 let node = network
3910 .cfg
3911 .init_node(
3912 1,
3913 ValidatedState::default(),
3914 no_storage::Options,
3915 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
3916 vec![url],
3917 Default::default(),
3918 Duration::from_secs(2),
3919 &NoMetrics,
3920 )),
3921 None,
3922 &NoMetrics,
3923 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
3924 NullEventConsumer,
3925 MOCK_SEQUENCER_VERSIONS,
3926 Default::default(),
3927 )
3928 .await;
3929 let mut events = node.event_stream();
3930
3931 let mut proposers = [false; NUM_NODES];
3934 loop {
3935 let event = events.next().await.unwrap();
3936 let CoordinatorEvent::LegacyEvent(Event {
3937 event: EventType::Decide { leaf_chain, .. },
3938 ..
3939 }) = event
3940 else {
3941 continue;
3942 };
3943 for LeafInfo { leaf, .. } in leaf_chain.iter().rev() {
3944 let height = leaf.height();
3945 let leaf_builder = (leaf.view_number().u64() as usize) % NUM_NODES;
3946 if height == 0 {
3947 continue;
3948 }
3949
3950 tracing::info!(
3951 "waiting for blocks from {proposers:?}, block {height} is from {leaf_builder}",
3952 );
3953 proposers[leaf_builder] = true;
3954 }
3955
3956 if proposers.iter().all(|has_proposed| *has_proposed) {
3957 break;
3958 }
3959 }
3960 }
3961
3962 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3963 async fn test_catchup() {
3964 run_catchup_test("").await;
3965 }
3966
3967 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3968 async fn test_catchup_v0() {
3969 run_catchup_test("/v0").await;
3970 }
3971
3972 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3973 async fn test_catchup_v1() {
3974 run_catchup_test("/v1").await;
3975 }
3976
3977 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3978 async fn test_catchup_no_state_peers() {
3979 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
3981 const NUM_NODES: usize = 5;
3982 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
3983 .api_config(Options::with_port(port))
3984 .network_config(TestConfigBuilder::default().build())
3985 .build();
3986 let mut network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
3987
3988 let mut events = network.peers[0].event_stream();
3990 loop {
3991 let event = events.next().await.unwrap();
3992 let CoordinatorEvent::LegacyEvent(Event {
3993 event: EventType::Decide { leaf_chain, .. },
3994 ..
3995 }) = event
3996 else {
3997 continue;
3998 };
3999 if leaf_chain[0].leaf.height() > 0 {
4000 break;
4001 }
4002 }
4003
4004 tracing::info!("shutting down node");
4009 network.peers.remove(0);
4010
4011 network
4013 .server
4014 .event_stream()
4015 .filter(|event| {
4016 future::ready(matches!(
4017 event,
4018 CoordinatorEvent::LegacyEvent(Event {
4019 event: EventType::Decide { .. },
4020 ..
4021 })
4022 ))
4023 })
4024 .take(3)
4025 .collect::<Vec<_>>()
4026 .await;
4027
4028 tracing::info!("restarting node");
4029 let node = network
4030 .cfg
4031 .init_node(
4032 1,
4033 ValidatedState::default(),
4034 no_storage::Options,
4035 None::<NullStateCatchup>,
4036 None,
4037 &NoMetrics,
4038 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
4039 NullEventConsumer,
4040 MOCK_SEQUENCER_VERSIONS,
4041 Default::default(),
4042 )
4043 .await;
4044 let mut events = node.event_stream();
4045
4046 let mut proposers = [false; NUM_NODES];
4049 loop {
4050 let event = events.next().await.unwrap();
4051 let CoordinatorEvent::LegacyEvent(Event {
4052 event: EventType::Decide { leaf_chain, .. },
4053 ..
4054 }) = event
4055 else {
4056 continue;
4057 };
4058 for LeafInfo { leaf, .. } in leaf_chain.iter().rev() {
4059 let height = leaf.height();
4060 let leaf_builder = (leaf.view_number().u64() as usize) % NUM_NODES;
4061 if height == 0 {
4062 continue;
4063 }
4064
4065 tracing::info!(
4066 "waiting for blocks from {proposers:?}, block {height} is from {leaf_builder}",
4067 );
4068 proposers[leaf_builder] = true;
4069 }
4070
4071 if proposers.iter().all(|has_proposed| *has_proposed) {
4072 break;
4073 }
4074 }
4075 }
4076
4077 #[ignore]
4078 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4079 async fn test_catchup_epochs_no_state_peers() {
4080 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4082 const EPOCH_HEIGHT: u64 = 5;
4083 let network_config = TestConfigBuilder::default()
4084 .epoch_height(EPOCH_HEIGHT)
4085 .build();
4086 const NUM_NODES: usize = 5;
4087 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
4088 .api_config(Options::with_port(port))
4089 .network_config(network_config)
4090 .build();
4091 let mut network = TestNetwork::new(config, Upgrade::trivial(EPOCH_VERSION)).await;
4092
4093 let mut events = network.peers[0].event_stream();
4095 loop {
4096 let event = events.next().await.unwrap();
4097 let CoordinatorEvent::LegacyEvent(Event {
4098 event: EventType::Decide { leaf_chain, .. },
4099 ..
4100 }) = event
4101 else {
4102 continue;
4103 };
4104 tracing::error!("got decide height {}", leaf_chain[0].leaf.height());
4105
4106 if leaf_chain[0].leaf.height() > EPOCH_HEIGHT * 3 {
4107 tracing::error!("decided past one epoch");
4108 break;
4109 }
4110 }
4111
4112 tracing::info!("shutting down node");
4117 network.peers.remove(0);
4118
4119 network
4121 .server
4122 .event_stream()
4123 .filter(|event| {
4124 future::ready(matches!(
4125 event,
4126 CoordinatorEvent::LegacyEvent(Event {
4127 event: EventType::Decide { .. },
4128 ..
4129 })
4130 ))
4131 })
4132 .take(3)
4133 .collect::<Vec<_>>()
4134 .await;
4135
4136 tracing::error!("restarting node");
4137 let node = network
4138 .cfg
4139 .init_node(
4140 1,
4141 ValidatedState::default(),
4142 no_storage::Options,
4143 None::<NullStateCatchup>,
4144 None,
4145 &NoMetrics,
4146 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
4147 NullEventConsumer,
4148 MOCK_SEQUENCER_VERSIONS,
4149 Default::default(),
4150 )
4151 .await;
4152 let mut events = node.event_stream();
4153
4154 let mut proposers = [false; NUM_NODES];
4157 loop {
4158 let event = events.next().await.unwrap();
4159 let CoordinatorEvent::LegacyEvent(Event {
4160 event: EventType::Decide { leaf_chain, .. },
4161 ..
4162 }) = event
4163 else {
4164 continue;
4165 };
4166 for LeafInfo { leaf, .. } in leaf_chain.iter().rev() {
4167 let height = leaf.height();
4168 let leaf_builder = (leaf.view_number().u64() as usize) % NUM_NODES;
4169 if height == 0 {
4170 continue;
4171 }
4172
4173 tracing::info!(
4174 "waiting for blocks from {proposers:?}, block {height} is from {leaf_builder}",
4175 );
4176 proposers[leaf_builder] = true;
4177 }
4178
4179 if proposers.iter().all(|has_proposed| *has_proposed) {
4180 break;
4181 }
4182 }
4183 }
4184
4185 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4186 async fn test_chain_config_from_instance() {
4187 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4193
4194 let chain_config: ChainConfig = ChainConfig::default();
4195
4196 let state = ValidatedState {
4197 chain_config: chain_config.commit().into(),
4198 ..Default::default()
4199 };
4200
4201 let states = std::array::from_fn(|_| state.clone());
4202
4203 let config = TestNetworkConfigBuilder::default()
4204 .api_config(Options::with_port(port))
4205 .states(states)
4206 .catchups(std::array::from_fn(|_| {
4207 StatePeers::<StaticVersion<0, 1>>::from_urls(
4208 vec![format!("http://localhost:{port}").parse().unwrap()],
4209 Default::default(),
4210 Duration::from_secs(2),
4211 &NoMetrics,
4212 )
4213 }))
4214 .network_config(TestConfigBuilder::default().build())
4215 .build();
4216
4217 let mut network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4218
4219 network
4221 .server
4222 .event_stream()
4223 .filter(|event| {
4224 future::ready(matches!(
4225 event,
4226 CoordinatorEvent::LegacyEvent(Event {
4227 event: EventType::Decide { .. },
4228 ..
4229 })
4230 ))
4231 })
4232 .take(3)
4233 .collect::<Vec<_>>()
4234 .await;
4235
4236 for peer in &network.peers {
4237 let state = peer.consensus_handle().decided_state().await.unwrap();
4238
4239 assert_eq!(state.chain_config.resolve().unwrap(), chain_config)
4240 }
4241
4242 network.server.shut_down().await;
4243 drop(network);
4244 }
4245
4246 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4247 async fn test_chain_config_catchup() {
4248 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4254
4255 let cf = ChainConfig {
4256 max_block_size: 300.into(),
4257 base_fee: 1.into(),
4258 ..Default::default()
4259 };
4260
4261 let state1 = ValidatedState {
4263 chain_config: cf.commit().into(),
4264 ..Default::default()
4265 };
4266
4267 let state2 = ValidatedState {
4269 chain_config: cf.into(),
4270 ..Default::default()
4271 };
4272
4273 let mut states = std::array::from_fn(|_| state1.clone());
4274 states[0] = state2;
4277
4278 const NUM_NODES: usize = 5;
4279 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
4280 .api_config(Options::from(options::Http {
4281 port,
4282 max_connections: None,
4283 tonic_port: None,
4284 }))
4285 .states(states)
4286 .catchups(std::array::from_fn(|_| {
4287 StatePeers::<StaticVersion<0, 1>>::from_urls(
4288 vec![format!("http://localhost:{port}").parse().unwrap()],
4289 Default::default(),
4290 Duration::from_secs(2),
4291 &NoMetrics,
4292 )
4293 }))
4294 .network_config(TestConfigBuilder::default().build())
4295 .build();
4296
4297 let mut network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4298
4299 network
4301 .server
4302 .event_stream()
4303 .filter(|event| {
4304 future::ready(matches!(
4305 event,
4306 CoordinatorEvent::LegacyEvent(Event {
4307 event: EventType::Decide { .. },
4308 ..
4309 })
4310 ))
4311 })
4312 .take(3)
4313 .collect::<Vec<_>>()
4314 .await;
4315
4316 for peer in &network.peers {
4317 let state = peer.consensus_handle().decided_state().await.unwrap();
4318
4319 assert_eq!(state.chain_config.resolve().unwrap(), cf)
4320 }
4321
4322 network.server.shut_down().await;
4323 drop(network);
4324 }
4325
4326 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4327 async fn test_pos_upgrade_view_based() {
4328 test_upgrade_helper(Upgrade::new(FEE_VERSION, EPOCH_VERSION)).await;
4329 }
4330
4331 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4332 async fn test_epoch_reward_upgrade() {
4333 test_upgrade_helper_with_nodes::<3>(
4336 Upgrade::new(
4337 versions::DRB_AND_HEADER_UPGRADE_VERSION,
4338 versions::EPOCH_REWARD_VERSION,
4339 ),
4340 100,
4341 )
4342 .await;
4343 }
4344
4345 async fn test_upgrade_helper(upgrade: Upgrade) {
4346 test_upgrade_helper_with_nodes::<5>(upgrade, 200).await;
4347 }
4348
4349 async fn test_upgrade_helper_with_nodes<const NUM_NODES: usize>(
4350 upgrade: Upgrade,
4351 start_proposing_view: u64,
4352 ) {
4353 let wait_extra_views = 10;
4356 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4357 let epoch_start_block = if upgrade.base >= versions::EPOCH_VERSION {
4358 0
4359 } else {
4360 321
4361 };
4362
4363 let test_config = TestConfigBuilder::default()
4364 .epoch_height(200)
4365 .epoch_start_block(epoch_start_block)
4366 .set_upgrades(upgrade.target)
4367 .await
4368 .upgrade_proposing_views(start_proposing_view, 1000)
4369 .build();
4370
4371 let chain_config_genesis = ValidatedState::default().chain_config.resolve().unwrap();
4372 let chain_config_upgrade = test_config.get_upgrade_map().chain_config(upgrade.target);
4373 assert_ne!(chain_config_genesis, chain_config_upgrade);
4374 tracing::debug!(?chain_config_genesis, ?chain_config_upgrade);
4375
4376 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
4377 let persistence: [_; NUM_NODES] = storage
4378 .iter()
4379 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
4380 .collect::<Vec<_>>()
4381 .try_into()
4382 .unwrap();
4383
4384 let mut builder = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
4385 .api_config(SqlDataSource::options(
4386 &storage[0],
4387 Options::with_port(port),
4388 ))
4389 .persistences(persistence)
4390 .catchups(std::array::from_fn(|_| {
4391 StatePeers::<SequencerApiVersion>::from_urls(
4392 vec![format!("http://localhost:{port}").parse().unwrap()],
4393 Default::default(),
4394 Duration::from_secs(2),
4395 &NoMetrics,
4396 )
4397 }))
4398 .network_config(test_config);
4399
4400 if upgrade.base >= versions::EPOCH_VERSION {
4403 let state = ValidatedState {
4404 chain_config: chain_config_upgrade.into(),
4405 ..Default::default()
4406 };
4407 builder = builder.states(std::array::from_fn(|_| state.clone()));
4408 }
4409
4410 let config = builder.build();
4411
4412 let mut network = TestNetwork::new(config, upgrade).await;
4413 let _events = network.server.event_stream();
4414
4415 let target = upgrade.target;
4416
4417 let mut hotshot_events = network
4424 .server
4425 .consensus_handle()
4426 .legacy_consensus()
4427 .read()
4428 .await
4429 .event_stream();
4430 let upgrade = loop {
4431 let event = hotshot_events.next().await.unwrap();
4432 if let EventType::UpgradeProposal { proposal, .. } = event.event {
4433 tracing::info!(?proposal, "proposal");
4434 let upgrade = proposal.data.upgrade_proposal;
4435 let new_version = upgrade.new_version;
4436 tracing::info!(?new_version, "upgrade proposal new version");
4437 assert_eq!(new_version, target);
4438 break upgrade;
4439 }
4440 };
4441
4442 let wanted_view = upgrade.new_version_first_view + wait_extra_views;
4443 loop {
4445 let event = hotshot_events.next().await.unwrap();
4446 let view_number = event.view_number;
4447
4448 tracing::debug!(?view_number, ?upgrade.new_version_first_view, "upgrade_new_view");
4449 if view_number > wanted_view {
4450 tracing::info!(?view_number, ?upgrade.new_version_first_view, "passed upgrade view");
4451 let states =
4452 join_all(network.peers.iter().map(|peer| async {
4453 peer.consensus_handle().decided_state().await.unwrap()
4454 }))
4455 .await;
4456 let leaves = join_all(
4457 network
4458 .peers
4459 .iter()
4460 .map(|peer| async { peer.consensus_handle().decided_leaf().await }),
4461 )
4462 .await;
4463 let configs: Vec<ChainConfig> = states
4464 .iter()
4465 .map(|state| state.chain_config.resolve().unwrap())
4466 .collect();
4467
4468 tracing::info!(?leaves, ?configs, "post upgrade state");
4469 for config in configs {
4470 assert_eq!(config, chain_config_upgrade);
4471 }
4472 for leaf in leaves {
4473 assert_eq!(leaf.block_header().version(), target);
4474 }
4475 break;
4476 }
4477 sleep(Duration::from_millis(200)).await;
4478 }
4479
4480 network.server.shut_down().await;
4481 }
4482
4483 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4484 pub(crate) async fn test_restart() {
4485 const NUM_NODES: usize = 5;
4486 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
4488 let persistence: [_; NUM_NODES] = storage
4489 .iter()
4490 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
4491 .collect::<Vec<_>>()
4492 .try_into()
4493 .unwrap();
4494 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4495 let config = TestNetworkConfigBuilder::default()
4496 .api_config(SqlDataSource::options(
4497 &storage[0],
4498 Options::with_port(port),
4499 ))
4500 .persistences(persistence.clone())
4501 .network_config(TestConfigBuilder::default().build())
4502 .build();
4503 let mut network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4504
4505 let client: Client<ClientErr, SequencerApiVersion> =
4507 Client::new(format!("http://localhost:{port}").parse().unwrap());
4508 client.connect(None).await;
4509 tracing::info!(port, "server running");
4510
4511 client
4513 .socket("availability/stream/blocks/0")
4514 .subscribe::<BlockQueryData<SeqTypes>>()
4515 .await
4516 .unwrap()
4517 .take(3)
4518 .collect::<Vec<_>>()
4519 .await;
4520
4521 tracing::info!("shutting down nodes");
4523 network.stop_consensus().await;
4524
4525 let height = client
4527 .get::<usize>("status/block-height")
4528 .send()
4529 .await
4530 .unwrap();
4531 tracing::info!("decided {height} blocks before shutting down");
4532
4533 let chain: Vec<LeafQueryData<SeqTypes>> = client
4535 .socket("availability/stream/leaves/0")
4536 .subscribe()
4537 .await
4538 .unwrap()
4539 .take(height)
4540 .try_collect()
4541 .await
4542 .unwrap();
4543 let decided_view = chain.last().unwrap().leaf().view_number();
4544
4545 let state = network.server.decided_state().await.unwrap();
4548 tracing::info!(?decided_view, ?state, "consensus state");
4549
4550 drop(network);
4552
4553 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4555
4556 let config = TestNetworkConfigBuilder::default()
4557 .api_config(SqlDataSource::options(
4558 &storage[0],
4559 Options::with_port(port),
4560 ))
4561 .persistences(persistence)
4562 .catchups(std::array::from_fn(|_| {
4563 StatePeers::<StaticVersion<0, 1>>::from_urls(
4567 vec![format!("http://localhost:{port}").parse().unwrap()],
4568 Default::default(),
4569 Duration::from_secs(2),
4570 &NoMetrics,
4571 )
4572 }))
4573 .network_config(TestConfigBuilder::default().build())
4574 .build();
4575 let _network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4576 let client: Client<ClientErr, StaticVersion<0, 1>> =
4577 Client::new(format!("http://localhost:{port}").parse().unwrap());
4578 client.connect(None).await;
4579 tracing::info!(port, "server running");
4580
4581 tracing::info!("waiting for decide, height {height}");
4583 let new_leaf: LeafQueryData<SeqTypes> = client
4584 .socket(&format!("availability/stream/leaves/{height}"))
4585 .subscribe()
4586 .await
4587 .unwrap()
4588 .next()
4589 .await
4590 .unwrap()
4591 .unwrap();
4592 assert_eq!(new_leaf.height(), height as u64);
4593 assert_eq!(
4594 new_leaf.leaf().parent_commitment(),
4595 chain[height - 1].hash()
4596 );
4597
4598 let new_chain: Vec<LeafQueryData<SeqTypes>> = client
4600 .socket("availability/stream/leaves/0")
4601 .subscribe()
4602 .await
4603 .unwrap()
4604 .take(height)
4605 .try_collect()
4606 .await
4607 .unwrap();
4608 assert_eq!(chain, new_chain);
4609 }
4610
4611 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4612 async fn test_fetch_config() {
4613 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4614 let url: Url = format!("http://localhost:{port}").parse().unwrap();
4615 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url.clone());
4616
4617 let options = Options::with_port(port).config(Default::default());
4618 let network_config = TestConfigBuilder::default().build();
4619 let config = TestNetworkConfigBuilder::default()
4620 .api_config(options)
4621 .network_config(network_config)
4622 .build();
4623 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4624 client.connect(None).await;
4625
4626 let peers = StatePeers::<StaticVersion<0, 1>>::from_urls(
4629 vec!["https://notarealnode.network".parse().unwrap(), url],
4630 Default::default(),
4631 Duration::from_secs(2),
4632 &NoMetrics,
4633 );
4634
4635 let validator =
4637 ValidatorConfig::generated_from_seed_indexed([0; 32], 1, U256::from(1), false);
4638 let config = peers.fetch_config(validator.clone()).await.unwrap();
4639
4640 assert_eq!(config.node_index, 1);
4642
4643 pretty_assertions::assert_eq!(
4646 serde_json::to_value(PublicHotShotConfig::from(config.config)).unwrap(),
4647 serde_json::to_value(PublicHotShotConfig::from(
4648 network.cfg.hotshot_config().clone()
4649 ))
4650 .unwrap()
4651 );
4652 }
4653
4654 async fn run_hotshot_event_streaming_test(url_suffix: &str) {
4655 let query_service_port =
4656 reserve_tcp_port().expect("OS should have ephemeral ports available");
4657
4658 let url = format!("http://localhost:{query_service_port}{url_suffix}")
4659 .parse()
4660 .unwrap();
4661
4662 let client: Client<ClientErr, SequencerApiVersion> = Client::new(url);
4663
4664 let options = Options::with_port(query_service_port).hotshot_events(HotshotEvents);
4665
4666 let network_config = TestConfigBuilder::default().build();
4667 let config = TestNetworkConfigBuilder::default()
4668 .api_config(options)
4669 .network_config(network_config)
4670 .build();
4671 let _network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
4672
4673 let mut subscribed_events = client
4674 .socket("hotshot-events/events")
4675 .subscribe::<Event<SeqTypes>>()
4676 .await
4677 .unwrap();
4678
4679 let total_count = 5;
4680 let mut receive_count = 0;
4682 loop {
4683 let event = subscribed_events.next().await.unwrap();
4684 tracing::info!("Received event in hotshot event streaming Client 1: {event:?}");
4685 receive_count += 1;
4686 if receive_count > total_count {
4687 tracing::info!("Client Received at least desired events, exiting loop");
4688 break;
4689 }
4690 }
4691 assert_eq!(receive_count, total_count + 1);
4692 }
4693
4694 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4695 async fn test_hotshot_event_streaming_v0() {
4696 run_hotshot_event_streaming_test("/v0").await;
4697 }
4698
4699 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4700 async fn test_hotshot_event_streaming_v1() {
4701 run_hotshot_event_streaming_test("/v1").await;
4702 }
4703
4704 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4705 async fn test_hotshot_event_streaming() {
4706 run_hotshot_event_streaming_test("").await;
4707 }
4708
4709 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4712 async fn test_hotshot_event_streaming_epoch_progression() {
4713 let epoch_height = 35;
4714 let wanted_epochs = 4;
4715
4716 let network_config = TestConfigBuilder::default()
4717 .epoch_height(epoch_height)
4718 .build();
4719
4720 let query_service_port =
4721 reserve_tcp_port().expect("OS should have ephemeral ports available");
4722
4723 let hotshot_url = format!("http://localhost:{query_service_port}")
4724 .parse()
4725 .unwrap();
4726
4727 let client: Client<ClientErr, SequencerApiVersion> = Client::new(hotshot_url);
4728 let options = Options::with_port(query_service_port).hotshot_events(HotshotEvents);
4729
4730 let config = TestNetworkConfigBuilder::default()
4731 .api_config(options)
4732 .network_config(network_config.clone())
4733 .pos_hook(
4734 DelegationConfig::VariableAmounts,
4735 Default::default(),
4736 POS_V3,
4737 )
4738 .await
4739 .expect("Pos Deployment")
4740 .build();
4741
4742 let _network = TestNetwork::new(config, POS_V3).await;
4743
4744 let mut subscribed_events = client
4745 .socket("hotshot-events/events")
4746 .subscribe::<Event<SeqTypes>>()
4747 .await
4748 .unwrap();
4749
4750 let wanted_views = epoch_height * wanted_epochs;
4751
4752 let mut views = HashSet::new();
4753 let mut epochs = HashSet::new();
4754 for _ in 0..=600 {
4755 let event = subscribed_events.next().await.unwrap();
4756 let event = event.unwrap();
4757 let view_number = event.view_number;
4758 views.insert(view_number.u64());
4759
4760 if let hotshot::types::EventType::Decide { committing_qc, .. } = event.event {
4761 assert!(committing_qc.epoch().is_some(), "epochs are live");
4762 assert!(committing_qc.block_number().is_some());
4763
4764 let epoch = committing_qc.epoch().unwrap().u64();
4765 epochs.insert(epoch);
4766
4767 tracing::debug!(
4768 "Got decide: epoch: {:?}, block: {:?} ",
4769 epoch,
4770 committing_qc.block_number()
4771 );
4772
4773 let expected_epoch =
4774 epoch_from_block_number(committing_qc.block_number().unwrap(), epoch_height);
4775 tracing::debug!("expected epoch: {expected_epoch}, qc epoch: {epoch}");
4776
4777 assert_eq!(expected_epoch, epoch);
4778 }
4779 if views.contains(&wanted_views) {
4780 tracing::info!("Client Received at least desired views, exiting loop");
4781 break;
4782 }
4783 }
4784
4785 assert!(views.contains(&wanted_views), "Views are not progressing");
4787 assert!(
4788 epochs.contains(&wanted_epochs),
4789 "Epochs are not progressing"
4790 );
4791 }
4792
4793 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4794 async fn test_pos_rewards_basic() -> anyhow::Result<()> {
4795 let epoch_height = 20;
4802
4803 let network_config = TestConfigBuilder::default()
4804 .epoch_height(epoch_height)
4805 .build();
4806
4807 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4808
4809 const NUM_NODES: usize = 1;
4810 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
4812 let persistence: [_; NUM_NODES] = storage
4813 .iter()
4814 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
4815 .collect::<Vec<_>>()
4816 .try_into()
4817 .unwrap();
4818
4819 let config = TestNetworkConfigBuilder::with_num_nodes()
4820 .api_config(SqlDataSource::options(
4821 &storage[0],
4822 Options::with_port(api_port),
4823 ))
4824 .network_config(network_config.clone())
4825 .persistences(persistence.clone())
4826 .catchups(std::array::from_fn(|_| {
4827 StatePeers::<StaticVersion<0, 1>>::from_urls(
4828 vec![format!("http://localhost:{api_port}").parse().unwrap()],
4829 Default::default(),
4830 Duration::from_secs(2),
4831 &NoMetrics,
4832 )
4833 }))
4834 .pos_hook(
4835 DelegationConfig::VariableAmounts,
4836 Default::default(),
4837 POS_V4,
4838 )
4839 .await
4840 .unwrap()
4841 .build();
4842
4843 let network = TestNetwork::new(config, POS_V4).await;
4844 let client: Client<ClientErr, SequencerApiVersion> =
4845 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
4846
4847 let _blocks = client
4852 .socket("availability/stream/blocks/0")
4853 .subscribe::<BlockQueryData<SeqTypes>>()
4854 .await
4855 .unwrap()
4856 .take(65)
4857 .try_collect::<Vec<_>>()
4858 .await
4859 .unwrap();
4860
4861 let staking_priv_keys = network_config.staking_priv_keys();
4862 let account = staking_priv_keys[0].signer.clone();
4863 let address = account.address();
4864
4865 let block_height = 60;
4866
4867 let node_state = network.server.node_state();
4868 let membership = node_state.coordinator.membership();
4869 let expected_amount = U256::from(20)
4870 * (membership
4871 .epoch_block_reward(3.into())
4872 .expect("block reward is not None"))
4873 .0;
4874
4875 let amount = client
4877 .get::<Option<RewardAmount>>(&format!(
4878 "reward-state/reward-balance/{block_height}/{address}"
4879 ))
4880 .send()
4881 .await
4882 .unwrap()
4883 .unwrap();
4884
4885 tracing::info!("amount={amount:?}");
4886
4887 assert_eq!(amount.0, expected_amount, "reward amount don't match");
4888
4889 Ok(())
4890 }
4891
4892 #[test_log::test(tokio::test(flavor = "multi_thread"))]
4893 async fn test_cumulative_pos_rewards() -> anyhow::Result<()> {
4894 let epoch_height = 20;
4900
4901 let network_config = TestConfigBuilder::default()
4902 .epoch_height(epoch_height)
4903 .build();
4904
4905 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
4906
4907 const NUM_NODES: usize = 5;
4908 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
4910 let persistence: [_; NUM_NODES] = storage
4911 .iter()
4912 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
4913 .collect::<Vec<_>>()
4914 .try_into()
4915 .unwrap();
4916
4917 let config = TestNetworkConfigBuilder::with_num_nodes()
4918 .api_config(SqlDataSource::options(
4919 &storage[0],
4920 Options::with_port(api_port),
4921 ))
4922 .network_config(network_config)
4923 .persistences(persistence.clone())
4924 .catchups(std::array::from_fn(|_| {
4925 StatePeers::<StaticVersion<0, 1>>::from_urls(
4926 vec![format!("http://localhost:{api_port}").parse().unwrap()],
4927 Default::default(),
4928 Duration::from_secs(2),
4929 &NoMetrics,
4930 )
4931 }))
4932 .pos_hook(
4933 DelegationConfig::MultipleDelegators,
4934 Default::default(),
4935 POS_V4,
4936 )
4937 .await
4938 .unwrap()
4939 .build();
4940
4941 let network = TestNetwork::new(config, POS_V4).await;
4942 let node_state = network.server.node_state();
4943 let client: Client<ClientErr, SequencerApiVersion> =
4944 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
4945
4946 let _blocks = client
4948 .socket("availability/stream/blocks/0")
4949 .subscribe::<BlockQueryData<SeqTypes>>()
4950 .await
4951 .unwrap()
4952 .take(75)
4953 .try_collect::<Vec<_>>()
4954 .await
4955 .unwrap();
4956
4957 let validators = client
4961 .get::<AuthenticatedValidatorMap>("node/validators/3")
4962 .send()
4963 .await
4964 .expect("failed to get validator");
4965
4966 let mut addresses = HashSet::new();
4970 for v in validators.values() {
4971 addresses.insert(v.account);
4972 addresses.extend(v.clone().delegators.keys().collect::<Vec<_>>());
4973 }
4974 let validators = client
4976 .get::<AuthenticatedValidatorMap>("node/validators/4")
4977 .send()
4978 .await
4979 .expect("failed to get validator");
4980 for v in validators.values() {
4981 addresses.insert(v.account);
4982 addresses.extend(v.clone().delegators.keys().collect::<Vec<_>>());
4983 }
4984
4985 let mut prev_cumulative_amount = U256::ZERO;
4986 for block in 41..=67 {
4988 let membership = node_state.coordinator.membership();
4989 let block_reward = membership
4990 .epoch_block_reward(epoch_from_block_number(block, epoch_height).into())
4991 .expect("block reward is not None");
4992
4993 let mut cumulative_amount = U256::ZERO;
4994 for address in addresses.clone() {
4995 let amount = client
4996 .get::<Option<RewardAmount>>(&format!(
4997 "reward-state/reward-balance/{block}/{address}"
4998 ))
4999 .send()
5000 .await
5001 .ok()
5002 .flatten();
5003
5004 if let Some(amount) = amount {
5005 tracing::info!("address={address}, amount={amount}");
5006 cumulative_amount += amount.0;
5007 };
5008 }
5009
5010 assert_eq!(cumulative_amount - prev_cumulative_amount, block_reward.0);
5012 tracing::info!("cumulative_amount is correct for block={block}");
5013 prev_cumulative_amount = cumulative_amount;
5014 }
5015
5016 Ok(())
5017 }
5018
5019 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5020 async fn test_stake_table_duplicate_events_from_contract() -> anyhow::Result<()> {
5021 let epoch_height = 20;
5025
5026 let network_config = TestConfigBuilder::default()
5027 .epoch_height(epoch_height)
5028 .build();
5029
5030 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
5031
5032 const NUM_NODES: usize = 5;
5033 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5035 let persistence: [_; NUM_NODES] = storage
5036 .iter()
5037 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5038 .collect::<Vec<_>>()
5039 .try_into()
5040 .unwrap();
5041
5042 let l1_url = network_config.l1_url();
5043 let config = TestNetworkConfigBuilder::with_num_nodes()
5044 .api_config(SqlDataSource::options(
5045 &storage[0],
5046 Options::with_port(api_port),
5047 ))
5048 .network_config(network_config)
5049 .persistences(persistence.clone())
5050 .catchups(std::array::from_fn(|_| {
5051 StatePeers::<StaticVersion<0, 1>>::from_urls(
5052 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5053 Default::default(),
5054 Duration::from_secs(2),
5055 &NoMetrics,
5056 )
5057 }))
5058 .pos_hook(
5059 DelegationConfig::MultipleDelegators,
5060 Default::default(),
5061 POS_V3,
5062 )
5063 .await
5064 .unwrap()
5065 .build();
5066
5067 let network = TestNetwork::new(config, POS_V3).await;
5068
5069 let mut prev_st = None;
5070 let state = network.server.decided_state().await.unwrap();
5071 let chain_config = state.chain_config.resolve().expect("resolve chain config");
5072 let stake_table = chain_config.stake_table_contract.unwrap();
5073
5074 let l1_client = L1ClientOptions::default()
5075 .connect(vec![l1_url])
5076 .expect("failed to connect to l1");
5077
5078 let client: Client<ClientErr, SequencerApiVersion> =
5079 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5080
5081 let mut headers = client
5082 .socket("availability/stream/headers/0")
5083 .subscribe::<Header>()
5084 .await
5085 .unwrap();
5086
5087 let mut target_bh = 0;
5088 while let Some(header) = headers.next().await {
5089 let header = header.unwrap();
5090 println!("got header with height {}", header.height());
5091 if header.height() == 0 {
5092 continue;
5093 }
5094 let l1_block = header.l1_finalized().expect("l1 block not found");
5095
5096 let sorted_events = Fetcher::fetch_events_from_contract(
5097 l1_client.clone(),
5098 stake_table,
5099 None,
5100 l1_block.number(),
5101 )
5102 .await?;
5103
5104 let mut sorted_dedup_removed = sorted_events.clone();
5105 sorted_dedup_removed.dedup();
5106
5107 assert_eq!(
5108 sorted_events.len(),
5109 sorted_dedup_removed.len(),
5110 "duplicates found"
5111 );
5112
5113 let stake_table =
5115 validators_from_l1_events(sorted_events.into_iter().map(|(_, e)| e)).unwrap();
5116 if let Some(prev_st) = prev_st {
5117 assert_eq!(stake_table, prev_st);
5118 }
5119
5120 prev_st = Some(stake_table);
5121
5122 if target_bh == 100 {
5123 break;
5124 }
5125
5126 target_bh = header.height();
5127 }
5128
5129 Ok(())
5130 }
5131
5132 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5133 async fn test_rewards_v4() -> anyhow::Result<()> {
5134 const EPOCH_HEIGHT: u64 = 20;
5144
5145 let network_config = TestConfigBuilder::default()
5146 .epoch_height(EPOCH_HEIGHT)
5147 .build();
5148
5149 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
5150
5151 const NUM_NODES: usize = 5;
5152
5153 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5154 let persistence: [_; NUM_NODES] = storage
5155 .iter()
5156 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5157 .collect::<Vec<_>>()
5158 .try_into()
5159 .unwrap();
5160
5161 let config = TestNetworkConfigBuilder::with_num_nodes()
5162 .api_config(SqlDataSource::options(
5163 &storage[0],
5164 Options::with_port(api_port),
5165 ))
5166 .network_config(network_config)
5167 .persistences(persistence.clone())
5168 .catchups(std::array::from_fn(|_| {
5169 StatePeers::<StaticVersion<0, 1>>::from_urls(
5170 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5171 Default::default(),
5172 Duration::from_secs(2),
5173 &NoMetrics,
5174 )
5175 }))
5176 .pos_hook(
5177 DelegationConfig::MultipleDelegators,
5178 Default::default(),
5179 POS_V4,
5180 )
5181 .await
5182 .unwrap()
5183 .build();
5184
5185 let network = TestNetwork::new(config, POS_V4).await;
5186 let client: Client<ClientErr, SequencerApiVersion> =
5187 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5188
5189 let mut events = network.peers[0].event_stream();
5191 while let Some(event) = events.next().await {
5192 if let CoordinatorEvent::LegacyEvent(Event {
5193 event: EventType::Decide { leaf_chain, .. },
5194 ..
5195 }) = event
5196 {
5197 let height = leaf_chain[0].leaf.height();
5198 tracing::info!("Node 0 decided at height: {height}");
5199 if height > EPOCH_HEIGHT * 3 {
5200 break;
5201 }
5202 }
5203 }
5204
5205 {
5207 client
5208 .get::<AuthenticatedValidatorMap>("node/validators/1")
5209 .send()
5210 .await
5211 .unwrap()
5212 .is_empty();
5213
5214 client
5215 .get::<AuthenticatedValidatorMap>("node/validators/2")
5216 .send()
5217 .await
5218 .unwrap()
5219 .is_empty();
5220 }
5221
5222 let validators = client
5224 .get::<AuthenticatedValidatorMap>("node/validators/3")
5225 .send()
5226 .await
5227 .expect("validators");
5228
5229 assert!(!validators.is_empty());
5230
5231 let mut addresses = HashSet::new();
5233 for v in validators.values() {
5234 addresses.insert(v.account);
5235 addresses.extend(v.clone().delegators.keys().collect::<Vec<_>>());
5236 }
5237
5238 let mut leaves = client
5239 .socket("availability/stream/leaves/0")
5240 .subscribe::<LeafQueryData<SeqTypes>>()
5241 .await
5242 .unwrap();
5243
5244 let node_state = network.server.node_state();
5245 let coordinator = node_state.coordinator;
5246
5247 let membership = coordinator.membership();
5248
5249 while let Some(leaf) = leaves.next().await {
5251 let leaf = leaf.unwrap();
5252 let header = leaf.header();
5253 assert_eq!(header.total_reward_distributed().unwrap().0, U256::ZERO);
5254
5255 let epoch_number =
5256 EpochNumber::new(epoch_from_block_number(leaf.height(), EPOCH_HEIGHT));
5257
5258 assert!(membership.epoch_block_reward(epoch_number).is_none());
5259
5260 let height = header.height();
5261 for address in addresses.clone() {
5262 let amount = client
5263 .get::<Option<RewardAmount>>(&format!(
5264 "reward-state-v2/reward-balance/{height}/{address}"
5265 ))
5266 .send()
5267 .await
5268 .ok()
5269 .flatten();
5270 assert!(amount.is_none(), "amount is not none for block {height}")
5271 }
5272
5273 if leaf.height() == EPOCH_HEIGHT * 2 {
5274 break;
5275 }
5276 }
5277
5278 let mut rewards_map = HashMap::new();
5279 let mut total_distributed = U256::ZERO;
5280 let mut epoch_rewards = HashMap::<EpochNumber, U256>::new();
5281
5282 while let Some(leaf) = leaves.next().await {
5283 let leaf = leaf.unwrap();
5284
5285 let header = leaf.header();
5286 let distributed = header
5287 .total_reward_distributed()
5288 .expect("rewards distributed is none");
5289
5290 let block = leaf.height();
5291 tracing::info!("verify rewards for block={block:?}");
5292 let membership = coordinator.membership();
5293 let epoch_number =
5294 EpochNumber::new(epoch_from_block_number(leaf.height(), EPOCH_HEIGHT));
5295
5296 let snapshot = membership.snapshot(epoch_number).expect("snapshot");
5297 let block_reward = snapshot.epoch_block_reward().unwrap();
5298 let leader = snapshot.leader(leaf.leaf().view_number()).expect("leader");
5299 let leader_eth_address = snapshot
5300 .validator_config(&leader)
5301 .expect("validator config")
5302 .account;
5303
5304 let validators = client
5305 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{epoch_number}"))
5306 .send()
5307 .await
5308 .expect("validators");
5309
5310 let leader_validator = validators
5311 .get(&leader_eth_address)
5312 .expect("leader not found");
5313
5314 let distributor =
5315 RewardDistributor::new(leader_validator.clone(), block_reward, distributed);
5316 for validator in validators.values() {
5318 let delegator_stake_sum: U256 = validator.delegators.values().cloned().sum();
5319
5320 assert_eq!(delegator_stake_sum, validator.stake);
5321 }
5322
5323 let computed_rewards = distributor.compute_rewards().expect("reward computation");
5324
5325 let total_reward = block_reward.0;
5327 let leader_commission_basis_points = U256::from(leader_validator.commission);
5328 let calculated_leader_commission_reward = leader_commission_basis_points
5329 .checked_mul(total_reward)
5330 .context("overflow")?
5331 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
5332 .context("overflow")?;
5333
5334 assert!(
5335 computed_rewards.leader_commission().0 - calculated_leader_commission_reward
5336 <= U256::from(10_u64)
5337 );
5338
5339 let leader_commission = *computed_rewards.leader_commission();
5341 for (address, amount) in computed_rewards.delegators().clone() {
5342 rewards_map
5343 .entry(address)
5344 .and_modify(|entry| *entry += amount)
5345 .or_insert(amount);
5346 }
5347
5348 rewards_map
5350 .entry(leader_eth_address)
5351 .and_modify(|entry| *entry += leader_commission)
5352 .or_insert(leader_commission);
5353
5354 for (address, calculated_amount) in rewards_map.iter() {
5356 let mut attempt = 0;
5357 let amount_from_api = loop {
5358 let result = client
5359 .get::<Option<RewardAmount>>(&format!(
5360 "reward-state-v2/reward-balance/{block}/{address}"
5361 ))
5362 .send()
5363 .await
5364 .ok()
5365 .flatten();
5366
5367 if let Some(amount) = result {
5368 break amount;
5369 }
5370
5371 attempt += 1;
5372 if attempt >= 3 {
5373 panic!(
5374 "Failed to fetch reward amount for address {address} after 3 retries"
5375 );
5376 }
5377
5378 sleep(Duration::from_secs(2)).await;
5379 };
5380
5381 assert_eq!(amount_from_api, *calculated_amount);
5382 }
5383
5384 total_distributed += block_reward.0;
5386 assert_eq!(
5387 header.total_reward_distributed().unwrap().0,
5388 total_distributed
5389 );
5390
5391 epoch_rewards
5393 .entry(epoch_number)
5394 .and_modify(|r| assert_eq!(*r, block_reward.0))
5395 .or_insert(block_reward.0);
5396
5397 if leaf.height() == EPOCH_HEIGHT * 5 {
5399 break;
5400 }
5401 }
5402
5403 Ok(())
5404 }
5405
5406 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5407 async fn test_epoch_reward_distribution_basic() -> anyhow::Result<()> {
5408 const EPOCH_HEIGHT: u64 = 10;
5409 const NUM_NODES: usize = 5;
5410
5411 const V5: Upgrade = Upgrade::trivial(EPOCH_REWARD_VERSION);
5412
5413 let network_config = TestConfigBuilder::default()
5414 .epoch_height(EPOCH_HEIGHT)
5415 .build();
5416
5417 let api_port = reserve_tcp_port().expect("No ports free for query service");
5418
5419 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5420 let persistence: [_; NUM_NODES] = storage
5421 .iter()
5422 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5423 .collect::<Vec<_>>()
5424 .try_into()
5425 .unwrap();
5426
5427 let config = TestNetworkConfigBuilder::with_num_nodes()
5428 .api_config(SqlDataSource::options(
5429 &storage[0],
5430 Options::with_port(api_port),
5431 ))
5432 .network_config(network_config)
5433 .persistences(persistence.clone())
5434 .catchups(std::array::from_fn(|_| {
5435 StatePeers::<StaticVersion<0, 1>>::from_urls(
5436 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5437 Default::default(),
5438 Duration::from_secs(2),
5439 &NoMetrics,
5440 )
5441 }))
5442 .pos_hook(DelegationConfig::MultipleDelegators, Default::default(), V5)
5443 .await
5444 .unwrap()
5445 .build();
5446
5447 let _network = TestNetwork::new(config, V5).await;
5448 let client: Client<ClientErr, SequencerApiVersion> =
5449 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5450
5451 let height_client: Client<ClientErr, StaticVersion<0, 1>> =
5453 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5454 wait_until_block_height(&height_client, "node/block-height", EPOCH_HEIGHT * 5).await;
5455
5456 let mut leaves = client
5457 .socket("availability/stream/leaves/0")
5458 .subscribe::<LeafQueryData<SeqTypes>>()
5459 .await
5460 .unwrap();
5461
5462 while let Some(leaf) = leaves.next().await {
5464 let leaf = leaf.unwrap();
5465 let header = leaf.header();
5466 let height = header.height();
5467
5468 let total_distributed = header.total_reward_distributed().unwrap();
5469 assert_eq!(
5470 total_distributed.0,
5471 U256::ZERO,
5472 "epochs 1-3 should have no rewards, height={height}"
5473 );
5474
5475 if height == EPOCH_HEIGHT * 3 {
5476 break;
5477 }
5478 }
5479
5480 while let Some(leaf) = leaves.next().await {
5481 let leaf = leaf.unwrap();
5482 let header = leaf.header();
5483 let height = header.height();
5484
5485 if height == EPOCH_HEIGHT * 4 {
5486 let total_distributed = header.total_reward_distributed().unwrap();
5487 assert!(total_distributed.0 > U256::ZERO,);
5488 break;
5489 }
5490 }
5491
5492 while let Some(leaf) = leaves.next().await {
5493 let leaf = leaf.unwrap();
5494 let header = leaf.header();
5495 let height = header.height();
5496
5497 if height == EPOCH_HEIGHT * 5 {
5498 let total_distributed = header.total_reward_distributed().unwrap();
5499 assert!(total_distributed.0 > U256::ZERO,);
5500 break;
5501 }
5502 }
5503
5504 Ok(())
5505 }
5506
5507 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5510 async fn test_new_protocol_produces_blocks() -> anyhow::Result<()> {
5511 const EPOCH_HEIGHT: u64 = 100;
5512 const NUM_NODES: usize = 5;
5513 const TARGET_BLOCK_HEIGHT: u64 = 100;
5514
5515 const NEW_PROTOCOL: Upgrade = Upgrade::trivial(NEW_PROTOCOL_VERSION);
5516
5517 let network_config = TestConfigBuilder::default()
5518 .epoch_height(EPOCH_HEIGHT)
5519 .epoch_start_block(0)
5520 .build();
5521
5522 let api_port = reserve_tcp_port().expect("No ports free for query service");
5523
5524 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5525 let persistence: [_; NUM_NODES] = storage
5526 .iter()
5527 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5528 .collect::<Vec<_>>()
5529 .try_into()
5530 .unwrap();
5531
5532 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
5533 .api_config(SqlDataSource::options(
5534 &storage[0],
5535 Options::with_port(api_port),
5536 ))
5537 .network_config(network_config)
5538 .persistences(persistence)
5539 .catchups(std::array::from_fn(|_| {
5540 StatePeers::<SequencerApiVersion>::from_urls(
5541 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5542 Default::default(),
5543 Duration::from_secs(2),
5544 &NoMetrics,
5545 )
5546 }))
5547 .pos_hook(
5548 DelegationConfig::MultipleDelegators,
5549 StakeTableContractVersion::V3,
5550 NEW_PROTOCOL,
5551 )
5552 .await
5553 .unwrap()
5554 .build();
5555
5556 let _network = TestNetwork::new(config, NEW_PROTOCOL).await;
5557
5558 let client: Client<ClientErr, SequencerApiVersion> =
5559 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5560 client.connect(Some(Duration::from_secs(30))).await;
5561
5562 let mut leaves = client
5563 .socket("availability/stream/leaves/0")
5564 .subscribe::<LeafQueryData<SeqTypes>>()
5565 .await
5566 .expect("subscribe to leaf stream");
5567
5568 let mut height = 0;
5569 while let Some(leaf) = leaves.next().await {
5570 let leaf = leaf.expect("leaf stream yielded an error");
5571 let header = leaf.header();
5572 height = header.height();
5573
5574 if height > 0 {
5575 assert_eq!(
5576 header.version(),
5577 NEW_PROTOCOL_VERSION,
5578 "block {height} should be produced under the new protocol version",
5579 );
5580 }
5581
5582 if height >= TARGET_BLOCK_HEIGHT {
5583 break;
5584 }
5585 }
5586
5587 assert!(
5588 height >= TARGET_BLOCK_HEIGHT,
5589 "expected at least {TARGET_BLOCK_HEIGHT} blocks, got {height} (leaf stream ended \
5590 early)",
5591 );
5592
5593 Ok(())
5594 }
5595
5596 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5604 async fn test_new_protocol_survives_legacy_shutdown() -> anyhow::Result<()> {
5605 const EPOCH_HEIGHT: u64 = 20;
5606 const NUM_NODES: usize = 5;
5607 const SHUTDOWN_HEIGHT: u64 = 10;
5608 const TARGET_BLOCK_HEIGHT: u64 = 50;
5609
5610 const NEW_PROTOCOL: Upgrade = Upgrade::trivial(NEW_PROTOCOL_VERSION);
5611
5612 let network_config = TestConfigBuilder::default()
5613 .epoch_height(EPOCH_HEIGHT)
5614 .epoch_start_block(0)
5615 .build();
5616
5617 let api_port = reserve_tcp_port().expect("No ports free for query service");
5618
5619 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5620 let persistence: [_; NUM_NODES] = storage
5621 .iter()
5622 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5623 .collect::<Vec<_>>()
5624 .try_into()
5625 .unwrap();
5626
5627 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
5628 .api_config(SqlDataSource::options(
5629 &storage[0],
5630 Options::with_port(api_port),
5631 ))
5632 .network_config(network_config)
5633 .persistences(persistence)
5634 .catchups(std::array::from_fn(|_| {
5635 StatePeers::<SequencerApiVersion>::from_urls(
5636 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5637 Default::default(),
5638 Duration::from_secs(2),
5639 &NoMetrics,
5640 )
5641 }))
5642 .pos_hook(
5643 DelegationConfig::MultipleDelegators,
5644 StakeTableContractVersion::V3,
5645 NEW_PROTOCOL,
5646 )
5647 .await
5648 .unwrap()
5649 .build();
5650
5651 let network = TestNetwork::new(config, NEW_PROTOCOL).await;
5652
5653 let client: Client<ClientErr, SequencerApiVersion> =
5654 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5655 client.connect(Some(Duration::from_secs(30))).await;
5656
5657 let mut leaves = client
5658 .socket("availability/stream/leaves/0")
5659 .subscribe::<LeafQueryData<SeqTypes>>()
5660 .await
5661 .expect("subscribe to leaf stream");
5662
5663 let mut height = 0;
5665 while height < SHUTDOWN_HEIGHT {
5666 let leaf = leaves
5667 .next()
5668 .await
5669 .expect("leaf stream ended early")
5670 .expect("leaf stream yielded an error");
5671 height = leaf.header().height();
5672 }
5673
5674 network.server.consensus_handle().shut_down_legacy().await;
5676 for peer in &network.peers {
5677 peer.consensus_handle().shut_down_legacy().await;
5678 }
5679
5680 while height < TARGET_BLOCK_HEIGHT {
5683 let leaf = leaves
5684 .next()
5685 .await
5686 .expect("leaf stream ended early")
5687 .expect("leaf stream yielded an error");
5688 height = leaf.header().height();
5689 }
5690
5691 Ok(())
5692 }
5693
5694 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5705 async fn test_new_protocol_validator_exit_at_epoch_boundary() -> anyhow::Result<()> {
5706 const NUM_NODES: usize = 5;
5707 const EPOCH_HEIGHT: u64 = 10;
5708 const NEW_PROTOCOL: Upgrade = Upgrade::trivial(NEW_PROTOCOL_VERSION);
5709 const MAX_ACTIVATION_EPOCHS: u64 = 10;
5712
5713 let network_config = TestConfigBuilder::default()
5714 .epoch_height(EPOCH_HEIGHT)
5715 .epoch_start_block(0)
5716 .build();
5717
5718 let api_port = reserve_tcp_port().expect("No ports free for query service");
5719
5720 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5721 let persistence: [_; NUM_NODES] = storage
5722 .iter()
5723 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5724 .collect::<Vec<_>>()
5725 .try_into()
5726 .unwrap();
5727
5728 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
5729 .api_config(SqlDataSource::options(
5730 &storage[0],
5731 Options::with_port(api_port),
5732 ))
5733 .network_config(network_config.clone())
5734 .persistences(persistence)
5735 .catchups(std::array::from_fn(|_| {
5736 StatePeers::<SequencerApiVersion>::from_urls(
5737 vec![format!("http://localhost:{api_port}").parse().unwrap()],
5738 Default::default(),
5739 Duration::from_secs(2),
5740 &NoMetrics,
5741 )
5742 }))
5743 .pos_hook(
5744 DelegationConfig::MultipleDelegators,
5745 StakeTableContractVersion::V3,
5746 NEW_PROTOCOL,
5747 )
5748 .await
5749 .unwrap()
5750 .build();
5751
5752 let network = TestNetwork::new(config, NEW_PROTOCOL).await;
5753 let st_addr = network
5754 .contracts
5755 .as_ref()
5756 .unwrap()
5757 .address(Contract::StakeTableProxy)
5758 .unwrap();
5759
5760 let (exiting, exiting_provider) = network_config
5764 .validator_providers()
5765 .pop()
5766 .expect("at least one validator");
5767
5768 let client: Client<ClientErr, SequencerApiVersion> =
5769 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
5770 client.connect(Some(Duration::from_secs(30))).await;
5771
5772 let mut events = network.peers[0].event_stream();
5774 wait_for_epochs(&mut events, EPOCH_HEIGHT, 3).await;
5775 let exit_epoch = network.peers[0]
5776 .decided_leaf()
5777 .await
5778 .epoch(EPOCH_HEIGHT)
5779 .unwrap()
5780 .u64();
5781
5782 let validators = client
5783 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{exit_epoch}"))
5784 .send()
5785 .await
5786 .expect("validators for the exit epoch");
5787 assert!(
5788 validators.contains_key(&exiting),
5789 "exiting validator should be in the committee before its exit activates"
5790 );
5791 assert_eq!(validators.len(), NUM_NODES);
5792
5793 let receipt = StakingTransaction::DeregisterValidator {
5794 stake_table: st_addr,
5795 }
5796 .send(&exiting_provider)
5797 .await?
5798 .get_receipt()
5799 .await?;
5800 assert!(receipt.status(), "deregistration transaction reverted");
5801
5802 let mut activation_epoch = None;
5805 for epoch in exit_epoch + 1..=exit_epoch + MAX_ACTIVATION_EPOCHS {
5806 wait_for_epochs(&mut events, EPOCH_HEIGHT, epoch).await;
5807 let validators = client
5808 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{epoch}"))
5809 .send()
5810 .await
5811 .expect("validators for a decided epoch");
5812 if !validators.contains_key(&exiting) {
5813 assert_eq!(
5814 validators.len(),
5815 NUM_NODES - 1,
5816 "only the exited validator should have left the committee"
5817 );
5818 activation_epoch = Some(epoch);
5819 break;
5820 }
5821 assert_eq!(validators.len(), NUM_NODES);
5822 }
5823 let activation_epoch = activation_epoch.unwrap_or_else(|| {
5824 panic!("validator still in the committee {MAX_ACTIVATION_EPOCHS} epochs after exiting")
5825 });
5826
5827 wait_for_epochs(&mut events, EPOCH_HEIGHT, activation_epoch + 2).await;
5830
5831 Ok(())
5832 }
5833
5834 #[test_log::test(tokio::test(flavor = "multi_thread"))]
5846 async fn test_new_protocol_query_node_restart_with_fresh_storage() -> anyhow::Result<()> {
5847 const NUM_NODES: usize = 6;
5848 const EPOCH_HEIGHT: u64 = 10;
5849 const EPOCHS_BEFORE_RESTART: u64 = 7;
5850 const NEW_PROTOCOL: Upgrade = Upgrade::trivial(NEW_PROTOCOL_VERSION);
5851 const RECOVERY_TIMEOUT: Duration = Duration::from_secs(240);
5853
5854 let network_config = TestConfigBuilder::default()
5855 .epoch_height(EPOCH_HEIGHT)
5856 .epoch_start_block(0)
5857 .build();
5858
5859 let api_port = reserve_tcp_port().expect("No ports free for query service");
5860 let query_port = reserve_tcp_port().expect("No ports free for query service");
5861 let api_url: Url = format!("http://localhost:{api_port}").parse()?;
5862 let query_url: Url = format!("http://localhost:{query_port}").parse()?;
5863
5864 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
5865 let persistence: [_; NUM_NODES] = storage
5866 .iter()
5867 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
5868 .collect::<Vec<_>>()
5869 .try_into()
5870 .unwrap();
5871
5872 let query_urls = vec![api_url.clone(), query_url.clone()];
5877 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
5878 .api_config(
5879 Options::with_port(api_port)
5880 .catchup(Default::default())
5881 .light_client(Default::default())
5882 .query_sql(
5883 Query {
5884 peers: vec![query_url.clone()],
5885 ..Default::default()
5886 },
5887 tmp_options(&storage[0]),
5888 ),
5889 )
5890 .network_config(network_config)
5891 .persistences(persistence.clone())
5892 .catchups(std::array::from_fn(|_| {
5893 StatePeers::<SequencerApiVersion>::from_urls(
5894 query_urls.clone(),
5895 Default::default(),
5896 Duration::from_secs(2),
5897 &NoMetrics,
5898 )
5899 }))
5900 .pos_hook(
5901 DelegationConfig::MultipleDelegators,
5902 StakeTableContractVersion::V3,
5903 NEW_PROTOCOL,
5904 )
5905 .await?
5906 .build();
5907
5908 let genesis_state = config.states()[0].clone();
5909 let mut network = TestNetwork::new(config, NEW_PROTOCOL).await;
5910
5911 network.peers[0].shut_down().await;
5915 network.peers.remove(0);
5916
5917 let cfg = network.cfg.clone();
5918 let peer_url = api_url.clone();
5919 let start_query_node = move |db: persistence::sql::Options| {
5920 let cfg = cfg.clone();
5921 let genesis_state = genesis_state.clone();
5922 let peer_url = peer_url.clone();
5923 async move {
5924 let opt = Options::with_port(query_port)
5925 .catchup(Default::default())
5926 .light_client(Default::default())
5927 .query_sql(
5928 Query {
5929 peers: vec![peer_url.clone()],
5930 ..Default::default()
5931 },
5932 db.clone(),
5933 );
5934 let ctx = opt
5935 .serve(move |metrics, consumer, storage| {
5936 async move {
5937 Ok(cfg
5938 .init_node(
5939 1,
5940 genesis_state,
5941 db,
5942 Some(StatePeers::<SequencerApiVersion>::from_urls(
5943 vec![peer_url],
5944 Default::default(),
5945 Duration::from_secs(2),
5946 &NoMetrics,
5947 )),
5948 storage,
5949 &*metrics,
5950 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
5951 consumer,
5952 NEW_PROTOCOL,
5953 Default::default(),
5954 )
5955 .await)
5956 }
5957 .boxed()
5958 })
5959 .await
5960 .expect("second query node should start");
5961 ctx.start_consensus().await;
5962 ctx
5963 }
5964 };
5965
5966 let mut query_node = start_query_node(tmp_options(&storage[1])).await;
5967
5968 let api_client: Client<ClientErr, SequencerApiVersion> = Client::new(api_url);
5969 let query_client: Client<ClientErr, SequencerApiVersion> = Client::new(query_url);
5970 assert!(
5971 api_client.connect(Some(Duration::from_secs(60))).await,
5972 "node 0 query API did not come up"
5973 );
5974 assert!(
5975 query_client.connect(Some(Duration::from_secs(60))).await,
5976 "node 1 query API did not come up"
5977 );
5978
5979 let mut events = network.peers[0].event_stream();
5982 wait_for_epochs(&mut events, EPOCH_HEIGHT, EPOCHS_BEFORE_RESTART).await;
5983
5984 let height_before_restart = query_node.decided_leaf().await.height();
5987 timeout(
5988 RECOVERY_TIMEOUT,
5989 wait_until_block_height(
5990 &query_client,
5991 "block-state/block-height",
5992 height_before_restart,
5993 ),
5994 )
5995 .await
5996 .context("second query node was not caught up before the restart")?;
5997
5998 let mut finalized_height = None;
6002 for height in (1..height_before_restart).rev() {
6003 if api_client
6004 .get::<espresso_types::Certificate2<SeqTypes>>(&format!(
6005 "availability/cert2/{height}"
6006 ))
6007 .send()
6008 .await
6009 .is_ok()
6010 {
6011 finalized_height = Some(height);
6012 break;
6013 }
6014 }
6015 let finalized_height =
6016 finalized_height.context("no cert2 stored on a new protocol chain")?;
6017
6018 tracing::info!(
6019 height_before_restart,
6020 finalized_height,
6021 "restarting the second query node with fresh storage"
6022 );
6023 query_node.shut_down().await;
6024 let cliquenet_port = network.cfg.known_nodes_with_stake()[1]
6030 .connect_info
6031 .as_ref()
6032 .expect("node 1 registered cliquenet connect info")
6033 .p2p_addr
6034 .port();
6035 timeout(RECOVERY_TIMEOUT, async {
6036 for port in [query_port, cliquenet_port] {
6037 while std::net::TcpListener::bind(("127.0.0.1", port)).is_err() {
6038 sleep(Duration::from_millis(100)).await;
6039 }
6040 }
6041 })
6042 .await
6043 .context("shut-down node did not release its ports")?;
6044
6045 let fresh_storage = SqlDataSource::create_storage().await;
6046 let query_node = start_query_node(tmp_options(&fresh_storage)).await;
6047 assert!(
6048 query_client.connect(Some(Duration::from_secs(60))).await,
6049 "restarted query API did not come up"
6050 );
6051
6052 timeout(
6055 RECOVERY_TIMEOUT,
6056 wait_until_block_height(&query_client, "status/block-height", height_before_restart),
6057 )
6058 .await
6059 .context("restarted node did not rebuild its archive")?;
6060 timeout(
6061 RECOVERY_TIMEOUT,
6062 wait_until_block_height(
6063 &query_client,
6064 "block-state/block-height",
6065 height_before_restart,
6066 ),
6067 )
6068 .await
6069 .context("restarted node did not rebuild its merklized state")?;
6070
6071 let wiped_range = (height_before_restart - 1) as usize;
6077 let stream_leaves = |client: Client<ClientErr, SequencerApiVersion>, who: &'static str| async move {
6078 let leaves: Vec<LeafQueryData<SeqTypes>> = client
6079 .socket("availability/stream/leaves/1")
6080 .subscribe()
6081 .await
6082 .with_context(|| format!("subscribing to {who}'s leaf stream"))?
6083 .take(wiped_range)
6084 .try_collect()
6085 .await
6086 .with_context(|| format!("{who}'s leaf stream errored"))?;
6087 anyhow::Ok(leaves)
6088 };
6089 let (ours, theirs) = timeout(
6092 RECOVERY_TIMEOUT,
6093 future::try_join(
6094 stream_leaves(query_client.clone(), "the restarted node"),
6095 stream_leaves(api_client.clone(), "node 0"),
6096 ),
6097 )
6098 .await
6099 .context("streaming the wiped range stalled")??;
6100 assert_eq!(
6101 ours.len(),
6102 wiped_range,
6103 "restarted node's leaf stream ended early"
6104 );
6105 assert_eq!(
6106 theirs.len(),
6107 wiped_range,
6108 "node 0's leaf stream ended early"
6109 );
6110 for (ours, theirs) in ours.iter().zip(&theirs) {
6111 assert_eq!(
6112 ours.hash(),
6113 theirs.hash(),
6114 "restarted node's leaf at height {} diverges from node 0",
6115 ours.height(),
6116 );
6117 assert_eq!(
6118 ours.header().version(),
6119 NEW_PROTOCOL_VERSION,
6120 "block {} should have been produced under the new protocol",
6121 ours.height(),
6122 );
6123 }
6124
6125 let cert2 = timeout(RECOVERY_TIMEOUT, async {
6126 loop {
6127 match query_client
6128 .get::<espresso_types::Certificate2<SeqTypes>>(&format!(
6129 "availability/cert2/{finalized_height}"
6130 ))
6131 .send()
6132 .await
6133 {
6134 Ok(cert2) => break cert2,
6135 Err(err) => {
6136 tracing::info!(finalized_height, %err, "cert2 not backfilled yet")
6137 },
6138 }
6139 sleep(Duration::from_secs(2)).await;
6140 }
6141 })
6142 .await
6143 .context("restarted node did not backfill the cert2")?;
6144 assert_eq!(cert2.data.block_number, finalized_height);
6145
6146 let node_1_key = network.cfg.known_nodes_with_stake()[1]
6149 .stake_table_entry
6150 .stake_key;
6151 let coordinator = query_node.node_state().coordinator;
6152 let mut events = query_node.event_stream();
6153 let proposed = timeout(RECOVERY_TIMEOUT, async {
6154 while let Some(event) = events.next().await {
6155 let leaf_infos: &[LeafInfo<SeqTypes>] = match &event {
6156 CoordinatorEvent::LegacyEvent(Event {
6157 event: EventType::Decide { leaf_chain, .. },
6158 ..
6159 }) => leaf_chain,
6160 CoordinatorEvent::NewDecide { leaf_infos, .. } => leaf_infos,
6161 _ => continue,
6162 };
6163 for LeafInfo { leaf, .. } in leaf_infos {
6164 if leaf.height() <= height_before_restart {
6165 continue;
6166 }
6167 let membership =
6168 match coordinator.membership_for_epoch(leaf.epoch(EPOCH_HEIGHT)) {
6169 Ok(membership) => membership,
6170 Err(err) => {
6171 tracing::warn!(
6172 height = leaf.height(),
6173 %err,
6174 "no membership for epoch",
6175 );
6176 continue;
6177 },
6178 };
6179 match membership.leader(leaf.view_number()) {
6180 Ok(leader) if leader == node_1_key => return Some(leaf.height()),
6181 Ok(_) => {},
6182 Err(err) => {
6183 tracing::warn!(view = ?leaf.view_number(), %err, "leader unresolved");
6184 },
6185 }
6186 }
6187 }
6188 None
6189 })
6190 .await
6191 .context("restarted node's event stream stalled")?;
6192 let proposed = proposed.context("restarted node's event stream ended")?;
6193 tracing::info!(proposed, "restarted node proposed a decided block");
6194
6195 Ok(())
6196 }
6197
6198 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6199 async fn test_epoch_reward_total_distributed_rewards() -> anyhow::Result<()> {
6200 const EPOCH_HEIGHT: u64 = 10;
6205 const NUM_NODES: usize = 5;
6206
6207 const V5: Upgrade = Upgrade::trivial(EPOCH_REWARD_VERSION);
6208
6209 let network_config = TestConfigBuilder::default()
6210 .epoch_height(EPOCH_HEIGHT)
6211 .build();
6212
6213 let api_port = reserve_tcp_port().expect("No ports free for query service");
6214
6215 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6216 let persistence: [_; NUM_NODES] = storage
6217 .iter()
6218 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6219 .collect::<Vec<_>>()
6220 .try_into()
6221 .unwrap();
6222
6223 let config = TestNetworkConfigBuilder::with_num_nodes()
6224 .api_config(SqlDataSource::options(
6225 &storage[0],
6226 Options::with_port(api_port),
6227 ))
6228 .network_config(network_config)
6229 .persistences(persistence.clone())
6230 .catchups(std::array::from_fn(|_| {
6231 StatePeers::<StaticVersion<0, 1>>::from_urls(
6232 vec![format!("http://localhost:{api_port}").parse().unwrap()],
6233 Default::default(),
6234 Duration::from_secs(2),
6235 &NoMetrics,
6236 )
6237 }))
6238 .pos_hook(DelegationConfig::MultipleDelegators, Default::default(), V5)
6239 .await
6240 .unwrap()
6241 .build();
6242
6243 let _network = TestNetwork::new(config, V5).await;
6244 let client: Client<ClientErr, SequencerApiVersion> =
6245 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
6246
6247 let height_client: Client<ClientErr, StaticVersion<0, 1>> =
6248 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
6249 wait_until_block_height(&height_client, "node/block-height", EPOCH_HEIGHT * 5).await;
6250
6251 let mut leaves = client
6252 .socket("availability/stream/leaves/0")
6253 .subscribe::<LeafQueryData<SeqTypes>>()
6254 .await
6255 .unwrap();
6256
6257 while let Some(leaf) = leaves.next().await {
6258 let leaf = leaf.unwrap();
6259 let header = leaf.header();
6260 let height = header.height();
6261
6262 let total_distributed = header.total_reward_distributed().unwrap();
6263 assert_eq!(total_distributed.0, U256::ZERO,);
6264
6265 if height == EPOCH_HEIGHT * 3 {
6266 break;
6267 }
6268 }
6269
6270 while let Some(leaf) = leaves.next().await {
6271 let leaf = leaf.unwrap();
6272 let header = leaf.header();
6273 let height = header.height();
6274
6275 let total_distributed = header.total_reward_distributed().unwrap();
6276
6277 if height < EPOCH_HEIGHT * 4 {
6278 assert_eq!(total_distributed.0, U256::ZERO,);
6279 } else {
6280 assert!(total_distributed.0 > U256::ZERO,);
6281 break;
6282 }
6283 }
6284
6285 let epoch4_last_reward = {
6286 let header = client
6287 .get::<Header>(&format!("availability/header/{}", EPOCH_HEIGHT * 4))
6288 .send()
6289 .await
6290 .unwrap();
6291 header.total_reward_distributed().unwrap()
6292 };
6293
6294 assert!(
6295 epoch4_last_reward.0 > U256::ZERO,
6296 "epoch 4 last block should have positive rewards"
6297 );
6298
6299 while let Some(leaf) = leaves.next().await {
6300 let leaf = leaf.unwrap();
6301 let header = leaf.header();
6302 let height = header.height();
6303
6304 let total_distributed = header.total_reward_distributed().unwrap();
6305
6306 if height < EPOCH_HEIGHT * 5 {
6307 assert_eq!(total_distributed, epoch4_last_reward,);
6308 } else {
6309 assert!(total_distributed.0 > epoch4_last_reward.0,);
6310 break;
6311 }
6312 }
6313
6314 Ok(())
6315 }
6316
6317 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6320 async fn test_reward_state_v2_epoch_distribution() -> anyhow::Result<()> {
6321 const EPOCH_HEIGHT: u64 = 10;
6322 const NUM_NODES: usize = 5;
6323 const NUM_EPOCHS: u64 = 6;
6324 const V5: Upgrade = Upgrade::trivial(EPOCH_REWARD_VERSION);
6325
6326 let network_config = TestConfigBuilder::default()
6327 .epoch_height(EPOCH_HEIGHT)
6328 .build();
6329
6330 let api_port = reserve_tcp_port().expect("No ports free for query service");
6331
6332 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6333 let persistence: [_; NUM_NODES] = storage
6334 .iter()
6335 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6336 .collect::<Vec<_>>()
6337 .try_into()
6338 .unwrap();
6339
6340 let config = TestNetworkConfigBuilder::with_num_nodes()
6341 .api_config(SqlDataSource::options(
6342 &storage[0],
6343 Options::with_port(api_port),
6344 ))
6345 .network_config(network_config)
6346 .persistences(persistence.clone())
6347 .catchups(std::array::from_fn(|_| {
6348 StatePeers::<StaticVersion<0, 1>>::from_urls(
6349 vec![format!("http://localhost:{api_port}").parse().unwrap()],
6350 Default::default(),
6351 Duration::from_secs(2),
6352 &NoMetrics,
6353 )
6354 }))
6355 .pos_hook(DelegationConfig::MultipleDelegators, Default::default(), V5)
6356 .await
6357 .unwrap()
6358 .build();
6359
6360 let network = TestNetwork::new(config, V5).await;
6361 let client: Client<ClientErr, SequencerApiVersion> =
6362 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
6363
6364 let node_state = network.server.node_state();
6365 let coordinator = node_state.coordinator;
6366
6367 let mut expected_total_distributed = U256::ZERO;
6368
6369 let mut leaves = client
6370 .socket("availability/stream/leaves/0")
6371 .subscribe::<LeafQueryData<SeqTypes>>()
6372 .await
6373 .unwrap();
6374
6375 while let Some(leaf) = leaves.next().await {
6376 let leaf = leaf.unwrap();
6377 let header = leaf.header();
6378 let height = header.height();
6379
6380 let epoch = epoch_from_block_number(height, EPOCH_HEIGHT);
6381
6382 let is_epoch_last_block = height % EPOCH_HEIGHT == 0;
6383
6384 if epoch <= 3 {
6385 continue;
6386 }
6387
6388 let header_total_distributed = header
6389 .total_reward_distributed()
6390 .expect("total_reward_distributed should exist");
6391
6392 if is_epoch_last_block {
6393 let prev_epoch = epoch - 1;
6394 let prev_epoch_number = EpochNumber::new(prev_epoch);
6395 let membership = coordinator.membership();
6396 let prev_block_reward = membership
6397 .epoch_block_reward(prev_epoch_number)
6398 .expect("epoch block reward should exist");
6399
6400 let epoch_total = prev_block_reward.0 * U256::from(EPOCH_HEIGHT);
6401 expected_total_distributed += epoch_total;
6402 }
6403
6404 assert_eq!(
6405 header_total_distributed.0, expected_total_distributed,
6406 "total_reward_distributed mismatch at height {height}"
6407 );
6408
6409 if height >= NUM_EPOCHS * EPOCH_HEIGHT {
6410 break;
6411 }
6412 }
6413
6414 Ok(())
6415 }
6416
6417 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6419 async fn test_epoch_leader_counts() -> anyhow::Result<()> {
6420 const EPOCH_HEIGHT: u64 = 10;
6421 const NUM_NODES: usize = 5;
6422 const NUM_EPOCHS: u64 = 6;
6423 const V5: Upgrade = Upgrade::trivial(EPOCH_REWARD_VERSION);
6424
6425 let network_config = TestConfigBuilder::default()
6426 .epoch_height(EPOCH_HEIGHT)
6427 .build();
6428
6429 let api_port = reserve_tcp_port().expect("No ports free for query service");
6430
6431 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6432 let persistence: [_; NUM_NODES] = storage
6433 .iter()
6434 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6435 .collect::<Vec<_>>()
6436 .try_into()
6437 .unwrap();
6438
6439 let config = TestNetworkConfigBuilder::with_num_nodes()
6440 .api_config(SqlDataSource::options(
6441 &storage[0],
6442 Options::with_port(api_port),
6443 ))
6444 .network_config(network_config)
6445 .persistences(persistence.clone())
6446 .catchups(std::array::from_fn(|_| {
6447 StatePeers::<StaticVersion<0, 1>>::from_urls(
6448 vec![format!("http://localhost:{api_port}").parse().unwrap()],
6449 Default::default(),
6450 Duration::from_secs(2),
6451 &NoMetrics,
6452 )
6453 }))
6454 .pos_hook(DelegationConfig::MultipleDelegators, Default::default(), V5)
6455 .await
6456 .unwrap()
6457 .build();
6458
6459 let network = TestNetwork::new(config, V5).await;
6460 let client: Client<ClientErr, SequencerApiVersion> =
6461 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
6462
6463 let node_state = network.server.node_state();
6464 let coordinator = node_state.coordinator;
6465
6466 let mut expected_counts: HashMap<Address, u16> = HashMap::new();
6468
6469 let mut leaves = client
6470 .socket("availability/stream/leaves/0")
6471 .subscribe::<LeafQueryData<SeqTypes>>()
6472 .await
6473 .unwrap();
6474
6475 while let Some(leaf) = leaves.next().await {
6476 let leaf = leaf.unwrap();
6477 let header = leaf.header();
6478 let height = header.height();
6479 let epoch = epoch_from_block_number(height, EPOCH_HEIGHT);
6480 let epoch_number = EpochNumber::new(epoch);
6481
6482 if epoch <= 2 {
6483 continue;
6484 }
6485
6486 let header_leader_counts = header
6487 .leader_counts()
6488 .expect("V5+ header must have leader_counts");
6489
6490 let is_epoch_start = (height - 1) % EPOCH_HEIGHT == 0;
6492 if is_epoch_start {
6493 expected_counts.clear();
6494 }
6495
6496 let view_number = leaf.leaf().view_number();
6498 let snapshot = coordinator
6499 .membership()
6500 .snapshot(epoch_number)
6501 .expect("committee for epoch_number");
6502 let leader = snapshot.leader(view_number).expect("leader should exist");
6503 let leader_address = snapshot
6504 .validator_config(&leader)
6505 .expect("leader should have an address")
6506 .account;
6507
6508 let validator_leader_counts =
6509 ValidatorLeaderCounts::new(&snapshot, *header_leader_counts)
6510 .expect("ValidatorLeaderCounts should build from header leader_counts");
6511
6512 *expected_counts.entry(leader_address).or_insert(0) += 1;
6513
6514 let header_counts: HashMap<Address, u16> = validator_leader_counts
6515 .active_leaders()
6516 .map(|(v, count)| (v.account, count))
6517 .collect();
6518
6519 assert_eq!(
6520 header_counts, expected_counts,
6521 "leader_counts mismatch at height {height} (epoch {epoch})"
6522 );
6523
6524 if height % EPOCH_HEIGHT == 0 {
6525 let total: u16 = expected_counts.values().sum();
6526 assert_eq!(
6527 total, EPOCH_HEIGHT as u16,
6528 "total leader_counts at epoch boundary should equal EPOCH_HEIGHT at height \
6529 {height}"
6530 );
6531 }
6532
6533 if height >= NUM_EPOCHS * EPOCH_HEIGHT {
6534 break;
6535 }
6536 }
6537
6538 Ok(())
6539 }
6540
6541 #[rstest]
6542 #[case(POS_V3)]
6543 #[case(POS_V4)]
6544 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6545 async fn test_node_stake_table_api(#[case] upgrade: Upgrade) {
6546 let epoch_height = 20;
6547
6548 let network_config = TestConfigBuilder::default()
6549 .epoch_height(epoch_height)
6550 .build();
6551
6552 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
6553
6554 const NUM_NODES: usize = 2;
6555 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6557 let persistence: [_; NUM_NODES] = storage
6558 .iter()
6559 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6560 .collect::<Vec<_>>()
6561 .try_into()
6562 .unwrap();
6563
6564 let config = TestNetworkConfigBuilder::with_num_nodes()
6565 .api_config(SqlDataSource::options(
6566 &storage[0],
6567 Options::with_port(api_port),
6568 ))
6569 .network_config(network_config)
6570 .persistences(persistence.clone())
6571 .catchups(std::array::from_fn(|_| {
6572 StatePeers::<StaticVersion<0, 1>>::from_urls(
6573 vec![format!("http://localhost:{api_port}").parse().unwrap()],
6574 Default::default(),
6575 Duration::from_secs(2),
6576 &NoMetrics,
6577 )
6578 }))
6579 .pos_hook(
6580 DelegationConfig::MultipleDelegators,
6581 Default::default(),
6582 upgrade,
6583 )
6584 .await
6585 .unwrap()
6586 .build();
6587
6588 let _network = TestNetwork::new(config, upgrade).await;
6589
6590 let client: Client<ClientErr, SequencerApiVersion> =
6591 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
6592
6593 let _blocks = client
6595 .socket("availability/stream/blocks/0")
6596 .subscribe::<BlockQueryData<SeqTypes>>()
6597 .await
6598 .unwrap()
6599 .take(40)
6600 .try_collect::<Vec<_>>()
6601 .await
6602 .unwrap();
6603
6604 for i in 1..=3 {
6605 let _st = client
6606 .get::<Vec<PeerConfig<SeqTypes>>>(&format!("node/stake-table/{}", i as u64))
6607 .send()
6608 .await
6609 .expect("failed to get stake table");
6610 }
6611
6612 let _st = client
6613 .get::<StakeTableWithEpochNumber<SeqTypes>>("node/stake-table/current")
6614 .send()
6615 .await
6616 .expect("failed to get stake table");
6617 }
6618
6619 #[rstest]
6620 #[case(POS_V3)]
6621 #[case(POS_V4)]
6622 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6623 async fn test_epoch_stake_table_catchup(#[case] upgrade: Upgrade) {
6624 const EPOCH_HEIGHT: u64 = 10;
6625 const NUM_NODES: usize = 6;
6626
6627 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
6628
6629 let network_config = TestConfigBuilder::default()
6630 .epoch_height(EPOCH_HEIGHT)
6631 .build();
6632
6633 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6635
6636 let persistence_options: [_; NUM_NODES] = storage
6637 .iter()
6638 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6639 .collect::<Vec<_>>()
6640 .try_into()
6641 .unwrap();
6642
6643 let catchup_peers = std::array::from_fn(|_| {
6645 StatePeers::<StaticVersion<0, 1>>::from_urls(
6646 vec![format!("http://localhost:{port}").parse().unwrap()],
6647 Default::default(),
6648 Duration::from_secs(2),
6649 &NoMetrics,
6650 )
6651 });
6652 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
6653 .api_config(SqlDataSource::options(
6654 &storage[0],
6655 Options::with_port(port),
6656 ))
6657 .network_config(network_config)
6658 .persistences(persistence_options.clone())
6659 .catchups(catchup_peers)
6660 .pos_hook(
6661 DelegationConfig::MultipleDelegators,
6662 Default::default(),
6663 upgrade,
6664 )
6665 .await
6666 .unwrap()
6667 .build();
6668
6669 let state = config.states()[0].clone();
6670 let mut network = TestNetwork::new(config, upgrade).await;
6671
6672 let mut events = network.peers[0].event_stream();
6674 while let Some(event) = events.next().await {
6675 if let CoordinatorEvent::LegacyEvent(Event {
6676 event: EventType::Decide { leaf_chain, .. },
6677 ..
6678 }) = event
6679 {
6680 let height = leaf_chain[0].leaf.height();
6681 tracing::info!("Node 0 decided at height: {height}");
6682 if height > EPOCH_HEIGHT * 3 {
6683 break;
6684 }
6685 }
6686 }
6687
6688 tracing::info!("Shutting down peer 0");
6690 network.peers.remove(0);
6691
6692 let mut events = network.server.event_stream();
6694 while let Some(event) = events.next().await {
6695 if let CoordinatorEvent::LegacyEvent(Event {
6696 event: EventType::Decide { leaf_chain, .. },
6697 ..
6698 }) = event
6699 {
6700 let height = leaf_chain[0].leaf.height();
6701 if height > EPOCH_HEIGHT * 7 {
6702 break;
6703 }
6704 }
6705 }
6706
6707 let storage = SqlDataSource::create_storage().await;
6709 let options = <SqlDataSource as TestableSequencerDataSource>::persistence_options(&storage);
6710 tracing::info!("Restarting peer 0");
6711 let node = network
6712 .cfg
6713 .init_node(
6714 1,
6715 state,
6716 options,
6717 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
6718 vec![format!("http://localhost:{port}").parse().unwrap()],
6719 Default::default(),
6720 Duration::from_secs(2),
6721 &NoMetrics,
6722 )),
6723 None,
6724 &NoMetrics,
6725 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
6726 NullEventConsumer,
6727 upgrade,
6728 Default::default(),
6729 )
6730 .await;
6731
6732 let coordinator = node.node_state().coordinator;
6733 let server_node_state = network.server.node_state();
6734 let server_coordinator = server_node_state.coordinator;
6735 for epoch_num in 1..=7 {
6737 let epoch = EpochNumber::new(epoch_num);
6738 let node_em = match coordinator.membership_for_epoch(Some(epoch)) {
6739 Ok(em) => em,
6740 Err(_) => coordinator.wait_for_catchup(epoch).await.unwrap(),
6741 };
6742 let server_em = match server_coordinator.membership_for_epoch(Some(epoch)) {
6743 Ok(em) => em,
6744 Err(_) => server_coordinator.wait_for_catchup(epoch).await.unwrap(),
6745 };
6746
6747 println!("have stake table for epoch = {epoch_num}");
6748
6749 let node_stake_table = HSStakeTable::from_iter(node_em.stake_table());
6750 let stake_table = HSStakeTable::from_iter(server_em.stake_table());
6751 println!("asserting stake table for epoch = {epoch_num}");
6752
6753 assert_eq!(
6754 node_stake_table, stake_table,
6755 "Stake table mismatch for epoch {epoch_num}",
6756 );
6757 }
6758 }
6759
6760 #[rstest]
6761 #[case(POS_V3)]
6762 #[case(POS_V4)]
6763 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6764 async fn test_epoch_stake_table_catchup_stress(#[case] upgrade: Upgrade) {
6765 const EPOCH_HEIGHT: u64 = 10;
6766 const NUM_NODES: usize = 6;
6767
6768 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
6769
6770 let network_config = TestConfigBuilder::default()
6771 .epoch_height(EPOCH_HEIGHT)
6772 .build();
6773
6774 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6776
6777 let persistence_options: [_; NUM_NODES] = storage
6778 .iter()
6779 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6780 .collect::<Vec<_>>()
6781 .try_into()
6782 .unwrap();
6783
6784 let catchup_peers = std::array::from_fn(|_| {
6786 StatePeers::<StaticVersion<0, 1>>::from_urls(
6787 vec![format!("http://localhost:{port}").parse().unwrap()],
6788 Default::default(),
6789 Duration::from_secs(2),
6790 &NoMetrics,
6791 )
6792 });
6793 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
6794 .api_config(SqlDataSource::options(
6795 &storage[0],
6796 Options::with_port(port),
6797 ))
6798 .network_config(network_config)
6799 .persistences(persistence_options.clone())
6800 .catchups(catchup_peers)
6801 .pos_hook(
6802 DelegationConfig::MultipleDelegators,
6803 Default::default(),
6804 upgrade,
6805 )
6806 .await
6807 .unwrap()
6808 .build();
6809
6810 let state = config.states()[0].clone();
6811 let mut network = TestNetwork::new(config, upgrade).await;
6812
6813 let mut events = network.peers[0].event_stream();
6815 while let Some(event) = events.next().await {
6816 if let CoordinatorEvent::LegacyEvent(Event {
6817 event: EventType::Decide { leaf_chain, .. },
6818 ..
6819 }) = event
6820 {
6821 let height = leaf_chain[0].leaf.height();
6822 tracing::info!("Node 0 decided at height: {height}");
6823 if height > EPOCH_HEIGHT * 3 {
6824 break;
6825 }
6826 }
6827 }
6828
6829 tracing::info!("Shutting down peer 0");
6831 network.peers.remove(0);
6832
6833 let mut events = network.server.event_stream();
6835 while let Some(event) = events.next().await {
6836 if let CoordinatorEvent::LegacyEvent(Event {
6837 event: EventType::Decide { leaf_chain, .. },
6838 ..
6839 }) = event
6840 {
6841 let height = leaf_chain[0].leaf.height();
6842 tracing::info!("Server decided at height: {height}");
6843 if height > EPOCH_HEIGHT * 7 {
6845 break;
6846 }
6847 }
6848 }
6849
6850 let storage = SqlDataSource::create_storage().await;
6852 let options = <SqlDataSource as TestableSequencerDataSource>::persistence_options(&storage);
6853
6854 tracing::info!("Restarting peer 0");
6855 let node = network
6856 .cfg
6857 .init_node(
6858 1,
6859 state,
6860 options,
6861 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
6862 vec![format!("http://localhost:{port}").parse().unwrap()],
6863 Default::default(),
6864 Duration::from_secs(2),
6865 &NoMetrics,
6866 )),
6867 None,
6868 &NoMetrics,
6869 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
6870 NullEventConsumer,
6871 upgrade,
6872 Default::default(),
6873 )
6874 .await;
6875
6876 let coordinator = node.node_state().coordinator;
6877
6878 let server_node_state = network.server.node_state();
6879 let server_coordinator = server_node_state.coordinator;
6880
6881 let mut rand_epochs: Vec<_> = (1..=7).collect();
6883 rand_epochs.shuffle(&mut rand::thread_rng());
6884 println!("trigger catchup in this order: {rand_epochs:?}");
6885 for epoch_num in rand_epochs {
6886 let epoch = EpochNumber::new(epoch_num);
6887 let _ = coordinator.membership_for_epoch(Some(epoch));
6888 }
6889
6890 for epoch_num in 1..=7 {
6892 println!("getting stake table for epoch = {epoch_num}");
6893 let epoch = EpochNumber::new(epoch_num);
6894 let node_em = coordinator.wait_for_catchup(epoch).await.unwrap();
6895 let server_em = match server_coordinator.membership_for_epoch(Some(epoch)) {
6896 Ok(em) => em,
6897 Err(_) => server_coordinator.wait_for_catchup(epoch).await.unwrap(),
6898 };
6899
6900 println!("have stake table for epoch = {epoch_num}");
6901
6902 let node_stake_table = HSStakeTable::from_iter(node_em.stake_table());
6903 let stake_table = HSStakeTable::from_iter(server_em.stake_table());
6904
6905 println!("asserting stake table for epoch = {epoch_num}");
6906
6907 assert_eq!(
6908 node_stake_table, stake_table,
6909 "Stake table mismatch for epoch {epoch_num}",
6910 );
6911 }
6912 }
6913
6914 #[rstest]
6915 #[case(POS_V3)]
6916 #[case(POS_V4)]
6917 #[test_log::test(tokio::test(flavor = "multi_thread"))]
6918 async fn test_merklized_state_catchup_on_restart(
6919 #[case] upgrade: Upgrade,
6920 ) -> anyhow::Result<()> {
6921 use espresso_types::{DECAF_CHAIN_ID, v0_3::ChainConfig};
6933
6934 const EPOCH_HEIGHT: u64 = 10;
6935
6936 let network_config = TestConfigBuilder::default()
6937 .epoch_height(EPOCH_HEIGHT)
6938 .build();
6939
6940 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
6941
6942 tracing::info!("API PORT = {api_port}");
6943 const NUM_NODES: usize = 5;
6944
6945 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
6946 let persistence: [_; NUM_NODES] = storage
6947 .iter()
6948 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
6949 .collect::<Vec<_>>()
6950 .try_into()
6951 .unwrap();
6952
6953 let decaf_state = ValidatedState {
6957 chain_config: ChainConfig {
6958 chain_id: DECAF_CHAIN_ID,
6959 ..Default::default()
6960 }
6961 .into(),
6962 ..Default::default()
6963 };
6964
6965 let config = TestNetworkConfigBuilder::with_num_nodes()
6966 .api_config(SqlDataSource::options(
6967 &storage[0],
6968 Options::with_port(api_port)
6969 .catchup(Default::default())
6970 .light_client(Default::default()),
6971 ))
6972 .network_config(network_config)
6973 .persistences(persistence.clone())
6974 .states(std::array::from_fn(|_| decaf_state.clone()))
6975 .catchups(std::array::from_fn(|_| {
6976 StatePeers::<StaticVersion<0, 1>>::from_urls(
6977 vec![format!("http://localhost:{api_port}").parse().unwrap()],
6978 Default::default(),
6979 Duration::from_secs(2),
6980 &NoMetrics,
6981 )
6982 }))
6983 .pos_hook(
6984 DelegationConfig::MultipleDelegators,
6985 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
6986 upgrade,
6987 )
6988 .await
6989 .unwrap()
6990 .build();
6991 let state = config.states()[0].clone();
6992 let mut network = TestNetwork::new(config, upgrade).await;
6993
6994 network.peers[0].shut_down().await;
6999 network.peers.remove(0);
7000 let node_0_storage = &storage[1];
7001 let node_0_persistence = persistence[1].clone();
7002 let node_0_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7003 tracing::info!("node_0_port {node_0_port}");
7004 let opt = Options::with_port(node_0_port).query_sql(
7006 Query {
7007 peers: vec![format!("http://localhost:{api_port}").parse().unwrap()],
7008 ..Default::default()
7009 },
7010 tmp_options(node_0_storage),
7011 );
7012
7013 let node_0 = opt
7015 .clone()
7016 .serve(|metrics, consumer, storage| {
7017 let cfg = network.cfg.clone();
7018 let node_0_persistence = node_0_persistence.clone();
7019 let state = state.clone();
7020 async move {
7021 Ok(cfg
7022 .init_node(
7023 1,
7024 state,
7025 node_0_persistence.clone(),
7026 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
7027 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7028 Default::default(),
7029 Duration::from_secs(2),
7030 &NoMetrics,
7031 )),
7032 storage,
7033 &*metrics,
7034 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
7035 consumer,
7036 upgrade,
7037 Default::default(),
7038 )
7039 .await)
7040 }
7041 .boxed()
7042 })
7043 .await
7044 .unwrap();
7045
7046 let mut events = network.peers[2].event_stream();
7047 wait_for_epochs(&mut events, EPOCH_HEIGHT, 1).await;
7049
7050 drop(node_0);
7052
7053 wait_for_epochs(&mut events, EPOCH_HEIGHT, 4).await;
7055
7056 tracing::info!("restarting node");
7058 let node_0 = opt
7059 .serve(|metrics, consumer, storage| {
7060 let cfg = network.cfg.clone();
7061 async move {
7062 Ok(cfg
7063 .init_node(
7064 1,
7065 state,
7066 node_0_persistence,
7067 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
7068 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7069 Default::default(),
7070 Duration::from_secs(2),
7071 &NoMetrics,
7072 )),
7073 storage,
7074 &*metrics,
7075 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
7076 consumer,
7077 upgrade,
7078 Default::default(),
7079 )
7080 .await)
7081 }
7082 .boxed()
7083 })
7084 .await
7085 .unwrap();
7086
7087 let client: Client<ClientErr, SequencerApiVersion> =
7088 Client::new(format!("http://localhost:{node_0_port}").parse().unwrap());
7089 client.connect(None).await;
7090
7091 wait_for_epochs(&mut events, EPOCH_HEIGHT, 6).await;
7092
7093 let epoch_7_block = EPOCH_HEIGHT * 6 + 1;
7094
7095 let mut retries = 0;
7097 loop {
7098 sleep(Duration::from_secs(1)).await;
7099 let state = node_0.decided_state().await.unwrap();
7100
7101 let leaves = if upgrade.base == EPOCH_VERSION {
7102 state.reward_merkle_tree_v1.num_leaves()
7104 } else {
7105 state.reward_merkle_tree_v2.num_leaves()
7107 };
7108
7109 if leaves > 0 {
7110 tracing::info!("Node's state has reward accounts");
7111 break;
7112 }
7113
7114 retries += 1;
7115 if retries > 120 {
7116 panic!("max retries reached. failed to catchup reward state");
7117 }
7118 }
7119
7120 retries = 0;
7121 loop {
7123 sleep(Duration::from_secs(3)).await;
7124
7125 let bh = client
7126 .get::<u64>("block-state/block-height")
7127 .send()
7128 .await
7129 .expect("block height not found");
7130
7131 tracing::info!("block state: block height={bh}");
7132 if bh > epoch_7_block {
7133 break;
7134 }
7135
7136 retries += 1;
7137 if retries > 30 {
7138 panic!(
7139 "max retries reached. block state block height is less than epoch 7 start \
7140 block"
7141 );
7142 }
7143 }
7144
7145 node_0.shutdown_consensus().await;
7147 let decided_leaf = node_0.decided_leaf().await;
7148 let state = node_0.decided_state().await.unwrap();
7149 tracing::info!(
7150 height = decided_leaf.height(),
7151 ?decided_leaf,
7152 ?state,
7153 "final state"
7154 );
7155
7156 let height = decided_leaf.height();
7157 let num_leaves = state.block_merkle_tree.num_leaves();
7158 tracing::info!(height, num_leaves, "checking block merkle tree state");
7159 state
7160 .block_merkle_tree
7161 .lookup(height - 1)
7162 .expect_ok()
7163 .unwrap_or_else(|err| {
7164 panic!(
7165 "block state not found ({err:#}):\n{:#?}",
7166 state.block_merkle_tree
7167 )
7168 });
7169
7170 Ok(())
7171 }
7172
7173 #[rstest]
7174 #[case(POS_V4)]
7175 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7176 async fn test_state_reconstruction(#[case] upgrade: Upgrade) -> anyhow::Result<()> {
7177 const EPOCH_HEIGHT: u64 = 10;
7193
7194 let network_config = TestConfigBuilder::default()
7195 .epoch_height(EPOCH_HEIGHT)
7196 .build();
7197
7198 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7199
7200 tracing::info!("API PORT = {api_port}");
7201 const NUM_NODES: usize = 5;
7202
7203 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
7204 let persistence: [_; NUM_NODES] = storage
7205 .iter()
7206 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
7207 .collect::<Vec<_>>()
7208 .try_into()
7209 .unwrap();
7210
7211 let config = TestNetworkConfigBuilder::with_num_nodes()
7212 .api_config(SqlDataSource::options(
7213 &storage[0],
7214 Options::with_port(api_port).light_client(Default::default()),
7215 ))
7216 .network_config(network_config)
7217 .persistences(persistence.clone())
7218 .catchups(std::array::from_fn(|_| {
7219 StatePeers::<StaticVersion<0, 1>>::from_urls(
7220 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7221 Default::default(),
7222 Duration::from_secs(2),
7223 &NoMetrics,
7224 )
7225 }))
7226 .pos_hook(
7227 DelegationConfig::MultipleDelegators,
7228 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
7229 upgrade,
7230 )
7231 .await
7232 .unwrap()
7233 .build();
7234 let state = config.states()[0].clone();
7235 let mut network = TestNetwork::new(config, upgrade).await;
7236 network.peers[0].shut_down().await;
7243 network.peers.remove(0);
7244
7245 let node_0_storage = &storage[1];
7246 let node_0_persistence = persistence[1].clone();
7247 let node_0_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7248 tracing::info!("node_0_port {node_0_port}");
7249 let opt = Options::with_port(node_0_port).query_sql(
7250 Query {
7251 peers: vec![format!("http://localhost:{api_port}").parse().unwrap()],
7252 ..Query::test()
7253 },
7254 tmp_options(node_0_storage),
7255 );
7256 let node_0 = opt
7257 .clone()
7258 .serve(|metrics, consumer, storage| {
7259 let cfg = network.cfg.clone();
7260 let node_0_persistence = node_0_persistence.clone();
7261 let state = state.clone();
7262 async move {
7263 Ok(cfg
7264 .init_node(
7265 1,
7266 state,
7267 node_0_persistence.clone(),
7268 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
7269 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7270 Default::default(),
7271 Duration::from_secs(2),
7272 &NoMetrics,
7273 )),
7274 storage,
7275 &*metrics,
7276 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
7277 consumer,
7278 upgrade,
7279 Default::default(),
7280 )
7281 .await)
7282 }
7283 .boxed()
7284 })
7285 .await
7286 .unwrap();
7287
7288 let mut events = network.peers[2].event_stream();
7289 wait_for_epochs(&mut events, EPOCH_HEIGHT, 3).await;
7291
7292 tracing::warn!("shutting down node 0");
7293
7294 node_0.shutdown_consensus().await;
7295
7296 let instance = node_0.node_state();
7297 let state = node_0.decided_state().await.unwrap();
7298 let fee_accounts = state
7299 .fee_merkle_tree
7300 .clone()
7301 .into_iter()
7302 .map(|(acct, _)| acct)
7303 .collect::<Vec<_>>();
7304 let reward_accounts = match upgrade.base {
7305 EPOCH_VERSION => state
7306 .reward_merkle_tree_v1
7307 .clone()
7308 .into_iter()
7309 .map(|(acct, _)| RewardAccountV2::from(acct))
7310 .collect::<Vec<_>>(),
7311 DRB_AND_HEADER_UPGRADE_VERSION => state
7312 .reward_merkle_tree_v2
7313 .clone()
7314 .into_iter()
7315 .map(|(acct, _)| acct)
7316 .collect::<Vec<_>>(),
7317 _ => panic!("invalid version"),
7318 };
7319
7320 let client: Client<ClientErr, SequencerApiVersion> =
7321 Client::new(format!("http://localhost:{node_0_port}").parse().unwrap());
7322 client.connect(Some(Duration::from_secs(10))).await;
7323
7324 sleep(Duration::from_secs(3)).await;
7327
7328 tracing::info!("getting node block height");
7329 let node_block_height = client
7330 .get::<u64>("node/block-height")
7331 .send()
7332 .await
7333 .context("getting Espresso block height")
7334 .unwrap();
7335
7336 tracing::info!("node block height={node_block_height}");
7337
7338 let leaf_query_data = client
7339 .get::<LeafQueryData<SeqTypes>>(&format!("availability/leaf/{}", node_block_height - 1))
7340 .send()
7341 .await
7342 .context("error getting leaf")
7343 .unwrap();
7344
7345 tracing::info!("leaf={leaf_query_data:?}");
7346 let leaf = leaf_query_data.leaf();
7347 let to_view = leaf.view_number() + 1;
7348
7349 let ds = SqlStorage::connect(
7350 Config::try_from(&node_0_persistence).unwrap(),
7351 StorageConnectionType::Sequencer,
7352 )
7353 .await
7354 .unwrap();
7355 let mut tx = ds.read().await?;
7356
7357 let (state, leaf) = reconstruct_state(
7358 &instance,
7359 &ds,
7360 &mut tx,
7361 node_block_height - 1,
7362 to_view,
7363 &[],
7364 &[],
7365 )
7366 .await
7367 .unwrap();
7368 assert_eq!(leaf.view_number(), to_view);
7369 assert!(
7370 state
7371 .block_merkle_tree
7372 .lookup(node_block_height - 1)
7373 .expect_ok()
7374 .is_ok(),
7375 "inconsistent block merkle tree"
7376 );
7377
7378 let (state, leaf) = reconstruct_state(
7380 &instance,
7381 &ds,
7382 &mut tx,
7383 node_block_height - 1,
7384 to_view,
7385 &fee_accounts,
7386 &[],
7387 )
7388 .await
7389 .unwrap();
7390
7391 assert_eq!(leaf.view_number(), to_view);
7392 assert!(
7393 state
7394 .block_merkle_tree
7395 .lookup(node_block_height - 1)
7396 .expect_ok()
7397 .is_ok(),
7398 "inconsistent block merkle tree"
7399 );
7400
7401 for account in &fee_accounts {
7402 state.fee_merkle_tree.lookup(account).expect_ok().unwrap();
7403 }
7404
7405 let (state, leaf) = reconstruct_state(
7408 &instance,
7409 &ds,
7410 &mut tx,
7411 node_block_height - 1,
7412 to_view,
7413 &[],
7414 &reward_accounts,
7415 )
7416 .await
7417 .unwrap();
7418
7419 match upgrade.base {
7420 EPOCH_VERSION => {
7421 for account in reward_accounts.clone() {
7422 state
7423 .reward_merkle_tree_v1
7424 .lookup(RewardAccountV1::from(account))
7425 .expect_ok()
7426 .unwrap();
7427 }
7428 },
7429 DRB_AND_HEADER_UPGRADE_VERSION => {
7430 for account in &reward_accounts {
7431 state
7432 .reward_merkle_tree_v2
7433 .lookup(account)
7434 .expect_ok()
7435 .unwrap();
7436 }
7437 },
7438 _ => panic!("invalid version"),
7439 };
7440
7441 assert_eq!(leaf.view_number(), to_view);
7442 assert!(
7443 state
7444 .block_merkle_tree
7445 .lookup(node_block_height - 1)
7446 .expect_ok()
7447 .is_ok(),
7448 "inconsistent block merkle tree"
7449 );
7450 let (state, leaf) = reconstruct_state(
7453 &instance,
7454 &ds,
7455 &mut tx,
7456 node_block_height - 1,
7457 to_view,
7458 &fee_accounts,
7459 &reward_accounts,
7460 )
7461 .await
7462 .unwrap();
7463
7464 assert!(
7465 state
7466 .block_merkle_tree
7467 .lookup(node_block_height - 1)
7468 .expect_ok()
7469 .is_ok(),
7470 "inconsistent block merkle tree"
7471 );
7472 assert_eq!(leaf.view_number(), to_view);
7473
7474 match upgrade.base {
7475 EPOCH_VERSION => {
7476 for account in reward_accounts.clone() {
7477 state
7478 .reward_merkle_tree_v1
7479 .lookup(RewardAccountV1::from(account))
7480 .expect_ok()
7481 .unwrap();
7482 }
7483 },
7484 DRB_AND_HEADER_UPGRADE_VERSION => {
7485 for account in &reward_accounts {
7486 state
7487 .reward_merkle_tree_v2
7488 .lookup(account)
7489 .expect_ok()
7490 .unwrap();
7491 }
7492 },
7493 _ => panic!("invalid version"),
7494 };
7495
7496 for account in &fee_accounts {
7497 state.fee_merkle_tree.lookup(account).expect_ok().unwrap();
7498 }
7499
7500 Ok(())
7501 }
7502
7503 #[rstest]
7504 #[case(POS_V3)]
7505 #[case(POS_V4)]
7506 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7507 async fn test_block_reward_api(#[case] upgrade: Upgrade) -> anyhow::Result<()> {
7508 let epoch_height = 10;
7509
7510 let network_config = TestConfigBuilder::default()
7511 .epoch_height(epoch_height)
7512 .build();
7513
7514 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7515
7516 const NUM_NODES: usize = 1;
7517 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
7519 let persistence: [_; NUM_NODES] = storage
7520 .iter()
7521 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
7522 .collect::<Vec<_>>()
7523 .try_into()
7524 .unwrap();
7525
7526 let config = TestNetworkConfigBuilder::with_num_nodes()
7527 .api_config(SqlDataSource::options(
7528 &storage[0],
7529 Options::with_port(api_port),
7530 ))
7531 .network_config(network_config.clone())
7532 .persistences(persistence.clone())
7533 .catchups(std::array::from_fn(|_| {
7534 StatePeers::<StaticVersion<0, 1>>::from_urls(
7535 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7536 Default::default(),
7537 Duration::from_secs(2),
7538 &NoMetrics,
7539 )
7540 }))
7541 .pos_hook(
7542 DelegationConfig::VariableAmounts,
7543 Default::default(),
7544 upgrade,
7545 )
7546 .await
7547 .unwrap()
7548 .build();
7549
7550 let _network = TestNetwork::new(config, upgrade).await;
7551 let client: Client<ClientErr, SequencerApiVersion> =
7552 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
7553
7554 let _blocks = client
7555 .socket("availability/stream/blocks/0")
7556 .subscribe::<BlockQueryData<SeqTypes>>()
7557 .await
7558 .unwrap()
7559 .take(3)
7560 .try_collect::<Vec<_>>()
7561 .await
7562 .unwrap();
7563
7564 let block_reward = client
7565 .get::<Option<RewardAmount>>("node/block-reward")
7566 .send()
7567 .await
7568 .expect("failed to get block reward")
7569 .expect("block reward is None");
7570 tracing::info!("block_reward={block_reward:?}");
7571
7572 assert!(block_reward.0 > U256::ZERO);
7573
7574 Ok(())
7575 }
7576
7577 #[rstest]
7579 #[case(POS_V4, None)]
7580 #[case(POS_V4, Some(1u64))]
7581 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7582 async fn test_token_supply_api(
7583 #[case] upgrade: Upgrade,
7584 #[case] chain_id: Option<u64>,
7585 ) -> anyhow::Result<()> {
7586 use alloy::primitives::utils::parse_ether;
7587 use espresso_types::v0_3::ChainConfig;
7588
7589 let epoch_height = 10;
7590 let network_config = TestConfigBuilder::default()
7591 .epoch_height(epoch_height)
7592 .build();
7593
7594 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7595
7596 const NUM_NODES: usize = 1;
7597 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
7598 let persistence: [_; NUM_NODES] = storage
7599 .iter()
7600 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
7601 .collect::<Vec<_>>()
7602 .try_into()
7603 .unwrap();
7604
7605 let initial_supply_tokens = U256::from(3_590_000_000u64);
7608 let initial_supply_wei = parse_ether("3590000000").unwrap();
7609
7610 let mut builder = TestNetworkConfigBuilder::with_num_nodes()
7611 .api_config(SqlDataSource::options(
7612 &storage[0],
7613 Options::with_port(api_port),
7614 ))
7615 .network_config(network_config.clone())
7616 .persistences(persistence.clone())
7617 .catchups(std::array::from_fn(|_| {
7618 StatePeers::<StaticVersion<0, 1>>::from_urls(
7619 vec![format!("http://localhost:{api_port}").parse().unwrap()],
7620 Default::default(),
7621 Duration::from_secs(2),
7622 &NoMetrics,
7623 )
7624 }))
7625 .initial_token_supply(initial_supply_tokens);
7626
7627 if let Some(id) = chain_id {
7629 let state = ValidatedState {
7630 chain_config: ChainConfig {
7631 chain_id: U256::from(id).into(),
7632 ..Default::default()
7633 }
7634 .into(),
7635 ..Default::default()
7636 };
7637 builder = builder.states(std::array::from_fn(|_| state.clone()));
7638 }
7639
7640 let config = builder
7641 .pos_hook(
7642 DelegationConfig::VariableAmounts,
7643 Default::default(),
7644 upgrade,
7645 )
7646 .await
7647 .unwrap()
7648 .build();
7649
7650 let _network = TestNetwork::new(config, upgrade).await;
7651 let client: Client<ClientErr, SequencerApiVersion> =
7652 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
7653
7654 let _blocks = client
7655 .socket("availability/stream/blocks/0")
7656 .subscribe::<BlockQueryData<SeqTypes>>()
7657 .await
7658 .unwrap()
7659 .take(3)
7660 .try_collect::<Vec<_>>()
7661 .await
7662 .unwrap();
7663
7664 let minted: String = client
7665 .get("token/total-minted-supply")
7666 .send()
7667 .await
7668 .expect("total-minted-supply");
7669 let circ_eth: String = client
7670 .get("token/circulating-supply-ethereum")
7671 .send()
7672 .await
7673 .expect("circulating-supply-ethereum");
7674 let circulating: String = client
7675 .get("token/circulating-supply")
7676 .send()
7677 .await
7678 .expect("circulating-supply");
7679 tracing::info!(%minted, %circ_eth, %circulating);
7680
7681 let minted = parse_ether(&minted)?;
7682 let circ_eth = parse_ether(&circ_eth)?;
7683 let circ = parse_ether(&circulating)?;
7684
7685 assert_eq!(minted, initial_supply_wei);
7686 assert!(circ_eth <= minted);
7687 assert!(circ >= circ_eth);
7688 assert!(circ > U256::ZERO);
7689
7690 if chain_id == Some(1) {
7691 assert!(circ_eth < minted);
7694 }
7695
7696 Ok(())
7697 }
7698
7699 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7700 async fn test_scanning_token_contract_initialized_event() -> anyhow::Result<()> {
7701 use espresso_types::v0_3::ChainConfig;
7702
7703 let blocks_per_epoch = 10;
7704
7705 let network_config = TestConfigBuilder::<1>::default()
7706 .epoch_height(blocks_per_epoch)
7707 .build();
7708
7709 let (genesis_state, genesis_stake) = light_client_genesis_from_stake_table(
7710 &network_config.hotshot_config().hotshot_stake_table(),
7711 STAKE_TABLE_CAPACITY_FOR_TEST,
7712 )
7713 .unwrap();
7714
7715 let deployer = ProviderBuilder::new()
7716 .wallet(EthereumWallet::from(network_config.signer().clone()))
7717 .connect_http(network_config.l1_url().clone());
7718
7719 let mut contracts = Contracts::new();
7720 let args = DeployerArgsBuilder::default()
7721 .deployer(deployer.clone())
7722 .rpc_url(network_config.l1_url().clone())
7723 .mock_light_client(true)
7724 .genesis_lc_state(genesis_state)
7725 .genesis_st_state(genesis_stake)
7726 .blocks_per_epoch(blocks_per_epoch)
7727 .epoch_start_block(1)
7728 .multisig_pauser(network_config.signer().address())
7729 .token_name("Espresso".to_string())
7730 .token_symbol("ESP".to_string())
7731 .initial_token_supply(U256::from(3590000000u64))
7732 .ops_timelock_delay(U256::from(0))
7733 .ops_timelock_admin(network_config.signer().address())
7734 .ops_timelock_proposers(vec![network_config.signer().address()])
7735 .ops_timelock_executors(vec![network_config.signer().address()])
7736 .safe_exit_timelock_delay(U256::from(0))
7737 .safe_exit_timelock_admin(network_config.signer().address())
7738 .safe_exit_timelock_proposers(vec![network_config.signer().address()])
7739 .safe_exit_timelock_executors(vec![network_config.signer().address()])
7740 .build()
7741 .unwrap();
7742
7743 args.deploy_to_stake_table_v3(&mut contracts).await.unwrap();
7744
7745 let st_addr = contracts
7746 .address(Contract::StakeTableProxy)
7747 .expect("StakeTableProxy deployed");
7748
7749 let l1_url = network_config.l1_url().clone();
7750
7751 let storage = SqlDataSource::create_storage().await;
7752 let mut opt = <SqlDataSource as TestableSequencerDataSource>::persistence_options(&storage);
7753 let persistence = opt.create().await.unwrap();
7754
7755 let l1_client = L1ClientOptions {
7756 stake_table_update_interval: Duration::from_secs(7),
7757 l1_retry_delay: Duration::from_millis(10),
7758 l1_events_max_block_range: 10000,
7759 ..Default::default()
7760 }
7761 .connect(vec![l1_url])
7762 .unwrap();
7763 l1_client.spawn_tasks().await;
7764
7765 let fetcher = Fetcher::new(
7766 Arc::new(NullStateCatchup::default()),
7767 Arc::new(Mutex::new(persistence.clone())),
7768 l1_client.clone(),
7769 ChainConfig {
7770 stake_table_contract: Some(st_addr),
7771 base_fee: 0.into(),
7772 ..Default::default()
7773 },
7774 );
7775
7776 let provider = l1_client.provider;
7777 let stake_table = StakeTableV3::new(st_addr, provider.clone());
7778
7779 let stake_table_init_block = stake_table
7780 .initializedAtBlock()
7781 .block(BlockId::finalized())
7782 .call()
7783 .await?
7784 .to::<u64>();
7785
7786 tracing::info!("stake table init block = {stake_table_init_block}");
7787
7788 let token_address = stake_table
7789 .token()
7790 .block(BlockId::finalized())
7791 .call()
7792 .await
7793 .context("Failed to get token address")?;
7794
7795 let token = EspToken::new(token_address, provider.clone());
7796
7797 let init_log = fetcher
7798 .scan_token_contract_initialized_event_log(stake_table_init_block, token.clone())
7799 .await
7800 .unwrap();
7801
7802 let init_block = init_log.block_number.context("missing block number")?;
7803 let init_tx_hash = init_log
7804 .transaction_hash
7805 .context("missing transaction hash")?;
7806
7807 let transfer_logs = token
7808 .Transfer_filter()
7809 .from_block(init_block)
7810 .to_block(init_block)
7811 .query()
7812 .await
7813 .unwrap();
7814
7815 let (mint_transfer, _) = transfer_logs
7816 .iter()
7817 .find(|(transfer, log)| {
7818 log.transaction_hash == Some(init_tx_hash) && transfer.from == Address::ZERO
7819 })
7820 .context("no mint transfer event in init tx")?;
7821
7822 assert!(mint_transfer.value > U256::ZERO);
7823
7824 Ok(())
7825 }
7826
7827 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7828 async fn test_tx_metadata() {
7829 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7830
7831 let url = format!("http://localhost:{port}").parse().unwrap();
7832 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
7833
7834 let storage = SqlDataSource::create_storage().await;
7835 let network_config = TestConfigBuilder::default().build();
7836 let config = TestNetworkConfigBuilder::default()
7837 .api_config(
7838 SqlDataSource::options(&storage, Options::with_port(port))
7839 .submit(Default::default())
7840 .explorer(Default::default()),
7841 )
7842 .network_config(network_config)
7843 .build();
7844 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
7845 let mut events = network.server.event_stream();
7846
7847 client.connect(None).await;
7848
7849 let namespace_counts = [(101, 1), (102, 2), (103, 3)];
7851 for (ns, count) in &namespace_counts {
7852 for i in 0..*count {
7853 let ns_id = NamespaceId::from(*ns as u64);
7854 let txn = Transaction::new(ns_id, vec![*ns, i]);
7855 client
7856 .post::<()>("submit/submit")
7857 .body_json(&txn)
7858 .unwrap()
7859 .send()
7860 .await
7861 .unwrap();
7862 let (block, _) = wait_for_decide_on_handle(&mut events, &txn).await;
7863
7864 let summary: BlockSummaryQueryData<SeqTypes> = client
7866 .get(&format!("availability/block/summary/{block}"))
7867 .send()
7868 .await
7869 .unwrap();
7870 let ns_info = summary.namespaces();
7871 assert_eq!(ns_info.len(), 1);
7872 assert_eq!(ns_info.keys().copied().collect::<Vec<_>>(), vec![ns_id]);
7873 assert_eq!(ns_info[&ns_id].num_transactions, 1);
7874 assert_eq!(ns_info[&ns_id].size, txn.size_in_block(true));
7875 }
7876 }
7877
7878 for (ns, count) in &namespace_counts {
7880 tracing::info!(ns, "list transactions in namespace");
7881
7882 let ns_id = NamespaceId::from(*ns as u64);
7883 let summaries: TransactionSummariesResponse<SeqTypes> = client
7884 .get(&format!(
7885 "explorer/transactions/latest/{count}/namespace/{ns_id}"
7886 ))
7887 .send()
7888 .await
7889 .unwrap();
7890 let txs = summaries.transaction_summaries;
7891 assert_eq!(txs.len(), *count as usize);
7892
7893 for i in 0..*count {
7895 let summary = &txs[i as usize];
7896 let expected = Transaction::new(ns_id, vec![*ns, count - i - 1]);
7897 assert_eq!(summary.rollups, vec![ns_id]);
7898 assert_eq!(summary.hash, expected.commit());
7899 }
7900 }
7901 }
7902
7903 use rand::thread_rng;
7904
7905 #[test_log::test(tokio::test(flavor = "multi_thread"))]
7906 async fn test_aggregator_namespace_endpoints() {
7907 let mut rng = thread_rng();
7908
7909 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
7910
7911 let url = format!("http://localhost:{port}").parse().unwrap();
7912 tracing::info!("Sequencer URL = {url}");
7913 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
7914
7915 let options = Options::with_port(port).submit(Default::default());
7916 const NUM_NODES: usize = 2;
7917 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
7919
7920 let persistence_options: [_; NUM_NODES] = storage
7921 .iter()
7922 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
7923 .collect::<Vec<_>>()
7924 .try_into()
7925 .unwrap();
7926
7927 let network_config = TestConfigBuilder::default().build();
7928
7929 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
7930 .api_config(SqlDataSource::options(&storage[0], options))
7931 .network_config(network_config)
7932 .persistences(persistence_options.clone())
7933 .build();
7934 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
7935 let mut events = network.server.event_stream();
7936 let start = Instant::now();
7937 let mut total_transactions = 0;
7938 let mut tx_heights = Vec::new();
7939 let mut sizes = HashMap::new();
7940 for namespace in 1..=4 {
7943 for _count in 0..namespace {
7944 let payload_len = rng.gen_range(4..=10);
7946 let payload: Vec<u8> = (0..payload_len).map(|_| rng.r#gen()).collect();
7947
7948 let txn = Transaction::new(NamespaceId::from(namespace as u32), payload);
7949
7950 client.connect(None).await;
7951
7952 let hash = client
7953 .post("submit/submit")
7954 .body_json(&txn)
7955 .unwrap()
7956 .send()
7957 .await
7958 .unwrap();
7959 assert_eq!(txn.commit(), hash);
7960
7961 let (height, size) = wait_for_decide_on_handle(&mut events, &txn).await;
7963 tx_heights.push(height);
7964 total_transactions += 1;
7965 *sizes.entry(namespace).or_insert(0) += size;
7966 }
7967 }
7968
7969 let duration = start.elapsed();
7970
7971 println!("Time elapsed to submit transactions: {duration:?}");
7972
7973 let last_tx_height = tx_heights.last().unwrap();
7974
7975 let aggregator_deadline = Instant::now() + Duration::from_secs(30);
7980 loop {
7981 let count = client
7982 .get::<u64>(&format!("node/transactions/count/{last_tx_height}"))
7983 .send()
7984 .await
7985 .ok();
7986 if count == Some(total_transactions) {
7987 break;
7988 }
7989 assert!(
7990 Instant::now() < aggregator_deadline,
7991 "aggregator did not catch up to height {last_tx_height} (got {count:?}, expected \
7992 {total_transactions})"
7993 );
7994 sleep(Duration::from_secs(1)).await;
7995 }
7996
7997 for namespace in 1..=4 {
7998 let count = client
7999 .get::<u64>(&format!("node/transactions/count/namespace/{namespace}"))
8000 .send()
8001 .await
8002 .unwrap();
8003 assert_eq!(
8004 count, namespace as u64,
8005 "Incorrect transaction count for namespace {namespace}: expected {namespace}, got \
8006 {count}"
8007 );
8008
8009 let to_endpoint_count = client
8011 .get::<u64>(&format!(
8012 "node/transactions/count/namespace/{namespace}/{last_tx_height}"
8013 ))
8014 .send()
8015 .await
8016 .unwrap();
8017 assert_eq!(
8018 to_endpoint_count, namespace as u64,
8019 "Incorrect transaction count for range endpoint (to only) for namespace \
8020 {namespace}: expected {namespace}, got {to_endpoint_count}"
8021 );
8022
8023 let from_to_endpoint_count = client
8025 .get::<u64>(&format!(
8026 "node/transactions/count/namespace/{namespace}/0/{last_tx_height}"
8027 ))
8028 .send()
8029 .await
8030 .unwrap();
8031 assert_eq!(
8032 from_to_endpoint_count, namespace as u64,
8033 "Incorrect transaction count for range endpoint (from-to) for namespace \
8034 {namespace}: expected {namespace}, got {from_to_endpoint_count}"
8035 );
8036
8037 let ns_size = client
8038 .get::<usize>(&format!("node/payloads/size/namespace/{namespace}"))
8039 .send()
8040 .await
8041 .unwrap();
8042
8043 let expected_ns_size = *sizes.get(&namespace).unwrap();
8044 assert_eq!(
8045 ns_size, expected_ns_size,
8046 "Incorrect payload size for namespace {namespace}: expected {expected_ns_size}, \
8047 got {ns_size}"
8048 );
8049
8050 let ns_size_to = client
8051 .get::<usize>(&format!(
8052 "node/payloads/size/namespace/{namespace}/{last_tx_height}"
8053 ))
8054 .send()
8055 .await
8056 .unwrap();
8057 assert_eq!(
8058 ns_size_to, expected_ns_size,
8059 "Incorrect payload size for namespace {namespace} up to height {last_tx_height}: \
8060 expected {expected_ns_size}, got {ns_size_to}"
8061 );
8062
8063 let ns_size_from_to = client
8064 .get::<usize>(&format!(
8065 "node/payloads/size/namespace/{namespace}/0/{last_tx_height}"
8066 ))
8067 .send()
8068 .await
8069 .unwrap();
8070 assert_eq!(
8071 ns_size_from_to, expected_ns_size,
8072 "Incorrect payload size for namespace {namespace} from 0 to height \
8073 {last_tx_height}: expected {expected_ns_size}, got {ns_size_from_to}"
8074 );
8075 }
8076
8077 let total_tx_count = client
8078 .get::<u64>("node/transactions/count")
8079 .send()
8080 .await
8081 .unwrap();
8082 assert_eq!(
8083 total_tx_count, total_transactions,
8084 "Incorrect total transaction count: expected {total_transactions}, got \
8085 {total_tx_count}"
8086 );
8087
8088 let total_payload_size = client
8089 .get::<usize>("node/payloads/size")
8090 .send()
8091 .await
8092 .unwrap();
8093
8094 let expected_total_size: usize = sizes.values().copied().sum();
8095 assert_eq!(
8096 total_payload_size, expected_total_size,
8097 "Incorrect total payload size: expected {expected_total_size}, got \
8098 {total_payload_size}"
8099 );
8100 }
8101
8102 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8103 async fn test_stream_transactions_endpoint() {
8104 let mut rng = thread_rng();
8110
8111 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8112
8113 let url = format!("http://localhost:{port}").parse().unwrap();
8114 tracing::info!("Sequencer URL = {url}");
8115 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
8116
8117 let options = Options::with_port(port).submit(Default::default());
8118 const NUM_NODES: usize = 2;
8119 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8121
8122 let persistence_options: [_; NUM_NODES] = storage
8123 .iter()
8124 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8125 .collect::<Vec<_>>()
8126 .try_into()
8127 .unwrap();
8128
8129 let network_config = TestConfigBuilder::default().build();
8130
8131 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
8132 .api_config(SqlDataSource::options(&storage[0], options))
8133 .network_config(network_config)
8134 .persistences(persistence_options.clone())
8135 .build();
8136 let network = TestNetwork::new(config, MOCK_SEQUENCER_VERSIONS).await;
8137 let mut events = network.server.event_stream();
8138 let mut all_transactions = HashMap::new();
8139 let mut namespace_tx: HashMap<_, HashSet<_>> = HashMap::new();
8140
8141 for namespace in 1..=4 {
8144 for _count in 0..namespace {
8145 let payload_len = rng.gen_range(4..=10);
8146 let payload: Vec<u8> = (0..payload_len).map(|_| rng.r#gen()).collect();
8147
8148 let txn = Transaction::new(NamespaceId::from(namespace as u32), payload);
8149
8150 client.connect(None).await;
8151
8152 let hash = client
8153 .post("submit/submit")
8154 .body_json(&txn)
8155 .unwrap()
8156 .send()
8157 .await
8158 .unwrap();
8159 assert_eq!(txn.commit(), hash);
8160
8161 wait_for_decide_on_handle(&mut events, &txn).await;
8163 all_transactions.insert(txn.commit(), txn.clone());
8166 namespace_tx.entry(namespace).or_default().insert(txn);
8167 }
8168 }
8169
8170 let mut transactions = client
8171 .socket("availability/stream/transactions/0")
8172 .subscribe::<TransactionQueryData<SeqTypes>>()
8173 .await
8174 .expect("failed to subscribe to transactions endpoint");
8175
8176 let mut count = 0;
8177 while let Some(tx) = transactions.next().await {
8178 let tx = tx.unwrap();
8179 let expected = all_transactions
8180 .get(&tx.transaction().commit())
8181 .expect("txn not found ");
8182 assert_eq!(tx.transaction(), expected, "invalid transaction");
8183 count += 1;
8184
8185 if count == all_transactions.len() {
8186 break;
8187 }
8188 }
8189
8190 for (namespace, expected_ns_txns) in &namespace_tx {
8193 let mut api_namespace_txns = client
8194 .socket(&format!(
8195 "availability/stream/transactions/0/namespace/{namespace}",
8196 ))
8197 .subscribe::<TransactionQueryData<SeqTypes>>()
8198 .await
8199 .unwrap_or_else(|_| {
8200 panic!("failed to subscribe to transactions namespace {namespace}")
8201 });
8202
8203 let mut received = HashSet::new();
8204
8205 while let Some(res) = api_namespace_txns.next().await {
8206 let tx = res.expect("stream error");
8207 received.insert(tx.transaction().clone());
8208
8209 if received.len() == expected_ns_txns.len() {
8210 break;
8211 }
8212 }
8213
8214 assert_eq!(
8215 received, *expected_ns_txns,
8216 "Mismatched transactions for namespace {namespace}"
8217 );
8218 }
8219 }
8220
8221 #[rstest]
8222 #[case(POS_V3)]
8223 #[case(POS_V4)]
8224 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8225 async fn test_v3_and_v4_reward_tree_updates(#[case] upgrade: Upgrade) -> anyhow::Result<()> {
8226 const EPOCH_HEIGHT: u64 = 10;
8236
8237 let network_config = TestConfigBuilder::default()
8238 .epoch_height(EPOCH_HEIGHT)
8239 .build();
8240
8241 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8242
8243 tracing::info!("API PORT = {api_port}");
8244 const NUM_NODES: usize = 5;
8245
8246 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8247 let persistence: [_; NUM_NODES] = storage
8248 .iter()
8249 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8250 .collect::<Vec<_>>()
8251 .try_into()
8252 .unwrap();
8253
8254 let config = TestNetworkConfigBuilder::with_num_nodes()
8255 .api_config(SqlDataSource::options(
8256 &storage[0],
8257 Options::with_port(api_port).catchup(Default::default()),
8258 ))
8259 .network_config(network_config)
8260 .persistences(persistence.clone())
8261 .catchups(std::array::from_fn(|_| {
8262 StatePeers::<StaticVersion<0, 1>>::from_urls(
8263 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8264 Default::default(),
8265 Duration::from_secs(2),
8266 &NoMetrics,
8267 )
8268 }))
8269 .pos_hook(
8270 DelegationConfig::MultipleDelegators,
8271 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
8272 upgrade,
8273 )
8274 .await
8275 .unwrap()
8276 .build();
8277 let mut network = TestNetwork::new(config, upgrade).await;
8278
8279 let mut events = network.peers[2].event_stream();
8280 wait_for_epochs(&mut events, EPOCH_HEIGHT, 4).await;
8282
8283 let validated_state = network.server.decided_state().await.unwrap();
8284 if upgrade.base == EPOCH_VERSION {
8285 let v1_tree = &validated_state.reward_merkle_tree_v1;
8286 assert!(v1_tree.num_leaves() > 0, "v1 reward tree tree is empty");
8287 let v2_tree = &validated_state.reward_merkle_tree_v2;
8288 assert!(
8289 v2_tree.num_leaves() == 0,
8290 "v2 reward tree tree is not empty"
8291 );
8292 } else {
8293 let v1_tree = &validated_state.reward_merkle_tree_v1;
8294 assert!(
8295 v1_tree.num_leaves() == 0,
8296 "v1 reward tree tree is not empty"
8297 );
8298 let v2_tree = &validated_state.reward_merkle_tree_v2;
8299 assert!(v2_tree.num_leaves() > 0, "v2 reward tree tree is empty");
8300 }
8301
8302 network.stop_consensus().await;
8303 Ok(())
8304 }
8305
8306 #[rstest]
8307 #[case(POS_V3)]
8308 #[case(POS_V4)]
8309 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8310 pub(crate) async fn test_state_cert_query(#[case] upgrade: Upgrade) {
8311 const TEST_EPOCH_HEIGHT: u64 = 10;
8312 const TEST_EPOCHS: u64 = 5;
8313
8314 let network_config = TestConfigBuilder::default()
8315 .epoch_height(TEST_EPOCH_HEIGHT)
8316 .build();
8317
8318 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8319
8320 tracing::info!("API PORT = {api_port}");
8321 const NUM_NODES: usize = 2;
8322
8323 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8324 let persistence: [_; NUM_NODES] = storage
8325 .iter()
8326 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8327 .collect::<Vec<_>>()
8328 .try_into()
8329 .unwrap();
8330
8331 let config = TestNetworkConfigBuilder::with_num_nodes()
8332 .api_config(SqlDataSource::options(
8333 &storage[0],
8334 Options::with_port(api_port).catchup(Default::default()),
8335 ))
8336 .network_config(network_config)
8337 .persistences(persistence.clone())
8338 .catchups(std::array::from_fn(|_| {
8339 StatePeers::<StaticVersion<0, 1>>::from_urls(
8340 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8341 Default::default(),
8342 Duration::from_secs(2),
8343 &NoMetrics,
8344 )
8345 }))
8346 .pos_hook(
8347 DelegationConfig::MultipleDelegators,
8348 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
8349 upgrade,
8350 )
8351 .await
8352 .unwrap()
8353 .build();
8354
8355 let network = TestNetwork::new(config, upgrade).await;
8356 let mut events = network.server.event_stream();
8357
8358 loop {
8360 let event = events.next().await.unwrap();
8361 tracing::info!("Received event from handle: {event:?}");
8362
8363 if let CoordinatorEvent::LegacyEvent(Event {
8364 event: EventType::Decide { leaf_chain, .. },
8365 ..
8366 }) = event
8367 {
8368 println!(
8369 "Decide event received: {:?}",
8370 leaf_chain.first().unwrap().leaf.height()
8371 );
8372 if let Some(first_leaf) = leaf_chain.first() {
8373 let height = first_leaf.leaf.height();
8374 tracing::info!("Decide event received at height: {height}");
8375
8376 if height >= TEST_EPOCHS * TEST_EPOCH_HEIGHT {
8377 break;
8378 }
8379 }
8380 }
8381 }
8382
8383 let client: Client<ClientErr, StaticVersion<0, 1>> =
8385 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
8386 client.connect(Some(Duration::from_secs(10))).await;
8387
8388 for i in 3..=TEST_EPOCHS {
8390 let state_query_data_v2 = client
8393 .get::<StateCertQueryDataV2<SeqTypes>>(&format!("availability/state-cert-v2/{i}"))
8394 .send()
8395 .await
8396 .unwrap();
8397 let state_cert_v2 = state_query_data_v2.0.clone();
8398 tracing::info!("state_cert_v2: {state_cert_v2:?}");
8399 assert_eq!(state_cert_v2.epoch.u64(), i);
8400 assert_eq!(
8401 state_cert_v2.light_client_state.block_height,
8402 i * TEST_EPOCH_HEIGHT - 5
8403 );
8404 let block_height = state_cert_v2.light_client_state.block_height;
8405
8406 let header: Header = client
8407 .get(&format!("availability/header/{block_height}"))
8408 .send()
8409 .await
8410 .unwrap();
8411
8412 if header.version() == DRB_AND_HEADER_UPGRADE_VERSION {
8414 let auth_root = state_cert_v2.auth_root;
8415 let header_auth_root = header.auth_root().unwrap();
8416 if auth_root.is_zero() || header_auth_root.is_zero() {
8417 panic!("auth root shouldn't be zero");
8418 }
8419
8420 assert_eq!(auth_root, header_auth_root, "auth root mismatch");
8421 }
8422
8423 let state_query_data_v1 = client
8425 .get::<StateCertQueryDataV1<SeqTypes>>(&format!("availability/state-cert/{i}"))
8426 .send()
8427 .await
8428 .unwrap();
8429
8430 let state_cert_v1 = state_query_data_v1.0.clone();
8431 tracing::info!("state_cert_v1: {state_cert_v1:?}");
8432 assert_eq!(state_query_data_v1, state_query_data_v2.into());
8433 }
8434 }
8435
8436 #[rstest]
8442 #[case(POS_V3)]
8443 #[case(POS_V4)]
8444 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8445 pub(crate) async fn test_state_cert_catchup(#[case] upgrade: Upgrade) {
8446 const EPOCH_HEIGHT: u64 = 10;
8447
8448 let network_config = TestConfigBuilder::default()
8449 .epoch_height(EPOCH_HEIGHT)
8450 .build();
8451
8452 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8453
8454 tracing::info!("API PORT = {api_port}");
8455 const NUM_NODES: usize = 5;
8456
8457 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8458 let persistence: [_; NUM_NODES] = storage
8459 .iter()
8460 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8461 .collect::<Vec<_>>()
8462 .try_into()
8463 .unwrap();
8464
8465 let config = TestNetworkConfigBuilder::with_num_nodes()
8466 .api_config(SqlDataSource::options(
8467 &storage[0],
8468 Options::with_port(api_port).light_client(Default::default()),
8469 ))
8470 .network_config(network_config)
8471 .persistences(persistence.clone())
8472 .catchups(std::array::from_fn(|_| {
8473 StatePeers::<StaticVersion<0, 1>>::from_urls(
8474 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8475 Default::default(),
8476 Duration::from_secs(2),
8477 &NoMetrics,
8478 )
8479 }))
8480 .pos_hook(
8481 DelegationConfig::MultipleDelegators,
8482 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
8483 upgrade,
8484 )
8485 .await
8486 .unwrap()
8487 .build();
8488 let state = config.states()[0].clone();
8489 let mut network = TestNetwork::new(config, upgrade).await;
8490
8491 let mut events = network.peers[2].event_stream();
8492 wait_for_epochs(&mut events, EPOCH_HEIGHT, 3).await;
8494
8495 network.peers.remove(0);
8500
8501 let new_storage: hotshot_query_service::data_source::sql::testing::TmpDb =
8502 SqlDataSource::create_storage().await;
8503 let new_persistence: persistence::sql::Options =
8504 <SqlDataSource as TestableSequencerDataSource>::persistence_options(&new_storage);
8505
8506 let node_0_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8507 tracing::info!("node_0_port {node_0_port}");
8508 let opt = Options::with_port(node_0_port).query_sql(
8509 Query {
8510 peers: vec![format!("http://localhost:{api_port}").parse().unwrap()],
8511 ..Query::test()
8512 },
8513 tmp_options(&new_storage),
8514 );
8515 let node_0 = opt
8516 .clone()
8517 .serve(|metrics, consumer, storage| {
8518 let cfg = network.cfg.clone();
8519 let new_persistence = new_persistence.clone();
8520 let state = state.clone();
8521 async move {
8522 Ok(cfg
8523 .init_node(
8524 1,
8525 state,
8526 new_persistence.clone(),
8527 Some(StatePeers::<StaticVersion<0, 1>>::from_urls(
8528 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8529 Default::default(),
8530 Duration::from_secs(2),
8531 &NoMetrics,
8532 )),
8533 storage,
8534 &*metrics,
8535 test_helpers::STAKE_TABLE_CAPACITY_FOR_TEST,
8536 consumer,
8537 upgrade,
8538 Default::default(),
8539 )
8540 .await)
8541 }
8542 .boxed()
8543 })
8544 .await
8545 .unwrap();
8546
8547 let mut events = node_0.event_stream();
8548 wait_for_epochs(&mut events, EPOCH_HEIGHT, 5).await;
8550
8551 let client: Client<ClientErr, StaticVersion<0, 1>> =
8552 Client::new(format!("http://localhost:{node_0_port}").parse().unwrap());
8553 client.connect(Some(Duration::from_secs(60))).await;
8554
8555 for epoch in 3..=5 {
8556 let state_cert = client
8557 .get::<StateCertQueryDataV2<SeqTypes>>(&format!(
8558 "availability/state-cert-v2/{epoch}"
8559 ))
8560 .send()
8561 .await
8562 .unwrap();
8563 assert_eq!(state_cert.0.epoch.u64(), epoch);
8564 }
8565 }
8566
8567 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8568 async fn test_integration_commission_updates() -> anyhow::Result<()> {
8569 const NUM_NODES: usize = 3;
8570 const EPOCH_HEIGHT: u64 = 10;
8571
8572 let versions = POS_V4;
8574
8575 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8576
8577 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8579 let persistence: [_; NUM_NODES] = storage
8580 .iter()
8581 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8582 .collect::<Vec<_>>()
8583 .try_into()
8584 .unwrap();
8585
8586 let network_config = TestConfigBuilder::default()
8588 .epoch_height(EPOCH_HEIGHT)
8589 .build();
8590
8591 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
8593 .api_config(SqlDataSource::options(
8594 &storage[0],
8595 Options::with_port(api_port),
8596 ))
8597 .network_config(network_config.clone())
8598 .persistences(persistence.clone())
8599 .catchups(std::array::from_fn(|_| {
8600 StatePeers::<SequencerApiVersion>::from_urls(
8601 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8602 Default::default(),
8603 Duration::from_secs(2),
8604 &NoMetrics,
8605 )
8606 }))
8607 .pos_hook(
8608 DelegationConfig::NoSelfDelegation,
8610 StakeTableContractVersion::V1, POS_V4,
8612 )
8613 .await
8614 .unwrap()
8615 .build();
8616
8617 let network = TestNetwork::new(config, versions).await;
8618 let provider = network.cfg.anvil().unwrap();
8619 let deployer_addr = network.cfg.signer().address();
8620 let mut contracts = network.contracts.unwrap();
8621 let st_addr = contracts.address(Contract::StakeTableProxy).unwrap();
8622 upgrade_stake_table_v2(
8623 provider,
8624 L1Client::new(vec![network.cfg.l1_url()])?,
8625 &mut contracts,
8626 deployer_addr,
8627 deployer_addr,
8628 )
8629 .await?;
8630
8631 let mut commissions = vec![];
8632 for (i, (validator, provider)) in
8633 network_config.validator_providers().into_iter().enumerate()
8634 {
8635 let commission = fetch_commission(provider.clone(), st_addr, validator).await?;
8636 let new_commission = match i {
8637 0 => 0u16,
8638 1 => commission.to_evm() + 500u16,
8639 2 => commission.to_evm() - 100u16,
8640 _ => unreachable!(),
8641 }
8642 .try_into()?;
8643 commissions.push((validator, commission, new_commission));
8644 tracing::info!(%validator, %commission, %new_commission, "Update commission");
8645 update_commission(provider, st_addr, new_commission)
8646 .await?
8647 .get_receipt()
8648 .await?;
8649 }
8650
8651 let current_epoch = network.peers[0]
8653 .decided_leaf()
8654 .await
8655 .epoch(EPOCH_HEIGHT)
8656 .unwrap();
8657 let target_epoch = current_epoch.u64() + 3;
8658 println!("target epoch for new stake table: {target_epoch}");
8659 let mut events = network.peers[0].event_stream();
8660 wait_for_epochs(&mut events, EPOCH_HEIGHT, target_epoch).await;
8661
8662 let client: Client<ClientErr, SequencerApiVersion> =
8664 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
8665 let validators = client
8666 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{}", target_epoch - 1))
8667 .send()
8668 .await
8669 .expect("validators");
8670 assert!(!validators.is_empty());
8671 for (val, old_comm, _) in commissions.clone() {
8672 assert_eq!(validators.get(&val).unwrap().commission, old_comm.to_evm());
8673 }
8674
8675 let client: Client<ClientErr, SequencerApiVersion> =
8677 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
8678 let validators = client
8679 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{target_epoch}"))
8680 .send()
8681 .await
8682 .expect("validators");
8683 assert!(!validators.is_empty());
8684 for (val, _, new_comm) in commissions.clone() {
8685 assert_eq!(validators.get(&val).unwrap().commission, new_comm.to_evm());
8686 }
8687
8688 let last_block_with_old_commissions = EPOCH_HEIGHT * (target_epoch - 1);
8689 let block_with_new_commissions = EPOCH_HEIGHT * target_epoch;
8690 let mut new_amounts = vec![];
8691 for (val, ..) in commissions {
8692 let before = client
8693 .get::<Option<RewardAmount>>(&format!(
8694 "reward-state-v2/reward-balance/{last_block_with_old_commissions}/{val}"
8695 ))
8696 .send()
8697 .await?
8698 .unwrap();
8699 let after = client
8700 .get::<Option<RewardAmount>>(&format!(
8701 "reward-state-v2/reward-balance/{block_with_new_commissions}/{val}"
8702 ))
8703 .send()
8704 .await?
8705 .unwrap();
8706 new_amounts.push(after - before);
8707 }
8708
8709 let tolerance = U256::from(10 * EPOCH_HEIGHT).into();
8710 assert!(new_amounts[0] < tolerance);
8712
8713 assert!(new_amounts[1] + new_amounts[2] > tolerance);
8715
8716 Ok(())
8717 }
8718
8719 #[test_log::test(tokio::test(flavor = "multi_thread"))]
8723 async fn test_integration_update_fast_finality_network_config() -> anyhow::Result<()> {
8724 const NUM_NODES: usize = 3;
8725 const EPOCH_HEIGHT: u64 = 10;
8726
8727 let versions = POS_V4;
8728 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
8729
8730 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
8731 let persistence: [_; NUM_NODES] = storage
8732 .iter()
8733 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
8734 .collect::<Vec<_>>()
8735 .try_into()
8736 .unwrap();
8737
8738 let network_config = TestConfigBuilder::default()
8739 .epoch_height(EPOCH_HEIGHT)
8740 .build();
8741
8742 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
8743 .api_config(SqlDataSource::options(
8744 &storage[0],
8745 Options::with_port(api_port),
8746 ))
8747 .network_config(network_config.clone())
8748 .persistences(persistence.clone())
8749 .catchups(std::array::from_fn(|_| {
8750 StatePeers::<SequencerApiVersion>::from_urls(
8751 vec![format!("http://localhost:{api_port}").parse().unwrap()],
8752 Default::default(),
8753 Duration::from_secs(2),
8754 &NoMetrics,
8755 )
8756 }))
8757 .pos_hook(
8758 DelegationConfig::MultipleDelegators,
8759 StakeTableContractVersion::V2,
8760 POS_V4,
8761 )
8762 .await
8763 .unwrap()
8764 .build();
8765
8766 let network = TestNetwork::new(config, versions).await;
8767 let provider = network.cfg.anvil().unwrap();
8768 let mut contracts = network.contracts.unwrap();
8769 let st_addr = contracts.address(Contract::StakeTableProxy).unwrap();
8770
8771 upgrade_stake_table_v3(provider, &mut contracts).await?;
8772
8773 let (validator, validator_provider) = network_config
8774 .validator_providers()
8775 .into_iter()
8776 .next()
8777 .unwrap();
8778 let x25519_key = x25519::Keypair::generate().unwrap().public_key();
8779 let p2p_addr: NetAddr = "127.0.0.1:9000".parse().unwrap();
8780 update_network_config(validator_provider, st_addr, x25519_key, p2p_addr.clone())
8781 .await?
8782 .get_receipt()
8783 .await?;
8784
8785 let current_epoch = network.peers[0]
8786 .decided_leaf()
8787 .await
8788 .epoch(EPOCH_HEIGHT)
8789 .unwrap();
8790 let target_epoch = current_epoch.u64() + 3;
8791 let mut events = network.peers[0].event_stream();
8792 wait_for_epochs(&mut events, EPOCH_HEIGHT, target_epoch).await;
8793
8794 let client: Client<ClientErr, SequencerApiVersion> =
8795 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
8796 let validators = client
8797 .get::<AuthenticatedValidatorMap>(&format!("node/validators/{target_epoch}"))
8798 .send()
8799 .await
8800 .expect("validators");
8801 let v = validators.get(&validator).expect("validator present");
8802 assert_eq!(v.x25519_key, Some(x25519_key));
8803 assert_eq!(v.p2p_addr, Some(p2p_addr));
8804
8805 Ok(())
8806 }
8807
8808 async fn assert_json_endpoint(
8810 http: &reqwest::Client,
8811 api_port: u16,
8812 path: &str,
8813 ) -> anyhow::Result<()> {
8814 let resp = http
8815 .get(format!("http://localhost:{api_port}/v1/{path}"))
8816 .send()
8817 .await?;
8818 let status = resp.status();
8819 assert!(
8820 status.is_success(),
8821 "v1/{path}: returned {status}, expected 2xx"
8822 );
8823 resp.json::<serde_json::Value>().await?;
8824 Ok(())
8825 }
8826
8827 async fn assert_json_body(
8831 http: &reqwest::Client,
8832 api_port: u16,
8833 path: &str,
8834 ) -> anyhow::Result<()> {
8835 http.get(format!("http://localhost:{api_port}/v1/{path}"))
8836 .send()
8837 .await?
8838 .json::<serde_json::Value>()
8839 .await?;
8840 Ok(())
8841 }
8842
8843 async fn assert_endpoint_status(
8845 http: &reqwest::Client,
8846 api_port: u16,
8847 path: &str,
8848 expected_status: u16,
8849 ) -> anyhow::Result<()> {
8850 let status = http
8851 .get(format!("http://localhost:{api_port}/v1/{path}"))
8852 .send()
8853 .await?
8854 .status()
8855 .as_u16();
8856 assert_eq!(
8857 status, expected_status,
8858 "v1/{path}: should return {expected_status}, got {status}"
8859 );
8860 Ok(())
8861 }
8862
8863 async fn assert_endpoint_ok(
8866 http: &reqwest::Client,
8867 api_port: u16,
8868 path: &str,
8869 ) -> anyhow::Result<()> {
8870 let status = http
8871 .get(format!("http://localhost:{api_port}/v1/{path}"))
8872 .send()
8873 .await?
8874 .status();
8875 assert!(
8876 status.is_success(),
8877 "v1/{path}: returned {status}, expected 2xx"
8878 );
8879 Ok(())
8880 }
8881
8882 async fn assert_error_body(
8885 http: &reqwest::Client,
8886 api_port: u16,
8887 path: &str,
8888 expected_status: u16,
8889 ) -> anyhow::Result<()> {
8890 let resp = http
8891 .get(format!("http://localhost:{api_port}/v1/{path}"))
8892 .send()
8893 .await?;
8894 let status = resp.status().as_u16();
8895 let body: serde_json::Value = resp.json().await?;
8896 assert_eq!(status, expected_status, "v1/{path}: status");
8897 let custom = body
8898 .get("Custom")
8899 .unwrap_or_else(|| panic!("v1/{path}: error body missing Custom envelope: {body}"));
8900 assert_eq!(
8901 custom.get("status").and_then(|s| s.as_u64()),
8902 Some(u64::from(expected_status)),
8903 "v1/{path}: envelope status: {body}"
8904 );
8905 assert!(
8906 custom.get("message").is_some_and(|m| m.is_string()),
8907 "v1/{path}: envelope message missing: {body}"
8908 );
8909 Ok(())
8910 }
8911
8912 async fn assert_post_binary<B: serde::Serialize>(
8918 http: &reqwest::Client,
8919 api_port: u16,
8920 path: &str,
8921 body: &B,
8922 ) -> anyhow::Result<()> {
8923 use vbs::{BinarySerializer, Serializer, version::StaticVersion};
8924 let payload = Serializer::<StaticVersion<0, 1>>::serialize(body)?;
8925 let resp = http
8926 .post(format!("http://localhost:{api_port}/v1/{path}"))
8927 .header("Content-Type", "application/octet-stream")
8928 .header("Accept", "application/octet-stream")
8929 .body(payload)
8930 .send()
8931 .await?;
8932 let status = resp.status();
8933 assert!(
8934 status.is_success(),
8935 "v1/{path}: binary POST returned {status}, expected 2xx"
8936 );
8937 Ok(())
8938 }
8939
8940 async fn assert_ws_endpoint(api_port: u16, path: &str) -> anyhow::Result<()> {
8943 use std::time::Duration;
8944
8945 use futures::StreamExt as _;
8946 use tokio::time::timeout;
8947 use tokio_tungstenite::{connect_async, tungstenite::Message};
8948
8949 let url = format!("ws://localhost:{api_port}/v1/{path}");
8950 let (mut ws, _) = connect_async(&url).await?;
8951 let mut messages = Vec::new();
8952 while messages.len() < 10 {
8953 match timeout(Duration::from_millis(500), ws.next()).await {
8954 Ok(Some(Ok(Message::Text(text)))) => {
8955 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
8956 messages.push(v);
8957 }
8958 },
8959 _ => break,
8960 }
8961 }
8962
8963 assert!(
8964 messages.len() >= 2,
8965 "v1/{path}: expected >=2 JSON messages from the stream, got {}",
8966 messages.len(),
8967 );
8968 Ok(())
8969 }
8970
8971 async fn assert_ws_endpoint_binary(api_port: u16, path: &str) -> anyhow::Result<()> {
8975 use std::time::Duration;
8976
8977 use futures::StreamExt as _;
8978 use tokio::time::timeout;
8979 use tokio_tungstenite::{
8980 connect_async,
8981 tungstenite::{client::IntoClientRequest, http::HeaderValue, protocol::Message},
8982 };
8983
8984 let url = format!("ws://localhost:{api_port}/v1/{path}");
8985 let mut req = url.as_str().into_client_request()?;
8986 req.headers_mut().insert(
8987 "Accept",
8988 HeaderValue::from_static("application/octet-stream"),
8989 );
8990 let (mut ws, _) = connect_async(req).await?;
8991 let mut frames = Vec::new();
8992 while frames.len() < 3 {
8993 match timeout(Duration::from_millis(500), ws.next()).await {
8994 Ok(Some(Ok(Message::Binary(bytes)))) => frames.push(bytes.to_vec()),
8995 _ => break,
8996 }
8997 }
8998
8999 assert!(
9000 !frames.is_empty(),
9001 "v1/{path}: no binary frames (Accept: application/octet-stream); handler likely \
9002 always sends text",
9003 );
9004 Ok(())
9005 }
9006
9007 #[rstest]
9008 #[case(POS_V4)]
9009 #[test_log::test]
9010 fn test_reward_proof_endpoint(#[case] upgrade: Upgrade) {
9011 let test = async move {
9012 const EPOCH_HEIGHT: u64 = 10;
9013 const NUM_NODES: usize = 5;
9014
9015 let network_config = TestConfigBuilder::default()
9016 .epoch_height(EPOCH_HEIGHT)
9017 .build();
9018
9019 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
9020 println!("API PORT = {api_port}");
9021
9022 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
9023 let persistence: [_; NUM_NODES] = storage
9024 .iter()
9025 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
9026 .collect::<Vec<_>>()
9027 .try_into()
9028 .unwrap();
9029
9030 let api_opts = Options::with_port(api_port)
9031 .catchup(Default::default())
9032 .config(Default::default())
9033 .explorer(Default::default())
9034 .light_client(Default::default())
9035 .hotshot_events(Default::default());
9036
9037 let config = TestNetworkConfigBuilder::with_num_nodes()
9038 .api_config(SqlDataSource::options(&storage[0], api_opts))
9039 .network_config(network_config.clone())
9040 .persistences(persistence.clone())
9041 .catchups(std::array::from_fn(|_| {
9042 StatePeers::<StaticVersion<0, 1>>::from_urls(
9043 vec![format!("http://localhost:{api_port}").parse().unwrap()],
9044 Default::default(),
9045 Duration::from_secs(2),
9046 &NoMetrics,
9047 )
9048 }))
9049 .pos_hook(
9050 DelegationConfig::MultipleDelegators,
9051 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
9052 upgrade,
9053 )
9054 .await
9055 .unwrap()
9056 .build();
9057
9058 let mut network = TestNetwork::new(config, upgrade).await;
9059
9060 let mut events = network.server.event_stream();
9062 wait_for_epochs(&mut events, EPOCH_HEIGHT, 4).await;
9063
9064 let url = format!("http://localhost:{api_port}").parse().unwrap();
9065 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
9066
9067 let validated_state = network.server.decided_state().await.unwrap();
9068 let decided_leaf = network.server.decided_leaf().await;
9069 let height = decided_leaf.height();
9070
9071 if upgrade.base == EPOCH_VERSION {
9073 wait_until_block_height(&client, "reward-state/block-height", height).await;
9075
9076 network.stop_consensus().await;
9077
9078 for (address, _) in validated_state.reward_merkle_tree_v1.iter() {
9079 let (_, expected_proof) = validated_state
9080 .reward_merkle_tree_v1
9081 .lookup(*address)
9082 .expect_ok()
9083 .unwrap();
9084
9085 let res = client
9086 .get::<RewardAccountQueryDataV1>(&format!(
9087 "reward-state/proof/{height}/{address}"
9088 ))
9089 .send()
9090 .await
9091 .unwrap();
9092
9093 match res.proof.proof {
9094 RewardMerkleProofV1::Presence(p) => {
9095 assert_eq!(
9096 p, expected_proof,
9097 "Proof mismatch for V1 at {height}, addr={address}"
9098 );
9099 },
9100 other => panic!(
9101 "Expected Present proof for V1 at {height}, addr={address}, got \
9102 {other:?}"
9103 ),
9104 }
9105 }
9106 } else {
9107 let avail_ns = NamespaceId::from(42_u32);
9116 let avail_tx = Transaction::new(avail_ns, vec![1, 2, 3]);
9117 network
9118 .server
9119 .submit_transaction(avail_tx.clone())
9120 .await
9121 .unwrap();
9122 let (avail_block, _) = wait_for_decide_on_handle(&mut events, &avail_tx).await;
9123
9124 let avail_tx2 = Transaction::new(avail_ns, vec![4, 5, 6]);
9127 network
9128 .server
9129 .submit_transaction(avail_tx2.clone())
9130 .await
9131 .unwrap();
9132 wait_for_decide_on_handle(&mut events, &avail_tx2).await;
9133
9134 wait_until_block_height(&client, "reward-state-v2/block-height", height).await;
9135 wait_until_block_height(&client, "node/block-height", avail_block).await;
9137
9138 let sample_start = Instant::now();
9146 let fee_account = 'fee_account: loop {
9147 let state = network.server.decided_state().await.unwrap();
9148 for (addr, _) in state.fee_merkle_tree.iter() {
9149 if client
9150 .get::<MerkleProof<FeeAmount, FeeAccount, Sha3Node, 256>>(&format!(
9151 "fee-state/{avail_block}/{addr}"
9152 ))
9153 .send()
9154 .await
9155 .is_ok()
9156 {
9157 break 'fee_account *addr;
9158 }
9159 }
9160 assert!(
9161 sample_start.elapsed() < Duration::from_secs(30),
9162 "no fee account provable at avail_block {avail_block} after 30s"
9163 );
9164 sleep(Duration::from_millis(500)).await;
9165 };
9166
9167 network.stop_consensus().await;
9168
9169 let http = reqwest::Client::new();
9170
9171 for (address, _) in validated_state.reward_merkle_tree_v2.iter() {
9172 let (_, expected_proof) = validated_state
9173 .reward_merkle_tree_v2
9174 .lookup(*address)
9175 .expect_ok()
9176 .unwrap();
9177
9178 let res = client
9179 .get::<RewardAccountQueryDataV2>(&format!(
9180 "reward-state-v2/proof/{height}/{address}"
9181 ))
9182 .send()
9183 .await
9184 .unwrap();
9185
9186 match res.proof.proof.clone() {
9187 RewardMerkleProofV2::Presence(p) => {
9188 assert_eq!(
9189 p, expected_proof,
9190 "Proof mismatch for V2 at {height}, addr={address}"
9191 );
9192 },
9193 other => panic!(
9194 "Expected Present proof for V2 at {height}, addr={address}, got \
9195 {other:?}"
9196 ),
9197 }
9198
9199 let reward_claim_input = client
9200 .get::<RewardClaimInput>(&format!(
9201 "reward-state-v2/reward-claim-input/{height}/{address}"
9202 ))
9203 .send()
9204 .await
9205 .unwrap();
9206
9207 assert_eq!(reward_claim_input, res.to_reward_claim_input()?);
9208
9209 let absent = alloy::primitives::Address::with_last_byte(0xaa);
9213 assert!(
9214 validated_state
9215 .reward_merkle_tree_v2
9216 .iter()
9217 .all(|(addr, _)| addr.0 != absent),
9218 "sentinel address unexpectedly present in reward tree"
9219 );
9220 let err = client
9221 .get::<RewardClaimInput>(&format!(
9222 "reward-state-v2/reward-claim-input/{height}/{absent}"
9223 ))
9224 .send()
9225 .await
9226 .unwrap_err();
9227 assert_matches!(err, ClientErr { status, .. } if status == StatusCode::NOT_FOUND);
9228
9229 assert_json_endpoint(
9231 &http,
9232 api_port,
9233 &format!("reward-state-v2/proof/{height}/{address}"),
9234 )
9235 .await?;
9236 assert_json_endpoint(
9237 &http,
9238 api_port,
9239 &format!("reward-state-v2/reward-claim-input/{height}/{address}"),
9240 )
9241 .await?;
9242 assert_json_endpoint(
9243 &http,
9244 api_port,
9245 &format!("reward-state-v2/reward-balance/{height}/{address}"),
9246 )
9247 .await?;
9248 assert_json_endpoint(
9249 &http,
9250 api_port,
9251 &format!("reward-state-v2/proof/latest/{address}"),
9252 )
9253 .await?;
9254 assert_json_endpoint(
9255 &http,
9256 api_port,
9257 &format!("reward-state-v2/reward-balance/latest/{address}"),
9258 )
9259 .await?;
9260
9261 assert_json_endpoint(
9265 &http,
9266 api_port,
9267 &format!("reward-state/proof/latest/{address}"),
9268 )
9269 .await?;
9270 assert_json_endpoint(
9271 &http,
9272 api_port,
9273 &format!("reward-state/reward-balance/latest/{address}"),
9274 )
9275 .await?;
9276 }
9277
9278 assert_json_endpoint(
9279 &http,
9280 api_port,
9281 &format!("reward-state-v2/reward-amounts/{height}/0/1000"),
9282 )
9283 .await?;
9284 assert_json_endpoint(
9285 &http,
9286 api_port,
9287 &format!("reward-state-v2/reward-merkle-tree-v2/{height}"),
9288 )
9289 .await?;
9290 assert_json_endpoint(
9291 &http,
9292 api_port,
9293 &format!("reward-state/reward-amounts/{height}/0/1000"),
9294 )
9295 .await?;
9296 assert_json_endpoint(
9297 &http,
9298 api_port,
9299 &format!("reward-state/reward-merkle-tree-v2/{height}"),
9300 )
9301 .await?;
9302
9303 let reward_address = validated_state
9311 .reward_merkle_tree_v2
9312 .iter()
9313 .next()
9314 .map(|(addr, _)| *addr)
9315 .expect("reward tree should have at least one account");
9316 let reward_header: Header = client
9317 .get(&format!("availability/header/{height}"))
9318 .send()
9319 .await
9320 .unwrap();
9321 let reward_mt_commit = match reward_header.reward_merkle_tree_root() {
9322 either::Either::Left(commit) => commit.to_string(),
9323 either::Either::Right(commit) => commit.to_string(),
9324 };
9325 for mount in ["reward-state", "reward-state-v2"] {
9326 assert_json_body(
9327 &http,
9328 api_port,
9329 &format!("{mount}/{height}/{reward_address}"),
9330 )
9331 .await?;
9332 assert_json_body(
9333 &http,
9334 api_port,
9335 &format!("{mount}/commit/{reward_mt_commit}/{reward_address}"),
9336 )
9337 .await?;
9338 }
9339
9340 assert_json_endpoint(
9344 &http,
9345 api_port,
9346 &format!("availability/block/{avail_block}/namespace/{avail_ns}"),
9347 )
9348 .await?;
9349
9350 let avail_header: Header = client
9352 .get(&format!("availability/header/{avail_block}"))
9353 .send()
9354 .await
9355 .unwrap();
9356 assert_json_endpoint(
9357 &http,
9358 api_port,
9359 &format!(
9360 "availability/block/hash/{}/namespace/{avail_ns}",
9361 avail_header.commit()
9362 ),
9363 )
9364 .await?;
9365 assert_json_endpoint(
9366 &http,
9367 api_port,
9368 &format!(
9369 "availability/block/payload-hash/{}/namespace/{avail_ns}",
9370 avail_header.payload_commitment()
9371 ),
9372 )
9373 .await?;
9374
9375 assert_json_endpoint(
9377 &http,
9378 api_port,
9379 &format!(
9380 "availability/block/{avail_block}/{}/namespace/{avail_ns}",
9381 avail_block + 1
9382 ),
9383 )
9384 .await?;
9385
9386 assert_json_endpoint(&http, api_port, "availability/state-cert/1").await?;
9388 assert_json_endpoint(&http, api_port, "availability/state-cert-v2/1").await?;
9389
9390 let avail_leaf: LeafQueryData<SeqTypes> = client
9392 .get(&format!("availability/leaf/{avail_block}"))
9393 .send()
9394 .await
9395 .unwrap();
9396 let leaf_hash = avail_leaf.hash();
9397 let block_hash = avail_header.commit();
9398 let payload_hash = avail_header.payload_commitment();
9399
9400 assert_json_endpoint(&http, api_port, &format!("availability/leaf/{avail_block}"))
9402 .await?;
9403 assert_json_endpoint(
9404 &http,
9405 api_port,
9406 &format!("availability/leaf/hash/{leaf_hash}"),
9407 )
9408 .await?;
9409 assert_json_endpoint(
9410 &http,
9411 api_port,
9412 &format!("availability/leaf/{avail_block}/{}", avail_block + 1),
9413 )
9414 .await?;
9415
9416 assert_json_endpoint(
9418 &http,
9419 api_port,
9420 &format!("availability/header/{avail_block}"),
9421 )
9422 .await?;
9423 assert_json_endpoint(
9424 &http,
9425 api_port,
9426 &format!("availability/header/hash/{block_hash}"),
9427 )
9428 .await?;
9429 assert_json_endpoint(
9430 &http,
9431 api_port,
9432 &format!("availability/header/payload-hash/{payload_hash}"),
9433 )
9434 .await?;
9435 assert_json_endpoint(
9436 &http,
9437 api_port,
9438 &format!("availability/header/{avail_block}/{}", avail_block + 1),
9439 )
9440 .await?;
9441
9442 assert_json_endpoint(
9444 &http,
9445 api_port,
9446 &format!("availability/block/{avail_block}"),
9447 )
9448 .await?;
9449 assert_json_endpoint(
9450 &http,
9451 api_port,
9452 &format!("availability/block/hash/{block_hash}"),
9453 )
9454 .await?;
9455 assert_json_endpoint(
9456 &http,
9457 api_port,
9458 &format!("availability/block/payload-hash/{payload_hash}"),
9459 )
9460 .await?;
9461 assert_json_endpoint(
9462 &http,
9463 api_port,
9464 &format!("availability/block/{avail_block}/{}", avail_block + 1),
9465 )
9466 .await?;
9467
9468 assert_json_endpoint(
9470 &http,
9471 api_port,
9472 &format!("availability/payload/{avail_block}"),
9473 )
9474 .await?;
9475 assert_json_endpoint(
9476 &http,
9477 api_port,
9478 &format!("availability/payload/hash/{payload_hash}"),
9479 )
9480 .await?;
9481 assert_json_endpoint(
9482 &http,
9483 api_port,
9484 &format!("availability/payload/block-hash/{block_hash}"),
9485 )
9486 .await?;
9487 assert_json_endpoint(
9488 &http,
9489 api_port,
9490 &format!("availability/payload/{avail_block}/{}", avail_block + 1),
9491 )
9492 .await?;
9493
9494 assert_json_endpoint(
9496 &http,
9497 api_port,
9498 &format!("availability/vid/common/{avail_block}"),
9499 )
9500 .await?;
9501 assert_json_endpoint(
9502 &http,
9503 api_port,
9504 &format!("availability/vid/common/hash/{block_hash}"),
9505 )
9506 .await?;
9507 assert_json_endpoint(
9508 &http,
9509 api_port,
9510 &format!("availability/vid/common/payload-hash/{payload_hash}"),
9511 )
9512 .await?;
9513 assert_json_endpoint(
9514 &http,
9515 api_port,
9516 &format!("availability/vid/common/{avail_block}/{}", avail_block + 1),
9517 )
9518 .await?;
9519
9520 let tx_hash = avail_tx.commit();
9522 assert_json_endpoint(
9523 &http,
9524 api_port,
9525 &format!("availability/transaction/{avail_block}/0/noproof"),
9526 )
9527 .await?;
9528 assert_json_endpoint(
9529 &http,
9530 api_port,
9531 &format!("availability/transaction/hash/{tx_hash}/noproof"),
9532 )
9533 .await?;
9534 assert_json_endpoint(
9535 &http,
9536 api_port,
9537 &format!("availability/transaction/{avail_block}/0/proof"),
9538 )
9539 .await?;
9540 assert_json_endpoint(
9541 &http,
9542 api_port,
9543 &format!("availability/transaction/hash/{tx_hash}/proof"),
9544 )
9545 .await?;
9546 assert_json_endpoint(
9547 &http,
9548 api_port,
9549 &format!("availability/transaction/{avail_block}/0"),
9550 )
9551 .await?;
9552 assert_json_endpoint(
9553 &http,
9554 api_port,
9555 &format!("availability/transaction/hash/{tx_hash}"),
9556 )
9557 .await?;
9558
9559 assert_json_endpoint(
9561 &http,
9562 api_port,
9563 &format!("availability/block/summary/{avail_block}"),
9564 )
9565 .await?;
9566 assert_json_endpoint(
9567 &http,
9568 api_port,
9569 &format!(
9570 "availability/block/summaries/{avail_block}/{}",
9571 avail_block + 1
9572 ),
9573 )
9574 .await?;
9575
9576 assert_json_endpoint(&http, api_port, "availability/limits").await?;
9578
9579 assert_endpoint_status(
9582 &http,
9583 api_port,
9584 &format!("availability/cert2/{avail_block}"),
9585 404,
9586 )
9587 .await?;
9588
9589 let ws_start = avail_block.saturating_sub(10);
9596 assert_ws_endpoint(api_port, &format!("availability/stream/leaves/{ws_start}"))
9597 .await?;
9598 assert_ws_endpoint(api_port, &format!("availability/stream/headers/{ws_start}"))
9599 .await?;
9600 assert_ws_endpoint(api_port, &format!("availability/stream/blocks/{ws_start}"))
9601 .await?;
9602 assert_ws_endpoint(
9603 api_port,
9604 &format!("availability/stream/payloads/{ws_start}"),
9605 )
9606 .await?;
9607 assert_ws_endpoint(
9608 api_port,
9609 &format!("availability/stream/vid/common/{ws_start}"),
9610 )
9611 .await?;
9612 assert_ws_endpoint(
9613 api_port,
9614 &format!("availability/stream/transactions/{ws_start}"),
9615 )
9616 .await?;
9617 assert_ws_endpoint(
9620 api_port,
9621 &format!("availability/stream/transactions/{avail_block}/namespace/{avail_ns}"),
9622 )
9623 .await?;
9624 assert_ws_endpoint(
9625 api_port,
9626 &format!("availability/stream/blocks/{avail_block}/namespace/{avail_ns}"),
9627 )
9628 .await?;
9629
9630 assert_ws_endpoint_binary(
9634 api_port,
9635 &format!("availability/stream/leaves/{ws_start}"),
9636 )
9637 .await?;
9638
9639 wait_until_block_height(&client, "block-state/block-height", avail_block).await;
9642 wait_until_block_height(&client, "fee-state/block-height", avail_block).await;
9643
9644 assert_json_endpoint(&http, api_port, "block-state/block-height").await?;
9647 assert_json_endpoint(&http, api_port, "fee-state/block-height").await?;
9648
9649 assert_json_endpoint(
9652 &http,
9653 api_port,
9654 &format!(
9655 "block-state/{avail_block}/{}",
9656 avail_block.saturating_sub(1)
9657 ),
9658 )
9659 .await?;
9660
9661 let block_mt_commit = avail_header.block_merkle_tree_root().to_string();
9664 assert_json_endpoint(
9665 &http,
9666 api_port,
9667 &format!(
9668 "block-state/commit/{block_mt_commit}/{}",
9669 avail_block.saturating_sub(1)
9670 ),
9671 )
9672 .await?;
9673
9674 assert_json_endpoint(
9677 &http,
9678 api_port,
9679 &format!("fee-state/{avail_block}/{fee_account}"),
9680 )
9681 .await?;
9682 let fee_mt_commit = avail_header.fee_merkle_tree_root().to_string();
9683 assert_json_endpoint(
9684 &http,
9685 api_port,
9686 &format!("fee-state/commit/{fee_mt_commit}/{fee_account}"),
9687 )
9688 .await?;
9689 assert_json_endpoint(
9690 &http,
9691 api_port,
9692 &format!("fee-state/fee-balance/latest/{fee_account}"),
9693 )
9694 .await?;
9695
9696 assert_json_endpoint(&http, api_port, "status/block-height").await?;
9700 assert_json_endpoint(&http, api_port, "status/success-rate").await?;
9701 assert_endpoint_ok(&http, api_port, "status/time-since-last-decide").await?;
9702 assert_endpoint_ok(&http, api_port, "status/metrics").await?;
9703
9704 assert_json_endpoint(&http, api_port, "config/hotshot").await?;
9707 assert_json_endpoint(&http, api_port, "config/env").await?;
9708 assert_endpoint_status(&http, api_port, "config/runtime", 404).await?;
9709
9710 assert_json_endpoint(&http, api_port, "node/block-height").await?;
9712 assert_json_endpoint(&http, api_port, "node/transactions/count").await?;
9713 assert_json_endpoint(
9714 &http,
9715 api_port,
9716 &format!("node/transactions/count/{avail_block}"),
9717 )
9718 .await?;
9719 assert_json_endpoint(
9720 &http,
9721 api_port,
9722 &format!("node/transactions/count/0/{avail_block}"),
9723 )
9724 .await?;
9725 assert_json_endpoint(
9726 &http,
9727 api_port,
9728 &format!("node/transactions/count/namespace/{avail_ns}"),
9729 )
9730 .await?;
9731 assert_json_endpoint(
9732 &http,
9733 api_port,
9734 &format!("node/transactions/count/namespace/{avail_ns}/{avail_block}"),
9735 )
9736 .await?;
9737 assert_json_endpoint(
9738 &http,
9739 api_port,
9740 &format!("node/transactions/count/namespace/{avail_ns}/0/{avail_block}"),
9741 )
9742 .await?;
9743
9744 assert_json_endpoint(&http, api_port, "node/payloads/size").await?;
9745 assert_json_endpoint(&http, api_port, "node/payloads/total-size").await?;
9746 assert_json_endpoint(
9747 &http,
9748 api_port,
9749 &format!("node/payloads/size/{avail_block}"),
9750 )
9751 .await?;
9752 assert_json_endpoint(
9753 &http,
9754 api_port,
9755 &format!("node/payloads/size/0/{avail_block}"),
9756 )
9757 .await?;
9758 assert_json_endpoint(
9759 &http,
9760 api_port,
9761 &format!("node/payloads/size/namespace/{avail_ns}"),
9762 )
9763 .await?;
9764 assert_json_endpoint(
9765 &http,
9766 api_port,
9767 &format!("node/payloads/size/namespace/{avail_ns}/{avail_block}"),
9768 )
9769 .await?;
9770 assert_json_endpoint(
9771 &http,
9772 api_port,
9773 &format!("node/payloads/size/namespace/{avail_ns}/0/{avail_block}"),
9774 )
9775 .await?;
9776
9777 assert_json_endpoint(&http, api_port, &format!("node/vid/share/{avail_block}"))
9778 .await?;
9779 assert_json_endpoint(
9780 &http,
9781 api_port,
9782 &format!("node/vid/share/hash/{block_hash}"),
9783 )
9784 .await?;
9785 assert_json_endpoint(
9786 &http,
9787 api_port,
9788 &format!("node/vid/share/payload-hash/{payload_hash}"),
9789 )
9790 .await?;
9791
9792 assert_json_endpoint(&http, api_port, "node/sync-status").await?;
9793 assert_json_endpoint(&http, api_port, "node/limits").await?;
9794
9795 let avail_ts = avail_header.timestamp();
9799 assert_json_endpoint(
9800 &http,
9801 api_port,
9802 &format!("node/header/window/{avail_ts}/{}", avail_ts + 1),
9803 )
9804 .await?;
9805 assert_json_endpoint(
9806 &http,
9807 api_port,
9808 &format!("node/header/window/from/{avail_block}/{}", avail_ts + 1),
9809 )
9810 .await?;
9811 assert_json_endpoint(
9812 &http,
9813 api_port,
9814 &format!("node/header/window/from/hash/{block_hash}/{}", avail_ts + 1),
9815 )
9816 .await?;
9817
9818 assert_json_endpoint(&http, api_port, "node/stake-table/current").await?;
9819 assert_json_endpoint(&http, api_port, "node/stake-table/1").await?;
9820 assert_json_endpoint(&http, api_port, "node/da-stake-table/current").await?;
9821 assert_json_endpoint(&http, api_port, "node/da-stake-table/1").await?;
9822
9823 assert_json_endpoint(&http, api_port, "node/validators/1").await?;
9824 assert_json_endpoint(&http, api_port, "node/all-validators/1/0/100").await?;
9825
9826 assert_json_endpoint(&http, api_port, "node/participation/proposal/current")
9827 .await?;
9828 assert_json_endpoint(&http, api_port, "node/participation/proposal/1").await?;
9829 assert_json_endpoint(&http, api_port, "node/participation/vote/current").await?;
9830 assert_json_endpoint(&http, api_port, "node/participation/vote/1").await?;
9831
9832 assert_json_endpoint(&http, api_port, "node/block-reward").await?;
9833 assert_json_endpoint(&http, api_port, "node/block-reward/epoch/1").await?;
9834
9835 assert_json_endpoint(&http, api_port, "node/oldest-block").await?;
9836 assert_json_endpoint(&http, api_port, "node/oldest-leaf").await?;
9837
9838 let decided_view = decided_leaf.view_number().u64();
9842 assert_json_endpoint(
9843 &http,
9844 api_port,
9845 &format!("catchup/{height}/{decided_view}/blocks"),
9846 )
9847 .await?;
9848 assert_endpoint_status(
9851 &http,
9852 api_port,
9853 "catchup/chain-config/CHAINCONFIG~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
9854 400,
9855 )
9856 .await?;
9857 assert_endpoint_status(&http, api_port, "catchup/999999/leafchain", 404).await?;
9859 assert_endpoint_status(&http, api_port, "catchup/999999/cert2", 404).await?;
9861 assert_endpoint_status(&http, api_port, "catchup/1/reward-amounts/100/0", 404)
9863 .await?;
9864
9865 assert_post_binary(
9871 &http,
9872 api_port,
9873 &format!("catchup/{height}/{decided_view}/accounts"),
9874 &vec![fee_account],
9875 )
9876 .await?;
9877 assert_post_binary(
9881 &http,
9882 api_port,
9883 &format!("catchup/{height}/{decided_view}/reward-accounts"),
9884 &Vec::<espresso_types::v0_3::RewardAccountV1>::new(),
9885 )
9886 .await?;
9887
9888 assert_endpoint_status(&http, api_port, "state-signature/block/999999", 404)
9890 .await?;
9891
9892 assert_error_body(&http, api_port, "catchup/999999/cert2", 404).await?;
9898 assert_error_body(&http, api_port, "catchup/999999/leafchain", 404).await?;
9899 assert_error_body(&http, api_port, "state-signature/block/999999", 404).await?;
9900
9901 assert_json_endpoint(&http, api_port, "explorer/explorer-summary").await?;
9903 assert_json_endpoint(&http, api_port, &format!("explorer/block/{avail_block}"))
9904 .await?;
9905 assert_json_endpoint(
9906 &http,
9907 api_port,
9908 &format!("explorer/block/hash/{block_hash}"),
9909 )
9910 .await?;
9911 assert_json_endpoint(&http, api_port, "explorer/blocks/latest/10").await?;
9912 assert_json_endpoint(
9913 &http,
9914 api_port,
9915 &format!("explorer/blocks/{avail_block}/10"),
9916 )
9917 .await?;
9918 assert_json_endpoint(&http, api_port, "explorer/transactions/latest/10").await?;
9919
9920 assert_json_endpoint(&http, api_port, &format!("light-client/leaf/{avail_block}"))
9922 .await?;
9923 assert_json_endpoint(
9924 &http,
9925 api_port,
9926 &format!("light-client/leaf/hash/{leaf_hash}"),
9927 )
9928 .await?;
9929 assert_json_endpoint(
9930 &http,
9931 api_port,
9932 &format!("light-client/payload/{avail_block}"),
9933 )
9934 .await?;
9935 assert_json_endpoint(
9936 &http,
9937 api_port,
9938 &format!("light-client/payload/{avail_block}/{}", avail_block + 1),
9939 )
9940 .await?;
9941 assert_json_endpoint(
9942 &http,
9943 api_port,
9944 &format!(
9945 "light-client/namespace/{avail_block}/{}",
9946 u64::from(avail_ns)
9947 ),
9948 )
9949 .await?;
9950 assert_json_endpoint(
9951 &http,
9952 api_port,
9953 &format!(
9954 "light-client/namespace/{avail_block}/{}/{}",
9955 avail_block + 1,
9956 u64::from(avail_ns)
9957 ),
9958 )
9959 .await?;
9960
9961 let encoded_ns = tagged_base64::TaggedBase64::new(
9964 ::light_client::client::NAMESPACES_PARAM_TAG,
9965 &serde_json::to_vec(&vec![u64::from(avail_ns)])?,
9966 )?;
9967 assert_endpoint_status(
9968 &http,
9969 api_port,
9970 &format!(
9971 "light-client/namespaces/{avail_block}/{}/{encoded_ns}",
9972 avail_block + 200
9973 ),
9974 400,
9975 )
9976 .await?;
9977
9978 assert_json_endpoint(&http, api_port, "hotshot-events/startup_info").await?;
9980
9981 assert_json_endpoint(&http, api_port, "token/total-minted-supply").await?;
9983 assert_json_endpoint(&http, api_port, "token/circulating-supply").await?;
9984 assert_json_endpoint(&http, api_port, "token/circulating-supply-ethereum").await?;
9985 assert_json_endpoint(&http, api_port, "token/total-issued-supply").await?;
9986 assert_json_endpoint(&http, api_port, "token/total-reward-distributed").await?;
9987
9988 assert_endpoint_status(&http, api_port, "availability/leaf/999999", 404).await?;
9993
9994 assert_endpoint_status(
9997 &http,
9998 api_port,
9999 &format!("availability/block/{avail_block}/{}", avail_block + 200),
10000 400,
10001 )
10002 .await?;
10003
10004 assert_endpoint_status(
10007 &http,
10008 api_port,
10009 &format!(
10010 "availability/block/{avail_block}/{}/namespace/{avail_ns}",
10011 avail_block + 200
10012 ),
10013 400,
10014 )
10015 .await?;
10016 }
10017
10018 anyhow::Ok(())
10019 };
10020
10021 std::thread::Builder::new()
10025 .stack_size(32 * 1024 * 1024)
10026 .spawn(move || {
10027 tokio::runtime::Builder::new_multi_thread()
10028 .enable_all()
10029 .build()
10030 .unwrap()
10031 .block_on(test)
10032 .unwrap()
10033 })
10034 .unwrap()
10035 .join()
10036 .unwrap()
10037 }
10038
10039 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10040 async fn test_all_validators_endpoint() -> anyhow::Result<()> {
10041 const EPOCH_HEIGHT: u64 = 20;
10042
10043 let network_config = TestConfigBuilder::default()
10044 .epoch_height(EPOCH_HEIGHT)
10045 .build();
10046
10047 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10048
10049 const NUM_NODES: usize = 5;
10050
10051 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10052 let persistence: [_; NUM_NODES] = storage
10053 .iter()
10054 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10055 .collect::<Vec<_>>()
10056 .try_into()
10057 .unwrap();
10058
10059 let config = TestNetworkConfigBuilder::with_num_nodes()
10060 .api_config(SqlDataSource::options(
10061 &storage[0],
10062 Options::with_port(api_port),
10063 ))
10064 .network_config(network_config)
10065 .persistences(persistence.clone())
10066 .catchups(std::array::from_fn(|_| {
10067 StatePeers::<StaticVersion<0, 1>>::from_urls(
10068 vec![format!("http://localhost:{api_port}").parse().unwrap()],
10069 Default::default(),
10070 Duration::from_secs(2),
10071 &NoMetrics,
10072 )
10073 }))
10074 .pos_hook(
10075 DelegationConfig::MultipleDelegators,
10076 Default::default(),
10077 POS_V4,
10078 )
10079 .await
10080 .unwrap()
10081 .build();
10082
10083 let network = TestNetwork::new(config, POS_V4).await;
10084 let client: Client<ClientErr, SequencerApiVersion> =
10085 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
10086
10087 let err = client
10088 .get::<Vec<RegisteredValidator<PubKey>>>("node/all-validators/1/0/1001")
10089 .header("Accept", "application/json")
10090 .send()
10091 .await
10092 .unwrap_err();
10093
10094 assert_matches!(err, ClientErr { status, message} if
10095 status == StatusCode::BAD_REQUEST
10096 && message.contains("Limit cannot be greater than 1000")
10097 );
10098
10099 let mut events = network.peers[0].event_stream();
10101 wait_for_epochs(&mut events, EPOCH_HEIGHT, 3).await;
10102
10103 {
10105 client
10106 .get::<Vec<RegisteredValidator<PubKey>>>("node/all-validators/1/0/100")
10107 .send()
10108 .await
10109 .unwrap()
10110 .is_empty();
10111
10112 client
10113 .get::<Vec<RegisteredValidator<PubKey>>>("node/all-validators/2/0/100")
10114 .send()
10115 .await
10116 .unwrap()
10117 .is_empty();
10118 }
10119
10120 let validators = client
10122 .get::<Vec<RegisteredValidator<PubKey>>>("node/all-validators/3/0/100")
10123 .send()
10124 .await
10125 .expect("validators");
10126
10127 assert!(!validators.is_empty());
10128
10129 Ok(())
10130 }
10131
10132 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10133 async fn test_reward_accounts_catchup_endpoint() -> anyhow::Result<()> {
10134 const EPOCH_HEIGHT: u64 = 10;
10135 const NUM_NODES: usize = 3;
10136
10137 let network_config = TestConfigBuilder::default()
10138 .epoch_height(EPOCH_HEIGHT)
10139 .build();
10140
10141 let api_port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10142 println!("API PORT = {api_port}");
10143
10144 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10145 let persistence: [_; NUM_NODES] = storage
10146 .iter()
10147 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10148 .collect::<Vec<_>>()
10149 .try_into()
10150 .unwrap();
10151
10152 let config = TestNetworkConfigBuilder::with_num_nodes()
10153 .api_config(SqlDataSource::options(
10154 &storage[0],
10155 Options::with_port(api_port).catchup(Default::default()),
10156 ))
10157 .network_config(network_config)
10158 .persistences(persistence.clone())
10159 .catchups(std::array::from_fn(|_| {
10160 StatePeers::<StaticVersion<0, 1>>::from_urls(
10161 vec![format!("http://localhost:{api_port}").parse().unwrap()],
10162 Default::default(),
10163 Duration::from_secs(2),
10164 &NoMetrics,
10165 )
10166 }))
10167 .pos_hook(
10168 DelegationConfig::MultipleDelegators,
10169 hotshot_contract_adapter::stake_table::StakeTableContractVersion::V3,
10170 POS_V4,
10171 )
10172 .await
10173 .unwrap()
10174 .build();
10175
10176 let mut network = TestNetwork::new(config, POS_V4).await;
10177
10178 let client: Client<ClientErr, StaticVersion<0, 1>> =
10179 Client::new(format!("http://localhost:{api_port}").parse().unwrap());
10180
10181 client.connect(None).await;
10182
10183 let mut events = network.server.event_stream();
10184 wait_for_epochs(&mut events, EPOCH_HEIGHT, 3).await;
10185
10186 network.stop_consensus().await;
10187 let height = network.server.decided_leaf().await.height();
10188 wait_until_block_height(&client, "reward-state-v2/block-height", height).await;
10189
10190 let err = client
10191 .get::<Vec<(RewardAccountV2, RewardAmount)>>(&format!(
10192 "reward-state-v2/reward-amounts/{height}/0/10001"
10193 ))
10194 .send()
10195 .await
10196 .unwrap_err();
10197
10198 assert_matches!(err, ClientErr { status, .. } if
10199 status == StatusCode::BAD_REQUEST
10200
10201 );
10202
10203 let mut expected: Vec<_> = network
10204 .server
10205 .decided_state()
10206 .await
10207 .unwrap()
10208 .reward_merkle_tree_v2
10209 .iter()
10210 .map(|(addr, amt)| (*addr, *amt))
10211 .collect();
10212 expected.sort_by_key(|(acct, _)| std::cmp::Reverse(*acct));
10214
10215 tracing::info!("expected accounts = {expected:?}");
10216 let limit = expected.len().min(10_000) as u64;
10217 let offset = 0u64;
10218 let expected: Vec<_> = expected.into_iter().take(limit as usize).collect();
10219
10220 let res = client
10221 .get::<Vec<(RewardAccountV2, RewardAmount)>>(&format!(
10222 "reward-state-v2/reward-amounts/{height}/{offset}/{limit}"
10223 ))
10224 .send()
10225 .await
10226 .unwrap();
10227
10228 assert_eq!(res, expected);
10229
10230 Ok(())
10231 }
10232
10233 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10234 async fn test_namespace_query_compat_v0_2() {
10235 test_namespace_query_compat_helper(Upgrade::trivial(FEE_VERSION)).await;
10236 }
10237
10238 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10239 async fn test_namespace_query_compat_v0_3() {
10240 test_namespace_query_compat_helper(Upgrade::trivial(EPOCH_VERSION)).await;
10241 }
10242
10243 async fn test_namespace_query_compat_helper(upgrade: Upgrade) {
10244 const NUM_NODES: usize = 5;
10246
10247 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10248 let url: Url = format!("http://localhost:{port}").parse().unwrap();
10249
10250 let test_config = TestConfigBuilder::default().build();
10251 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
10252 .api_config(Options::from(options::Http {
10253 port,
10254 max_connections: None,
10255 tonic_port: None,
10256 }))
10257 .catchups(std::array::from_fn(|_| {
10258 StatePeers::<SequencerApiVersion>::from_urls(
10259 vec![url.clone()],
10260 Default::default(),
10261 Duration::from_secs(2),
10262 &NoMetrics,
10263 )
10264 }))
10265 .network_config(test_config)
10266 .build();
10267
10268 let mut network = TestNetwork::new(config, upgrade).await;
10269 let mut events = network.server.event_stream();
10270
10271 let ns = NamespaceId::from(10_000u64);
10273 let tx = Transaction::new(ns, vec![1, 2, 3]);
10274 network.server.submit_transaction(tx.clone()).await.unwrap();
10275 let block = wait_for_decide_on_handle(&mut events, &tx).await.0;
10276
10277 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
10279 client.connect(None).await;
10280
10281 let (header, common): (Header, VidCommonQueryData<SeqTypes>) = try_join!(
10282 client.get(&format!("availability/header/{block}")).send(),
10283 client
10284 .get(&format!("availability/vid/common/{block}"))
10285 .send()
10286 )
10287 .unwrap();
10288 let version = header.version();
10289
10290 for api_ver in ["/v1", ""] {
10293 tracing::info!("test namespace API version: {api_ver}");
10294
10295 let ns_proof: NamespaceProofQueryData = client
10296 .get(&format!(
10297 "{api_ver}/availability/block/{block}/namespace/{ns}"
10298 ))
10299 .send()
10300 .await
10301 .unwrap();
10302 let proof = ns_proof.proof.as_ref().unwrap();
10303 if version < EPOCH_VERSION {
10304 assert!(matches!(proof, NsProof::V0(..)));
10305 } else {
10306 assert!(matches!(proof, NsProof::V1(..)));
10307 }
10308 let (txs, ns_from_proof) = proof
10309 .verify(
10310 header.ns_table(),
10311 &header.payload_commitment(),
10312 common.common(),
10313 )
10314 .unwrap();
10315 assert_eq!(ns_from_proof, ns);
10316 assert_eq!(txs, ns_proof.transactions);
10317 assert_eq!(txs, std::slice::from_ref(&tx));
10318
10319 let ns_proofs: Vec<NamespaceProofQueryData> = client
10321 .get(&format!(
10322 "{api_ver}/availability/block/{}/{}/namespace/{ns}",
10323 block,
10324 block + 1
10325 ))
10326 .send()
10327 .await
10328 .unwrap();
10329 assert_eq!(&ns_proofs, std::slice::from_ref(&ns_proof));
10330
10331 let ns_proof: NamespaceProofQueryData = client
10333 .get(&format!(
10334 "{api_ver}/availability/block/{}/namespace/{ns}",
10335 block - 1
10336 ))
10337 .send()
10338 .await
10339 .unwrap();
10340 assert_eq!(ns_proof.proof, None);
10341 assert_eq!(ns_proof.transactions, vec![]);
10342
10343 let mut proofs = client
10345 .socket(&format!(
10346 "{api_ver}/availability/stream/blocks/0/namespace/{ns}"
10347 ))
10348 .subscribe()
10349 .await
10350 .unwrap();
10351 for i in 0.. {
10352 tracing::info!(i, "stream proof");
10353 let proof: NamespaceProofQueryData = proofs.next().await.unwrap().unwrap();
10354 if proof.proof.is_none() {
10355 tracing::info!("waiting for non-trivial proof from stream");
10356 continue;
10357 }
10358 assert_eq!(&proof.transactions, std::slice::from_ref(&tx));
10359 break;
10360 }
10361 }
10362
10363 network.server.shut_down().await;
10364 }
10365
10366 async fn check_light_client_proofs(
10372 client: &Client<ClientErr, StaticVersion<0, 1>>,
10373 actual_leaves: &[LeafQueryData<SeqTypes>],
10374 actual_blocks: &[BlockQueryData<SeqTypes>],
10375 heights: impl IntoIterator<Item = u64>,
10376 epoch_height: u64,
10377 ) {
10378 let quorum = EpochChangeQuorum::new(epoch_height);
10379 for i in heights {
10380 let leaf = &actual_leaves[i as usize];
10381 let block = &actual_blocks[i as usize];
10382 let version = leaf.header().version();
10383 tracing::info!(i, %version, "check light client proofs");
10384
10385 let proofs = try_join_all(
10387 [
10388 format!("light-client/leaf/{i}"),
10389 format!("light-client/leaf/hash/{}", leaf.hash()),
10390 format!("light-client/leaf/block-hash/{}", leaf.block_hash()),
10391 ]
10392 .into_iter()
10393 .map(|path| async move {
10394 tracing::info!(i, path, "fetch leaf proof");
10395 let proof = client.get::<LeafProof>(&path).send().await?;
10396 Ok::<_, anyhow::Error>((path, proof))
10397 }),
10398 )
10399 .await
10400 .unwrap();
10401
10402 for (path, proof) in proofs {
10404 tracing::info!(i, path, "check leaf proof");
10405 assert_eq!(
10406 proof
10407 .verify(LeafProofHint::Quorum(&quorum))
10408 .await
10409 .unwrap_or_else(|err| panic!("{path}: proof failed to verify: {err:#}")),
10410 *leaf
10411 );
10412 }
10413
10414 let root_height = i + 1;
10416 let root = actual_leaves[root_height as usize].header();
10417 let proofs = try_join_all(
10418 [
10419 format!("light-client/header/{root_height}/{i}"),
10420 format!(
10421 "light-client/header/{root_height}/hash/{}",
10422 leaf.block_hash()
10423 ),
10424 ]
10425 .into_iter()
10426 .map(|path| async move {
10427 tracing::info!(i, path, "fetch header proof");
10428 let proof = client.get::<HeaderProof>(&path).send().await?;
10429 Ok::<_, anyhow::Error>((path, proof))
10430 }),
10431 )
10432 .await
10433 .unwrap();
10434 for (path, proof) in proofs {
10435 tracing::info!(i, path, "check header proof");
10436 assert_eq!(
10437 proof.verify_ref(root.block_merkle_tree_root()).unwrap(),
10438 leaf.header()
10439 );
10440 }
10441
10442 let proof = client
10444 .get::<PayloadProof>(&format!("light-client/payload/{i}"))
10445 .send()
10446 .await
10447 .unwrap();
10448 assert_eq!(proof.verify(leaf.header()).unwrap(), *block.payload());
10449 }
10450 }
10451
10452 async fn check_light_client_stake_table<N, P>(
10456 client: &Client<ClientErr, StaticVersion<0, 1>>,
10457 server: &SequencerContext<N, P>,
10458 first_epoch: EpochNumber,
10459 ) where
10460 N: ConnectedNetwork<PubKey>,
10461 P: SequencerPersistence,
10462 {
10463 let events: Vec<StakeTableEvent> = client
10464 .get(&format!("light-client/stake-table/{}", first_epoch + 2))
10465 .send()
10466 .await
10467 .unwrap();
10468 let mut state_from_events = StakeTableState::default();
10469 for event in events {
10470 state_from_events.apply_event(event).unwrap().unwrap();
10471 }
10472 assert_eq!(
10473 state_from_events.into_validators(),
10474 server
10475 .consensus_handle()
10476 .storage()
10477 .await
10478 .load_all_validators(first_epoch + 2, 0, 1_000_000)
10479 .await
10480 .unwrap()
10481 .into_iter()
10482 .map(|v| (v.account, v))
10483 .collect::<RegisteredValidatorMap>()
10484 );
10485
10486 let err = client
10488 .get::<Vec<StakeTableEvent>>(&format!("light-client/stake-table/{}", first_epoch + 1))
10489 .send()
10490 .await
10491 .unwrap_err();
10492 assert_eq!(err.status, StatusCode::BAD_REQUEST);
10493 }
10494
10495 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10496 async fn test_light_client_completeness() {
10497 const NUM_NODES: usize = 1;
10501 const EPOCH_HEIGHT: u64 = 200;
10502
10503 let upgrade = Upgrade::new(LEGACY_VERSION, EPOCH_VERSION);
10504 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10505 let url: Url = format!("http://localhost:{port}").parse().unwrap();
10506
10507 let test_config = TestConfigBuilder::default()
10508 .epoch_height(EPOCH_HEIGHT)
10509 .epoch_start_block(321)
10510 .set_upgrades(upgrade.target)
10511 .await
10512 .build();
10513
10514 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10515 let persistence: [_; NUM_NODES] = storage
10516 .iter()
10517 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10518 .collect::<Vec<_>>()
10519 .try_into()
10520 .unwrap();
10521
10522 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
10523 .api_config(
10524 SqlDataSource::options(&storage[0], Options::with_port(port))
10525 .light_client(Default::default()),
10526 )
10527 .persistences(persistence.clone())
10528 .catchups(std::array::from_fn(|_| {
10529 StatePeers::<SequencerApiVersion>::from_urls(
10530 vec![url.clone()],
10531 Default::default(),
10532 Duration::from_secs(2),
10533 &NoMetrics,
10534 )
10535 }))
10536 .network_config(test_config)
10537 .build();
10538
10539 let mut network = TestNetwork::new(config, upgrade).await;
10540 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
10541 client.connect(None).await;
10542
10543 let mut actual_leaves = vec![];
10546 let mut actual_blocks = vec![];
10547 let mut leaves = client
10548 .socket("availability/stream/leaves/0")
10549 .subscribe::<LeafQueryData<SeqTypes>>()
10550 .await
10551 .unwrap()
10552 .zip(
10553 client
10554 .socket("availability/stream/blocks/0")
10555 .subscribe::<BlockQueryData<SeqTypes>>()
10556 .await
10557 .unwrap(),
10558 )
10559 .map(|(leaf, block)| {
10560 let leaf = leaf.unwrap();
10561 let block = block.unwrap();
10562 actual_leaves.push(leaf.clone());
10563 actual_blocks.push(block);
10564 leaf
10565 });
10566
10567 let (upgrade_height, first_epoch) = loop {
10569 let leaf: LeafQueryData<SeqTypes> = leaves.next().await.unwrap();
10570 if leaf.header().version() < EPOCH_VERSION {
10571 tracing::info!(version = %leaf.header().version(), height = leaf.header().height(), view = ?leaf.leaf().view_number(), "waiting for epoch upgrade");
10572 continue;
10573 }
10574 break (leaf.height(), leaf.leaf().epoch(EPOCH_HEIGHT).unwrap());
10575 };
10576 tracing::info!(upgrade_height, ?first_epoch, "epochs enabled");
10577
10578 let mut epoch_heights = [0; 2];
10581 for (i, epoch_height) in epoch_heights.iter_mut().enumerate() {
10582 let desired_epoch = first_epoch + (i as u64) + 1;
10583 *epoch_height = loop {
10584 let leaf = leaves.next().await.unwrap();
10585 let epoch = leaf.leaf().epoch(EPOCH_HEIGHT).unwrap();
10586 if epoch > desired_epoch {
10587 tracing::info!(
10588 height = leaf.height(),
10589 ?desired_epoch,
10590 ?epoch,
10591 "changed epoch"
10592 );
10593 break leaf.height();
10594 }
10595 tracing::info!(
10596 ?desired_epoch,
10597 height = leaf.header().height(),
10598 view = ?leaf.leaf().view_number(),
10599 "waiting for epoch change"
10600 );
10601 };
10602 }
10603
10604 let max_block = epoch_heights[1] + 1;
10606 loop {
10607 let leaf = leaves.next().await.unwrap();
10608 if leaf.height() > max_block {
10609 break;
10610 }
10611 tracing::info!(max_block, height = leaf.height(), "waiting for block");
10612 }
10613
10614 network.stop_consensus().await;
10617
10618 let heights =
10621 (0..=1)
10623 .chain(upgrade_height-1..=upgrade_height+1)
10625 .chain(epoch_heights[0]-1..=epoch_heights[0] + 1)
10627 .chain(epoch_heights[1]-1..=max_block);
10629
10630 check_light_client_proofs(
10631 &client,
10632 &actual_leaves,
10633 &actual_blocks,
10634 heights,
10635 EPOCH_HEIGHT,
10636 )
10637 .await;
10638 check_light_client_stake_table(&client, &network.server, first_epoch).await;
10639 }
10640
10641 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10645 async fn test_light_client_new_protocol_upgrade() {
10646 const NUM_NODES: usize = 5;
10647 const EPOCH_HEIGHT: u64 = 70;
10648 const UPGRADE_START_PROPOSING_VIEW: u64 = 3 * EPOCH_HEIGHT + 5;
10649 const UPGRADE: Upgrade = Upgrade::new(EPOCH_REWARD_VERSION, NEW_PROTOCOL_VERSION);
10650
10651 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10652 let url: Url = format!("http://localhost:{port}").parse().unwrap();
10653
10654 let test_config = TestConfigBuilder::<NUM_NODES>::default()
10655 .epoch_height(EPOCH_HEIGHT)
10656 .epoch_start_block(0)
10657 .builder_timeout(Duration::from_millis(500))
10658 .set_upgrades(NEW_PROTOCOL_VERSION)
10659 .await
10660 .upgrade_proposing_views(UPGRADE_START_PROPOSING_VIEW, 1000)
10661 .build();
10662
10663 test_config
10664 .anvil()
10665 .expect("TestConfigBuilder starts an anvil")
10666 .anvil_set_interval_mining(1)
10667 .await
10668 .expect("interval mining");
10669
10670 let genesis_state = ValidatedState {
10673 chain_config: test_config
10674 .get_upgrade_map()
10675 .chain_config(NEW_PROTOCOL_VERSION)
10676 .into(),
10677 ..Default::default()
10678 };
10679
10680 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10681 let persistence: [_; NUM_NODES] = storage
10682 .iter()
10683 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10684 .collect::<Vec<_>>()
10685 .try_into()
10686 .unwrap();
10687
10688 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
10689 .api_config(
10690 SqlDataSource::options(&storage[0], Options::with_port(port))
10691 .light_client(Default::default()),
10692 )
10693 .persistences(persistence)
10694 .states(std::array::from_fn(|_| genesis_state.clone()))
10695 .catchups(std::array::from_fn(|_| {
10696 StatePeers::<SequencerApiVersion>::from_urls(
10697 vec![url.clone()],
10698 Default::default(),
10699 Duration::from_secs(2),
10700 &NoMetrics,
10701 )
10702 }))
10703 .network_config(test_config)
10704 .build();
10705
10706 let mut network = TestNetwork::new(config, UPGRADE).await;
10707 let client: Client<ClientErr, StaticVersion<0, 1>> = Client::new(url);
10708 client.connect(None).await;
10709
10710 let mut actual_leaves = vec![];
10713 let mut actual_blocks = vec![];
10714 let mut leaves = client
10715 .socket("availability/stream/leaves/0")
10716 .subscribe::<LeafQueryData<SeqTypes>>()
10717 .await
10718 .unwrap()
10719 .zip(
10720 client
10721 .socket("availability/stream/blocks/0")
10722 .subscribe::<BlockQueryData<SeqTypes>>()
10723 .await
10724 .unwrap(),
10725 )
10726 .map(|(leaf, block)| {
10727 let leaf = leaf.unwrap();
10728 actual_leaves.push(leaf.clone());
10729 actual_blocks.push(block.unwrap());
10730 leaf
10731 });
10732
10733 let upgrade_height = timeout(Duration::from_secs(600), async {
10735 loop {
10736 let leaf = leaves.next().await.unwrap();
10737 if leaf.header().version() >= NEW_PROTOCOL_VERSION {
10738 break leaf.height();
10739 }
10740 tracing::info!(
10741 version = %leaf.header().version(),
10742 height = leaf.header().height(),
10743 view = ?leaf.leaf().view_number(),
10744 "waiting for new protocol upgrade"
10745 );
10746 }
10747 })
10748 .await
10749 .expect("the network did not upgrade to the new protocol");
10750 let upgrade_epoch = epoch_from_block_number(upgrade_height, EPOCH_HEIGHT);
10751 tracing::info!(upgrade_height, upgrade_epoch, "new protocol enabled");
10752
10753 let epoch_change_height = timeout(Duration::from_secs(300), async {
10756 loop {
10757 let leaf = leaves.next().await.unwrap();
10758 let epoch = epoch_from_block_number(leaf.height(), EPOCH_HEIGHT);
10759 if epoch > upgrade_epoch {
10760 break leaf.height();
10761 }
10762 tracing::info!(
10763 height = leaf.height(),
10764 ?epoch,
10765 "waiting for a post-upgrade epoch change"
10766 );
10767 }
10768 })
10769 .await
10770 .expect("no epoch change happened after the upgrade");
10771 tracing::info!(epoch_change_height, "post upgrade epoch change");
10772
10773 let max_block = epoch_change_height + 3;
10776 timeout(Duration::from_secs(120), async {
10777 loop {
10778 let leaf = leaves.next().await.unwrap();
10779 if leaf.height() > max_block {
10780 break;
10781 }
10782 tracing::info!(max_block, height = leaf.height(), "waiting for block");
10783 }
10784 })
10785 .await
10786 .expect("the chain stopped making progress after the upgrade");
10787
10788 network.stop_consensus().await;
10790
10791 let heights =
10794 (upgrade_height - 3..=upgrade_height + 1).chain(epoch_change_height - 1..=max_block);
10795
10796 check_light_client_proofs(
10797 &client,
10798 &actual_leaves,
10799 &actual_blocks,
10800 heights,
10801 EPOCH_HEIGHT,
10802 )
10803 .await;
10804
10805 let client = &client;
10806 let finality_proof = |height: u64| async move {
10807 client
10808 .get::<LeafProof>(&format!("light-client/leaf/{height}"))
10809 .send()
10810 .await
10811 .unwrap()
10812 };
10813 for height in upgrade_height - 10..=upgrade_height - 3 {
10815 let proof = finality_proof(height).await;
10816 assert!(
10817 matches!(proof.proof(), FinalityProof::HotStuff2 { .. }),
10818 "leaf {height} should be proven by a HotStuff2 QC chain, got {:?}",
10819 proof.proof(),
10820 );
10821 }
10822
10823 for height in [upgrade_height - 1, epoch_change_height] {
10829 let proof = finality_proof(height).await;
10830 assert!(
10831 matches!(proof.proof(), FinalityProof::NewProtocol { .. }),
10832 "leaf {height} should be proven by a new protocol certificate, got {:?}",
10833 proof.proof(),
10834 );
10835 }
10836
10837 let first_epoch = EpochNumber::new(epoch_from_block_number(0, EPOCH_HEIGHT));
10840 check_light_client_stake_table(client, &network.server, first_epoch).await;
10841 }
10842
10843 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10845 async fn test_fetch_leaf_returns_exact_height() -> anyhow::Result<()> {
10846 const EPOCH_HEIGHT: u64 = 10;
10847 const NUM_NODES: usize = 5;
10848 const TARGET_HEIGHT: u64 = EPOCH_HEIGHT * 3 + 2;
10849
10850 let network_config = TestConfigBuilder::default()
10851 .epoch_height(EPOCH_HEIGHT)
10852 .build();
10853
10854 let port = reserve_tcp_port().expect("No ports free for query service");
10855
10856 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10857 let persistence: [_; NUM_NODES] = storage
10858 .iter()
10859 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10860 .collect::<Vec<_>>()
10861 .try_into()
10862 .unwrap();
10863
10864 let catchup_peers = std::array::from_fn(|_| {
10865 StatePeers::<StaticVersion<0, 1>>::from_urls(
10866 vec![format!("http://localhost:{port}").parse().unwrap()],
10867 Default::default(),
10868 Duration::from_secs(2),
10869 &NoMetrics,
10870 )
10871 });
10872
10873 let config = TestNetworkConfigBuilder::with_num_nodes()
10874 .api_config(SqlDataSource::options(
10875 &storage[0],
10876 Options::with_port(port),
10877 ))
10878 .network_config(network_config)
10879 .persistences(persistence)
10880 .catchups(catchup_peers)
10881 .pos_hook(
10882 DelegationConfig::MultipleDelegators,
10883 Default::default(),
10884 POS_V4,
10885 )
10886 .await?
10887 .build();
10888
10889 let network = TestNetwork::new(config, POS_V4).await;
10890
10891 let height_client: Client<ClientErr, StaticVersion<0, 1>> =
10893 Client::new(format!("http://localhost:{port}").parse().unwrap());
10894 wait_until_block_height(&height_client, "node/block-height", TARGET_HEIGHT + 5).await;
10895
10896 let coordinator = network.server.node_state().coordinator;
10897
10898 let catchup = StatePeers::<StaticVersion<0, 1>>::from_urls(
10900 vec![format!("http://localhost:{port}").parse().unwrap()],
10901 Default::default(),
10902 Duration::from_secs(5),
10903 &NoMetrics,
10904 );
10905
10906 let leaf = catchup.fetch_leaf(coordinator, TARGET_HEIGHT).await?;
10907
10908 assert_eq!(
10909 leaf.height(),
10910 TARGET_HEIGHT,
10911 "fetch_leaf must return the leaf at exactly the requested height"
10912 );
10913
10914 Ok(())
10915 }
10916
10917 #[test_log::test(tokio::test(flavor = "multi_thread"))]
10920 async fn test_v5_restart_before_epoch_boundary() {
10921 const NUM_NODES: usize = 3;
10922 const EPOCH_HEIGHT: u64 = 10;
10923 const RESTART_EPOCH: u64 = 4;
10926 const RESTART_BOUNDARY: u64 = RESTART_EPOCH * EPOCH_HEIGHT;
10927 const RESTART_HEIGHT: u64 = RESTART_BOUNDARY - 3;
10928 const BLOCKS_AFTER_RESTART: u64 = 5;
10930
10931 const V5: Upgrade = Upgrade::trivial(EPOCH_REWARD_VERSION);
10932
10933 let port = reserve_tcp_port().expect("OS should have ephemeral ports available");
10934
10935 let network_config = TestConfigBuilder::<NUM_NODES>::default()
10939 .epoch_height(EPOCH_HEIGHT)
10940 .builder_timeout(Duration::from_secs(3))
10941 .next_view_timeout(Duration::from_secs(10))
10942 .build();
10943
10944 let storage = join_all((0..NUM_NODES).map(|_| SqlDataSource::create_storage())).await;
10945 let persistence: [_; NUM_NODES] = storage
10946 .iter()
10947 .map(<SqlDataSource as TestableSequencerDataSource>::persistence_options)
10948 .collect::<Vec<_>>()
10949 .try_into()
10950 .unwrap();
10951
10952 let config = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
10953 .api_config(SqlDataSource::options(
10954 &storage[0],
10955 Options::with_port(port),
10956 ))
10957 .persistences(persistence.clone())
10958 .catchups(std::array::from_fn(|_| {
10959 StatePeers::<SequencerApiVersion>::from_urls(
10960 vec![format!("http://localhost:{port}").parse().unwrap()],
10961 Default::default(),
10962 Duration::from_secs(2),
10963 &NoMetrics,
10964 )
10965 }))
10966 .network_config(network_config)
10967 .pos_hook(DelegationConfig::MultipleDelegators, Default::default(), V5)
10968 .await
10969 .unwrap()
10970 .build();
10971
10972 let mut network = TestNetwork::new(config, V5).await;
10973
10974 {
10979 let mut events = network.server.event_stream();
10980 'wait: loop {
10981 let event = events
10982 .next()
10983 .await
10984 .expect("event stream ended unexpectedly");
10985 let CoordinatorEvent::LegacyEvent(Event {
10986 event: EventType::Decide { leaf_chain, .. },
10987 ..
10988 }) = event
10989 else {
10990 continue;
10991 };
10992 for LeafInfo { leaf, .. } in leaf_chain.iter() {
10995 if leaf.block_header().height() >= RESTART_HEIGHT {
10996 break 'wait;
10997 }
10998 }
10999 }
11000 }
11001
11002 let restart_height = network.server.decided_leaf().await.height();
11003 tracing::info!(
11004 restart_height,
11005 restart_epoch = RESTART_EPOCH,
11006 restart_boundary = RESTART_BOUNDARY,
11007 "restarting all nodes 3 blocks before an epoch boundary"
11008 );
11009
11010 let saved_cfg = network.cfg.clone();
11012
11013 network.stop_consensus().await;
11014 drop(network);
11015
11016 let port2 = reserve_tcp_port().expect("OS should have ephemeral ports available");
11018 let config2 = TestNetworkConfigBuilder::<NUM_NODES, _, _>::with_num_nodes()
11019 .api_config(SqlDataSource::options(
11020 &storage[0],
11021 Options::with_port(port2),
11022 ))
11023 .persistences(persistence)
11024 .catchups(std::array::from_fn(|_| {
11025 StatePeers::<SequencerApiVersion>::from_urls(
11026 vec![format!("http://localhost:{port2}").parse().unwrap()],
11027 Default::default(),
11028 Duration::from_secs(2),
11029 &NoMetrics,
11030 )
11031 }))
11032 .network_config(saved_cfg)
11033 .build();
11034 let network2 = TestNetwork::new(config2, V5).await;
11035
11036 let target_height = restart_height + BLOCKS_AFTER_RESTART;
11040 let stall_limit = 30; let mut last_height = network2.server.decided_leaf().await.height();
11042 let mut stalled_polls = 0;
11043 while last_height < target_height {
11044 sleep(Duration::from_secs(2)).await;
11045 let height = network2.server.decided_leaf().await.height();
11046 if height > last_height {
11047 last_height = height;
11048 stalled_polls = 0;
11049 } else {
11050 stalled_polls += 1;
11051 if stalled_polls >= stall_limit {
11052 panic!(
11053 "chain stalled after restart 3 blocks before boundary {RESTART_BOUNDARY}: \
11054 no new decide for 60s at height {last_height}, unable to produce/cross \
11055 the epoch boundary (target height was {target_height})."
11056 );
11057 }
11058 }
11059 }
11060 }
11061}