1use std::collections::HashMap;
12
13use alloy::primitives::{Address, U256};
14use anyhow::Context;
15use async_trait::async_trait;
16use espresso_types::{
17 PubKey,
18 v0_3::{ChainConfig, RegisteredValidator},
19};
20
21pub mod fs;
22pub mod no_storage;
23mod persistence_metrics;
24pub mod sql {
29 mod options;
30
31 pub use options::*;
32
33 pub mod postgres;
34 pub mod sqlite;
35}
36pub(crate) mod storage_probe;
37
38#[derive(serde::Serialize, serde::Deserialize)]
41pub(crate) struct RegisteredValidatorNoX25519 {
42 pub account: Address,
43 pub stake_table_key: PubKey,
44 pub state_ver_key: hotshot_types::light_client::StateVerKey,
45 pub stake: U256,
46 pub commission: u16,
47 pub delegators: HashMap<Address, U256>,
48 pub authenticated: bool,
49}
50
51impl RegisteredValidatorNoX25519 {
52 pub fn migrate(self) -> RegisteredValidator<PubKey> {
53 RegisteredValidator {
54 account: self.account,
55 stake_table_key: Some(self.stake_table_key),
56 state_ver_key: Some(self.state_ver_key),
57 stake: self.stake,
58 commission: self.commission,
59 delegators: self.delegators,
60 authenticated: self.authenticated,
61 x25519_key: None,
62 p2p_addr: None,
63 }
64 }
65}
66
67#[derive(serde::Serialize, serde::Deserialize)]
70pub(crate) struct RegisteredValidatorPreOption {
71 pub account: Address,
72 pub stake_table_key: PubKey,
73 pub state_ver_key: hotshot_types::light_client::StateVerKey,
74 pub stake: U256,
75 pub commission: u16,
76 pub delegators: HashMap<Address, U256>,
77 pub authenticated: bool,
78 pub x25519_key: Option<hotshot_types::x25519::PublicKey>,
79 pub p2p_addr: Option<hotshot_types::addr::NetAddr>,
80}
81
82impl RegisteredValidatorPreOption {
83 pub fn migrate(self) -> RegisteredValidator<PubKey> {
84 RegisteredValidator {
85 account: self.account,
86 stake_table_key: Some(self.stake_table_key),
87 state_ver_key: Some(self.state_ver_key),
88 stake: self.stake,
89 commission: self.commission,
90 delegators: self.delegators,
91 authenticated: self.authenticated,
92 x25519_key: self.x25519_key,
93 p2p_addr: self.p2p_addr,
94 }
95 }
96}
97
98#[derive(serde::Serialize, serde::Deserialize)]
103pub(crate) struct RegisteredValidatorPreSchnorrOption {
104 pub account: Address,
105 pub stake_table_key: Option<PubKey>,
106 pub state_ver_key: hotshot_types::light_client::StateVerKey,
107 pub stake: U256,
108 pub commission: u16,
109 pub delegators: HashMap<Address, U256>,
110 pub authenticated: bool,
111 pub x25519_key: Option<hotshot_types::x25519::PublicKey>,
112 pub p2p_addr: Option<hotshot_types::addr::NetAddr>,
113}
114
115impl RegisteredValidatorPreSchnorrOption {
116 pub fn migrate(self) -> RegisteredValidator<PubKey> {
117 RegisteredValidator {
118 account: self.account,
119 stake_table_key: self.stake_table_key,
120 state_ver_key: Some(self.state_ver_key),
121 stake: self.stake,
122 commission: self.commission,
123 delegators: self.delegators,
124 authenticated: self.authenticated,
125 x25519_key: self.x25519_key,
126 p2p_addr: self.p2p_addr,
127 }
128 }
129}
130
131fn migrate_network_config(
133 mut network_config: serde_json::Value,
134) -> anyhow::Result<serde_json::Value> {
135 let config = network_config
136 .get_mut("config")
137 .context("missing field `config`")?
138 .as_object_mut()
139 .context("`config` must be an object")?;
140
141 if !config.contains_key("builder_urls") {
142 let url = config
147 .remove("builder_url")
148 .context("missing field `builder_url`")?;
149 config.insert("builder_urls".into(), vec![url].into());
150 }
151
152 if !config.contains_key("start_proposing_view") {
158 config.insert("start_proposing_view".into(), 9007199254740991u64.into());
159 }
160 if !config.contains_key("stop_proposing_view") {
161 config.insert("stop_proposing_view".into(), 0.into());
162 }
163 if !config.contains_key("start_voting_view") {
164 config.insert("start_voting_view".into(), 9007199254740991u64.into());
165 }
166 if !config.contains_key("stop_voting_view") {
167 config.insert("stop_voting_view".into(), 0.into());
168 }
169 if !config.contains_key("start_proposing_time") {
170 config.insert("start_proposing_time".into(), 9007199254740991u64.into());
171 }
172 if !config.contains_key("stop_proposing_time") {
173 config.insert("stop_proposing_time".into(), 0.into());
174 }
175 if !config.contains_key("start_voting_time") {
176 config.insert("start_voting_time".into(), 9007199254740991u64.into());
177 }
178 if !config.contains_key("stop_voting_time") {
179 config.insert("stop_voting_time".into(), 0.into());
180 }
181
182 if !config.contains_key("epoch_height") {
185 config.insert("epoch_height".into(), 0.into());
186 }
187
188 if !config.contains_key("drb_difficulty") {
191 config.insert("drb_difficulty".into(), 0.into());
192 }
193 if !config.contains_key("drb_upgrade_difficulty") {
194 config.insert("drb_upgrade_difficulty".into(), 0.into());
195 }
196
197 if !config.contains_key("da_committees") {
199 config.insert("da_committees".into(), serde_json::json!([]));
200 }
201
202 Ok(network_config)
203}
204
205#[async_trait]
206pub trait ChainConfigPersistence: Sized + Send + Sync {
207 async fn insert_chain_config(&mut self, chain_config: ChainConfig) -> anyhow::Result<()>;
208}
209
210#[cfg(test)]
211mod tests {
212 use std::{cmp::max, collections::BTreeMap, marker::PhantomData, sync::Arc, time::Duration};
213
214 use alloy::{
215 network::EthereumWallet,
216 primitives::{Address, U256},
217 providers::{Provider, ProviderBuilder, ext::AnvilApi},
218 };
219 use anyhow::{bail, ensure};
220 use async_lock::{Mutex, RwLock};
221 use async_trait::async_trait;
222 use committable::{Commitment, Committable};
223 use espresso_contract_deployer::{
224 Contract, Contracts, DEFAULT_EXIT_ESCROW_PERIOD_SECONDS, builder::DeployerArgsBuilder,
225 network_config::light_client_genesis_from_stake_table,
226 };
227 use espresso_types::{
228 L1Client, L1ClientOptions, Leaf, Leaf2, NodeState, PubKey, SeqTypes, TEST_UPGRADE,
229 ValidatedState,
230 traits::{
231 EventConsumer, EventsPersistenceRead, MembershipPersistence, NullEventConsumer,
232 PersistenceOptions, SequencerPersistence,
233 },
234 v0_3::{AuthenticatedValidator, EventKey, Fetcher, RegisteredValidator, StakeTableEvent},
235 };
236 use futures::{StreamExt, TryStreamExt, future::join_all};
237 use hotshot_contract_adapter::{
238 sol_types::StakeTableV3::Delegated, stake_table::StakeTableContractVersion,
239 };
240 use hotshot_example_types::node_types::TEST_VERSIONS;
241 use hotshot_new_protocol::message::Certificate2;
242 use hotshot_query_service::{availability::BlockQueryData, testing::mocks::MOCK_UPGRADE};
243 use hotshot_types::{
244 data::{
245 DaProposal2, EpochNumber, QuorumProposal2, QuorumProposalWrapper, VidCommitment,
246 ViewNumber,
247 ns_table::parse_ns_table,
248 vid_commitment,
249 vid_disperse::{AvidMDisperseShare, AvidmGf2DisperseShare},
250 },
251 event::{HotShotAction, LeafInfo},
252 initializer::InitializerEpochInfo,
253 light_client::{LightClientState, StakeTableState, StateKeyPair},
254 message::{Proposal, UpgradeLock, convert_proposal},
255 new_protocol::CoordinatorEvent,
256 signature_key::BLSPubKey,
257 simple_certificate::{
258 CertificatePair, LightClientStateUpdateCertificateV2, NextEpochQuorumCertificate2,
259 QuorumCertificate, QuorumCertificate2, UpgradeCertificate,
260 },
261 simple_vote::{
262 NextEpochQuorumData2, QuorumData2, UpgradeProposalData, VersionedVoteData, Vote2Data,
263 },
264 traits::{EncodeBytes, block_contents::BlockHeader, signature_key::SignatureKey},
265 utils::EpochTransitionIndicator,
266 vid::{
267 avidm::{AvidMScheme, init_avidm_param},
268 avidm_gf2::{AvidmGf2Scheme, init_avidm_gf2_param},
269 },
270 vote::HasViewNumber,
271 };
272 use http_client::{Client, error::ClientErr};
273 use indexmap::IndexMap;
274 use staking_cli::demo::{DelegationConfig, StakingTransactions};
275 use test_utils::reserve_tcp_port;
276 use tokio::{spawn, time::sleep};
277 use vbs::version::Version;
278 use versions::Upgrade;
279
280 use crate::{
281 RECENT_STAKE_TABLES_LIMIT, SequencerApiVersion,
282 api::{
283 Options,
284 data_source::testing::TestableSqlDataSource,
285 test_helpers::{STAKE_TABLE_CAPACITY_FOR_TEST, TestNetwork, TestNetworkConfigBuilder},
286 },
287 catchup::NullStateCatchup,
288 legacy_chain::LegacyChain,
289 persistence::sql::DECIDE_GAP_FILL_HORIZON,
290 prefetch_stake_table_events,
291 testing::{TestConfigBuilder, staking_priv_keys},
292 };
293
294 #[async_trait]
295 pub trait TestablePersistence: SequencerPersistence + MembershipPersistence {
296 type Storage: Sync;
297
298 async fn tmp_storage() -> Self::Storage;
299 fn options(storage: &Self::Storage) -> impl PersistenceOptions<Persistence = Self>;
300
301 async fn connect(storage: &Self::Storage) -> Self {
302 Self::options(storage).create().await.unwrap()
303 }
304 }
305
306 #[rstest_reuse::template]
307 #[rstest::rstest]
308 #[case::postgres(PhantomData::<crate::persistence::sql::postgres::Persistence>)]
309 #[case::sqlite(PhantomData::<crate::persistence::sql::sqlite::Persistence>)]
310 #[case::fs(PhantomData::<crate::persistence::fs::Persistence>)]
311 #[test_log::test(tokio::test(flavor = "multi_thread"))]
312 pub fn persistence_types<P: TestablePersistence>(#[case] _p: PhantomData<P>) {}
313
314 #[derive(Clone, Debug, Default)]
315 struct EventCollector {
316 events: Arc<RwLock<Vec<CoordinatorEvent<SeqTypes>>>>,
317 }
318
319 impl EventCollector {
320 async fn leaf_chain(&self) -> Vec<LeafInfo<SeqTypes>> {
322 self.events
323 .read()
324 .await
325 .iter()
326 .flat_map(|event| {
327 let CoordinatorEvent::NewDecide { leaf_infos, .. } = event else {
328 panic!("expected decide event, got {event:?}");
329 };
330 leaf_infos.iter().cloned().rev()
331 })
332 .collect::<Vec<_>>()
333 }
334 }
335
336 #[async_trait]
337 impl EventConsumer for EventCollector {
338 async fn handle_event(&self, event: &CoordinatorEvent<SeqTypes>) -> anyhow::Result<()> {
339 self.events.write().await.push(event.clone());
340 Ok(())
341 }
342 }
343
344 #[derive(Clone, Copy, Debug)]
345 struct FailConsumer;
346
347 #[async_trait]
348 impl EventConsumer for FailConsumer {
349 async fn handle_event(&self, _: &CoordinatorEvent<SeqTypes>) -> anyhow::Result<()> {
350 bail!("mock error injection");
351 }
352 }
353
354 #[rstest_reuse::apply(persistence_types)]
355 pub async fn test_voted_view<P: TestablePersistence>(_p: PhantomData<P>) {
356 let tmp = P::tmp_storage().await;
357 let storage = P::connect(&tmp).await;
358
359 assert_eq!(storage.load_latest_acted_view().await.unwrap(), None);
361
362 let view1 = ViewNumber::genesis();
364 storage
365 .record_action(view1, HotShotAction::Vote)
366 .await
367 .unwrap();
368 assert_eq!(
369 storage.load_latest_acted_view().await.unwrap().unwrap(),
370 view1
371 );
372
373 let view2 = view1 + 1;
375 storage
376 .record_action(view2, HotShotAction::Vote)
377 .await
378 .unwrap();
379 assert_eq!(
380 storage.load_latest_acted_view().await.unwrap().unwrap(),
381 view2
382 );
383
384 storage
386 .record_action(view1, HotShotAction::Vote)
387 .await
388 .unwrap();
389 assert_eq!(
390 storage.load_latest_acted_view().await.unwrap().unwrap(),
391 view2
392 );
393 }
394
395 #[rstest_reuse::apply(persistence_types)]
396 pub async fn test_high_qc2_monotonic<P: TestablePersistence>(_p: PhantomData<P>) {
397 let tmp = P::tmp_storage().await;
398 let storage = P::connect(&tmp).await;
399
400 assert_eq!(storage.load_high_qc2().await.unwrap(), None);
402
403 let mut qc = QuorumCertificate2::genesis(
404 &ValidatedState::default(),
405 &NodeState::mock(),
406 TEST_VERSIONS.test,
407 )
408 .await;
409
410 qc.view_number = ViewNumber::new(5);
412 storage.append_high_qc2(qc.clone()).await.unwrap();
413 assert_eq!(
414 storage.load_high_qc2().await.unwrap().unwrap().view_number,
415 ViewNumber::new(5)
416 );
417
418 qc.view_number = ViewNumber::new(6);
420 storage.append_high_qc2(qc.clone()).await.unwrap();
421 assert_eq!(
422 storage.load_high_qc2().await.unwrap().unwrap().view_number,
423 ViewNumber::new(6)
424 );
425
426 qc.view_number = ViewNumber::new(4);
429 storage.append_high_qc2(qc.clone()).await.unwrap();
430 assert_eq!(
431 storage.load_high_qc2().await.unwrap().unwrap().view_number,
432 ViewNumber::new(6)
433 );
434
435 let epoch = qc.data.epoch.unwrap_or_else(EpochNumber::genesis);
438 let at = |qc: &QuorumCertificate2<SeqTypes>| (qc.data.epoch, qc.view_number);
439 qc.data.epoch = Some(epoch + 1);
440 qc.view_number = ViewNumber::new(5);
441 storage.append_high_qc2(qc.clone()).await.unwrap();
442 assert_eq!(
443 at(&storage.load_high_qc2().await.unwrap().unwrap()),
444 (Some(epoch + 1), ViewNumber::new(5))
445 );
446 qc.data.epoch = Some(epoch);
447 qc.view_number = ViewNumber::new(9);
448 storage.append_high_qc2(qc.clone()).await.unwrap();
449 assert_eq!(
450 at(&storage.load_high_qc2().await.unwrap().unwrap()),
451 (Some(epoch + 1), ViewNumber::new(5))
452 );
453 }
454
455 #[rstest_reuse::apply(persistence_types)]
456 pub async fn test_boundary_qc2_monotonic<P: TestablePersistence>(_p: PhantomData<P>) {
457 let tmp = P::tmp_storage().await;
458 let storage = P::connect(&tmp).await;
459
460 assert_eq!(storage.load_boundary_qc2().await.unwrap(), None);
461
462 let mut qc = QuorumCertificate2::genesis(
463 &ValidatedState::default(),
464 &NodeState::mock(),
465 TEST_VERSIONS.test,
466 )
467 .await;
468 let at = |qc: &QuorumCertificate2<SeqTypes>| (qc.data.epoch, qc.view_number);
469
470 qc.data.epoch = Some(EpochNumber::new(2));
472 qc.view_number = ViewNumber::new(5);
473 storage.append_boundary_qc2(qc.clone()).await.unwrap();
474 assert_eq!(
475 at(&storage.load_boundary_qc2().await.unwrap().unwrap()),
476 (Some(EpochNumber::new(2)), ViewNumber::new(5))
477 );
478
479 qc.data.epoch = Some(EpochNumber::new(1));
481 qc.view_number = ViewNumber::new(9);
482 storage.append_boundary_qc2(qc.clone()).await.unwrap();
483 assert_eq!(
484 at(&storage.load_boundary_qc2().await.unwrap().unwrap()),
485 (Some(EpochNumber::new(2)), ViewNumber::new(5))
486 );
487
488 qc.data.epoch = Some(EpochNumber::new(3));
490 qc.view_number = ViewNumber::new(4);
491 storage.append_boundary_qc2(qc.clone()).await.unwrap();
492 assert_eq!(
493 at(&storage.load_boundary_qc2().await.unwrap().unwrap()),
494 (Some(EpochNumber::new(3)), ViewNumber::new(4))
495 );
496
497 qc.view_number = ViewNumber::new(6);
499 storage.append_boundary_qc2(qc.clone()).await.unwrap();
500 assert_eq!(
501 at(&storage.load_boundary_qc2().await.unwrap().unwrap()),
502 (Some(EpochNumber::new(3)), ViewNumber::new(6))
503 );
504 qc.view_number = ViewNumber::new(5);
505 storage.append_boundary_qc2(qc.clone()).await.unwrap();
506 assert_eq!(
507 at(&storage.load_boundary_qc2().await.unwrap().unwrap()),
508 (Some(EpochNumber::new(3)), ViewNumber::new(6))
509 );
510 }
511
512 #[rstest_reuse::apply(persistence_types)]
513 pub async fn test_restart_view<P: TestablePersistence>(_p: PhantomData<P>) {
514 let tmp = P::tmp_storage().await;
515 let storage = P::connect(&tmp).await;
516
517 assert_eq!(storage.load_restart_view().await.unwrap(), None);
519
520 let view1 = ViewNumber::genesis();
522 storage
523 .record_action(view1, HotShotAction::Vote)
524 .await
525 .unwrap();
526 assert_eq!(
527 storage.load_restart_view().await.unwrap().unwrap(),
528 view1 + 1
529 );
530
531 let view2 = view1 + 1;
533 storage
534 .record_action(view2, HotShotAction::Vote)
535 .await
536 .unwrap();
537 assert_eq!(
538 storage.load_restart_view().await.unwrap().unwrap(),
539 view2 + 1
540 );
541
542 storage
544 .record_action(view1, HotShotAction::Vote)
545 .await
546 .unwrap();
547 assert_eq!(
548 storage.load_restart_view().await.unwrap().unwrap(),
549 view2 + 1
550 );
551
552 storage
554 .record_action(view2 + 1, HotShotAction::Propose)
555 .await
556 .unwrap();
557 assert_eq!(
558 storage.load_restart_view().await.unwrap().unwrap(),
559 view2 + 1
560 );
561
562 storage
564 .record_action(view2 + 1, HotShotAction::TimeoutVote)
565 .await
566 .unwrap();
567 assert_eq!(
568 storage.load_restart_view().await.unwrap().unwrap(),
569 view2 + 1
570 );
571 }
572
573 #[rstest_reuse::apply(persistence_types)]
574 pub async fn test_store_drb_input<P: TestablePersistence>(_p: PhantomData<P>) {
575 use hotshot_types::drb::DrbInput;
576
577 let tmp = P::tmp_storage().await;
578 let storage = P::connect(&tmp).await;
579 let difficulty_level = 10;
580
581 if storage.load_drb_input(10).await.is_ok() {
583 panic!("unexpected nonempty drb_input");
584 }
585
586 let drb_input_1 = DrbInput {
587 epoch: 10,
588 iteration: 10,
589 value: [0u8; 32],
590 difficulty_level,
591 };
592
593 let drb_input_2 = DrbInput {
594 epoch: 10,
595 iteration: 20,
596 value: [0u8; 32],
597 difficulty_level,
598 };
599
600 let drb_input_3 = DrbInput {
601 epoch: 10,
602 iteration: 30,
603 value: [0u8; 32],
604 difficulty_level,
605 };
606
607 let _ = storage.store_drb_input(drb_input_1.clone()).await;
608
609 assert_eq!(storage.load_drb_input(10).await.unwrap(), drb_input_1);
610
611 let _ = storage.store_drb_input(drb_input_3.clone()).await;
612
613 assert_eq!(storage.load_drb_input(10).await.unwrap(), drb_input_3);
615
616 let _ = storage.store_drb_input(drb_input_2.clone()).await;
617
618 assert_eq!(storage.load_drb_input(10).await.unwrap(), drb_input_3);
620 }
621
622 #[rstest_reuse::apply(persistence_types)]
623 pub async fn test_epoch_info<P: TestablePersistence>(_p: PhantomData<P>) {
624 let tmp = P::tmp_storage().await;
625 let storage = P::connect(&tmp).await;
626
627 assert_eq!(storage.load_start_epoch_info().await.unwrap(), Vec::new());
629
630 storage
632 .store_drb_result(EpochNumber::new(1), [1; 32])
633 .await
634 .unwrap();
635 assert_eq!(
636 storage.load_start_epoch_info().await.unwrap(),
637 vec![InitializerEpochInfo::<SeqTypes> {
638 epoch: EpochNumber::new(1),
639 drb_result: [1; 32],
640 block_header: None,
641 }]
642 );
643
644 storage
646 .store_drb_result(EpochNumber::new(2), [3; 32])
647 .await
648 .unwrap();
649 assert_eq!(
650 storage.load_start_epoch_info().await.unwrap(),
651 vec![
652 InitializerEpochInfo::<SeqTypes> {
653 epoch: EpochNumber::new(1),
654 drb_result: [1; 32],
655 block_header: None,
656 },
657 InitializerEpochInfo::<SeqTypes> {
658 epoch: EpochNumber::new(2),
659 drb_result: [3; 32],
660 block_header: None,
661 }
662 ]
663 );
664
665 let instance_state = NodeState::mock();
667 let validated_state = hotshot_types::traits::ValidatedState::genesis(&instance_state).0;
668 let leaf: Leaf2 = Leaf::genesis(&validated_state, &instance_state, MOCK_UPGRADE.base)
669 .await
670 .into();
671 let header = leaf.block_header().clone();
672
673 storage
675 .store_epoch_root(EpochNumber::new(1), header.clone())
676 .await
677 .unwrap();
678 assert_eq!(
679 storage.load_start_epoch_info().await.unwrap(),
680 vec![
681 InitializerEpochInfo::<SeqTypes> {
682 epoch: EpochNumber::new(1),
683 drb_result: [1; 32],
684 block_header: Some(header.clone()),
685 },
686 InitializerEpochInfo::<SeqTypes> {
687 epoch: EpochNumber::new(2),
688 drb_result: [3; 32],
689 block_header: None,
690 }
691 ]
692 );
693
694 let total_epochs = RECENT_STAKE_TABLES_LIMIT + 10;
696 for i in 0..total_epochs {
697 let epoch = EpochNumber::new(i);
698 let drb = [i as u8; 32];
699 storage
700 .store_drb_result(epoch, drb)
701 .await
702 .unwrap_or_else(|_| panic!("Failed to store DRB result for epoch {i}"));
703 }
704
705 let results = storage.load_start_epoch_info().await.unwrap();
706
707 assert_eq!(
709 results.len(),
710 RECENT_STAKE_TABLES_LIMIT as usize,
711 "Should return only the most recent {RECENT_STAKE_TABLES_LIMIT} epochs",
712 );
713
714 for (i, info) in results.iter().enumerate() {
715 let expected_epoch =
716 EpochNumber::new(total_epochs - RECENT_STAKE_TABLES_LIMIT + i as u64);
717 let expected_drb = [(total_epochs - RECENT_STAKE_TABLES_LIMIT + i as u64) as u8; 32];
718 assert_eq!(info.epoch, expected_epoch, "invalid epoch at index {i}",);
719 assert_eq!(info.drb_result, expected_drb, "invalid DRB at index {i}",);
720 assert!(info.block_header.is_none(), "Expected no block header");
721 }
722 }
723
724 fn leaf_info(leaf: Leaf2) -> LeafInfo<SeqTypes> {
725 LeafInfo {
726 leaf,
727 vid_share: None,
728 state: Default::default(),
729 delta: None,
730 state_cert: None,
731 }
732 }
733
734 #[rstest_reuse::apply(persistence_types)]
735 pub async fn test_append_and_decide<P: TestablePersistence>(_p: PhantomData<P>) {
736 let tmp = P::tmp_storage().await;
737 let storage = P::connect(&tmp).await;
738
739 assert_eq!(
741 storage.load_vid_share(ViewNumber::new(0)).await.unwrap(),
742 None
743 );
744
745 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
746 let leaf: Leaf2 =
747 Leaf2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE.base).await;
748 let leaf_payload = leaf.block_payload().unwrap();
749 let leaf_payload_bytes_arc = leaf_payload.encode();
750
751 let avidm_param = init_avidm_gf2_param(2).unwrap();
752 let weights = vec![1u32; 2];
753
754 let ns_table = parse_ns_table(
755 leaf_payload.byte_len().as_usize(),
756 &leaf_payload.ns_table().encode(),
757 );
758 let (payload_commitment, common, shares) =
759 AvidmGf2Scheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
760 .unwrap();
761
762 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
763 let signature = PubKey::sign(&privkey, &[]).unwrap();
764 let mut vid = AvidmGf2DisperseShare::<SeqTypes> {
765 view_number: ViewNumber::new(0),
766 payload_commitment,
767 share: shares[0].clone(),
768 recipient_key: pubkey,
769 epoch: Some(EpochNumber::new(0)),
770 target_epoch: Some(EpochNumber::new(0)),
771 common,
772 };
773 let mut quorum_proposal = Proposal {
774 data: QuorumProposalWrapper::<SeqTypes> {
775 proposal: QuorumProposal2::<SeqTypes> {
776 epoch: None,
777 block_header: leaf.block_header().clone(),
778 view_number: ViewNumber::genesis(),
779 justify_qc: QuorumCertificate2::genesis(
780 &ValidatedState::default(),
781 &node_state,
782 TEST_UPGRADE,
783 )
784 .await,
785 upgrade_certificate: None,
786 view_change_evidence: None,
787 next_drb_result: None,
788 next_epoch_justify_qc: None,
789 state_cert: None,
790 },
791 },
792 signature,
793 _pd: Default::default(),
794 };
795
796 let vid_share0 = convert_proposal(vid.clone().to_proposal(&privkey).unwrap().clone());
797
798 storage.append_vid(&vid_share0).await.unwrap();
799
800 assert_eq!(
801 storage.load_vid_share(ViewNumber::new(0)).await.unwrap(),
802 Some(vid_share0.clone())
803 );
804
805 vid.view_number = ViewNumber::new(1);
806
807 let vid_share1 = convert_proposal(vid.clone().to_proposal(&privkey).unwrap().clone());
808 storage.append_vid(&vid_share1).await.unwrap();
809
810 assert_eq!(
811 storage.load_vid_share(vid.view_number()).await.unwrap(),
812 Some(vid_share1.clone())
813 );
814
815 vid.view_number = ViewNumber::new(2);
816
817 let vid_share2 = convert_proposal(vid.clone().to_proposal(&privkey).unwrap().clone());
818 storage.append_vid(&vid_share2).await.unwrap();
819
820 assert_eq!(
821 storage.load_vid_share(vid.view_number()).await.unwrap(),
822 Some(vid_share2.clone())
823 );
824
825 vid.view_number = ViewNumber::new(3);
826
827 let vid_share3 = convert_proposal(vid.clone().to_proposal(&privkey).unwrap().clone());
828 storage.append_vid(&vid_share3).await.unwrap();
829
830 assert_eq!(
831 storage.load_vid_share(vid.view_number()).await.unwrap(),
832 Some(vid_share3.clone())
833 );
834
835 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
836 .expect("Failed to sign block payload");
837
838 let da_proposal_inner = DaProposal2::<SeqTypes> {
839 encoded_transactions: leaf_payload_bytes_arc.clone(),
840 metadata: leaf_payload.ns_table().clone(),
841 view_number: ViewNumber::new(0),
842 epoch: None,
843 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
844 };
845
846 let da_proposal = Proposal {
847 data: da_proposal_inner,
848 signature: block_payload_signature,
849 _pd: Default::default(),
850 };
851
852 let vid_commitment = vid_commitment(
853 &leaf_payload_bytes_arc,
854 &leaf.block_header().metadata().encode(),
855 2,
856 TEST_UPGRADE.base,
857 );
858
859 storage
860 .append_da2(&da_proposal, vid_commitment)
861 .await
862 .unwrap();
863
864 assert_eq!(
865 storage.load_da_proposal(ViewNumber::new(0)).await.unwrap(),
866 Some(da_proposal.clone())
867 );
868
869 let mut da_proposal1 = da_proposal.clone();
870 da_proposal1.data.view_number = ViewNumber::new(1);
871 storage
872 .append_da2(&da_proposal1.clone(), vid_commitment)
873 .await
874 .unwrap();
875
876 assert_eq!(
877 storage
878 .load_da_proposal(da_proposal1.data.view_number)
879 .await
880 .unwrap(),
881 Some(da_proposal1.clone())
882 );
883
884 let mut da_proposal2 = da_proposal1.clone();
885 da_proposal2.data.view_number = ViewNumber::new(2);
886 storage
887 .append_da2(&da_proposal2.clone(), vid_commitment)
888 .await
889 .unwrap();
890
891 assert_eq!(
892 storage
893 .load_da_proposal(da_proposal2.data.view_number)
894 .await
895 .unwrap(),
896 Some(da_proposal2.clone())
897 );
898
899 let mut da_proposal3 = da_proposal2.clone();
900 da_proposal3.data.view_number = ViewNumber::new(3);
901 storage
902 .append_da2(&da_proposal3.clone(), vid_commitment)
903 .await
904 .unwrap();
905
906 assert_eq!(
907 storage
908 .load_da_proposal(da_proposal3.data.view_number)
909 .await
910 .unwrap(),
911 Some(da_proposal3.clone())
912 );
913
914 let quorum_proposal1 = quorum_proposal.clone();
915
916 storage
917 .append_quorum_proposal2(&quorum_proposal1)
918 .await
919 .unwrap();
920
921 assert_eq!(
922 storage.load_quorum_proposals().await.unwrap(),
923 BTreeMap::from_iter([(ViewNumber::genesis(), quorum_proposal1.clone())])
924 );
925
926 quorum_proposal.data.proposal.view_number = ViewNumber::new(1);
927 let quorum_proposal2 = quorum_proposal.clone();
928 storage
929 .append_quorum_proposal2(&quorum_proposal2)
930 .await
931 .unwrap();
932
933 assert_eq!(
934 storage.load_quorum_proposals().await.unwrap(),
935 BTreeMap::from_iter([
936 (ViewNumber::genesis(), quorum_proposal1.clone()),
937 (ViewNumber::new(1), quorum_proposal2.clone())
938 ])
939 );
940
941 quorum_proposal.data.proposal.view_number = ViewNumber::new(2);
942 quorum_proposal.data.proposal.justify_qc.view_number = ViewNumber::new(1);
943 let quorum_proposal3 = quorum_proposal.clone();
944 storage
945 .append_quorum_proposal2(&quorum_proposal3)
946 .await
947 .unwrap();
948
949 assert_eq!(
950 storage.load_quorum_proposals().await.unwrap(),
951 BTreeMap::from_iter([
952 (ViewNumber::genesis(), quorum_proposal1.clone()),
953 (ViewNumber::new(1), quorum_proposal2.clone()),
954 (ViewNumber::new(2), quorum_proposal3.clone())
955 ])
956 );
957
958 quorum_proposal.data.proposal.view_number = ViewNumber::new(3);
959 quorum_proposal.data.proposal.justify_qc.view_number = ViewNumber::new(2);
960
961 let quorum_proposal4 = quorum_proposal.clone();
963 storage
964 .append_quorum_proposal2(&quorum_proposal4)
965 .await
966 .unwrap();
967
968 assert_eq!(
969 storage.load_quorum_proposals().await.unwrap(),
970 BTreeMap::from_iter([
971 (ViewNumber::genesis(), quorum_proposal1.clone()),
972 (ViewNumber::new(1), quorum_proposal2.clone()),
973 (ViewNumber::new(2), quorum_proposal3.clone()),
974 (ViewNumber::new(3), quorum_proposal4.clone())
975 ])
976 );
977
978 let leaves = [
981 Leaf2::from_quorum_proposal(&quorum_proposal1.data),
982 Leaf2::from_quorum_proposal(&quorum_proposal2.data),
983 Leaf2::from_quorum_proposal(&quorum_proposal3.data),
984 Leaf2::from_quorum_proposal(&quorum_proposal4.data),
985 ];
986 let mut final_qc = leaves[3].justify_qc().clone();
987 final_qc.view_number += 1;
988 final_qc.data.leaf_commit = Committable::commit(&leaf);
989 let qcs = [
990 CertificatePair::non_epoch_change(leaves[1].justify_qc().clone()),
991 CertificatePair::non_epoch_change(leaves[2].justify_qc().clone()),
992 CertificatePair::non_epoch_change(leaves[3].justify_qc().clone()),
993 CertificatePair::non_epoch_change(final_qc),
994 ];
995
996 assert_eq!(
997 storage.load_anchor_view().await.unwrap(),
998 ViewNumber::genesis()
999 );
1000
1001 let consumer = EventCollector::default();
1002 let leaf_chain = leaves
1003 .iter()
1004 .take(3)
1005 .map(|leaf| leaf_info(leaf.clone()))
1006 .zip(&qcs)
1007 .collect::<Vec<_>>();
1008 tracing::info!(?leaf_chain, "decide view 2");
1009 storage
1010 .append_decided_leaves(
1011 ViewNumber::new(2),
1012 leaf_chain.iter().map(|(leaf, qc)| (leaf, (*qc).clone())),
1013 &consumer,
1014 )
1015 .await
1016 .unwrap();
1017 assert_eq!(
1018 storage.load_anchor_view().await.unwrap(),
1019 ViewNumber::new(2)
1020 );
1021
1022 for i in 0..=2 {
1023 assert_eq!(
1024 storage.load_da_proposal(ViewNumber::new(i)).await.unwrap(),
1025 None
1026 );
1027
1028 assert_eq!(
1029 storage.load_vid_share(ViewNumber::new(i)).await.unwrap(),
1030 None
1031 );
1032 }
1033
1034 assert_eq!(
1035 storage.load_da_proposal(ViewNumber::new(3)).await.unwrap(),
1036 Some(da_proposal3)
1037 );
1038
1039 assert_eq!(
1040 storage.load_vid_share(ViewNumber::new(3)).await.unwrap(),
1041 Some(convert_proposal(vid_share3.clone()))
1042 );
1043
1044 let proposals = storage.load_quorum_proposals().await.unwrap();
1045 assert_eq!(
1046 proposals,
1047 BTreeMap::from_iter([(ViewNumber::new(3), quorum_proposal4)])
1048 );
1049
1050 for (leaf, info) in leaves.iter().zip(consumer.leaf_chain().await.iter()) {
1052 assert_eq!(info.leaf, *leaf);
1053 let decided_vid_share = info.vid_share.as_ref().unwrap();
1054 assert_eq!(decided_vid_share.view_number(), leaf.view_number());
1055 }
1056
1057 assert_eq!(
1059 storage.load_anchor_leaf().await.unwrap(),
1060 Some((leaves[2].clone(), qcs[2].clone()))
1061 );
1062 assert_eq!(
1063 storage.load_anchor_view().await.unwrap(),
1064 leaves[2].view_number()
1065 );
1066
1067 let consumer = EventCollector::default();
1069 tracing::info!(leaf = ?leaves[3], qc = ?qcs[3], "decide view 3");
1070 storage
1071 .append_decided_leaves(
1072 ViewNumber::new(3),
1073 vec![(&leaf_info(leaves[3].clone()), qcs[3].clone())],
1074 &consumer,
1075 )
1076 .await
1077 .unwrap();
1078 assert_eq!(
1079 storage.load_anchor_view().await.unwrap(),
1080 ViewNumber::new(3)
1081 );
1082
1083 let events = consumer.events.read().await;
1085 assert_eq!(events.len(), 1);
1086 let CoordinatorEvent::NewDecide {
1087 leaf_infos, cert1, ..
1088 } = &events[0]
1089 else {
1090 panic!("expected decide event, got {:?}", events[0]);
1091 };
1092 assert_eq!(cert1, qcs[3].qc());
1093 assert_eq!(leaf_infos.len(), 1);
1094 let info = &leaf_infos[0];
1095 assert_eq!(info.leaf, leaves[3]);
1096 assert_eq!(info.leaf.view_number(), ViewNumber::new(3));
1097
1098 assert_eq!(
1100 storage.load_da_proposal(ViewNumber::new(3)).await.unwrap(),
1101 None
1102 );
1103
1104 assert_eq!(
1105 storage.load_vid_share(ViewNumber::new(3)).await.unwrap(),
1106 None
1107 );
1108 assert_eq!(
1109 storage.load_quorum_proposals().await.unwrap(),
1110 BTreeMap::new()
1111 );
1112 }
1113
1114 #[rstest_reuse::apply(persistence_types)]
1115 pub async fn test_upgrade_certificate<P: TestablePersistence>(_p: PhantomData<P>) {
1116 let tmp = P::tmp_storage().await;
1117 let storage = P::connect(&tmp).await;
1118
1119 assert_eq!(storage.load_upgrade_certificate().await.unwrap(), None);
1121
1122 let upgrade_data = UpgradeProposalData {
1123 old_version: Version { major: 0, minor: 1 },
1124 new_version: Version { major: 1, minor: 0 },
1125 decide_by: ViewNumber::genesis(),
1126 new_version_hash: Default::default(),
1127 old_version_last_view: ViewNumber::genesis(),
1128 new_version_first_view: ViewNumber::genesis(),
1129 };
1130
1131 let decide_upgrade_certificate = UpgradeCertificate::<SeqTypes>::new(
1132 upgrade_data.clone(),
1133 upgrade_data.commit(),
1134 ViewNumber::genesis(),
1135 Default::default(),
1136 Default::default(),
1137 );
1138 let res = storage
1139 .store_upgrade_certificate(Some(decide_upgrade_certificate.clone()))
1140 .await;
1141 assert!(res.is_ok());
1142
1143 let res = storage.load_upgrade_certificate().await.unwrap();
1144 let view_number = res.unwrap().view_number;
1145 assert_eq!(view_number, ViewNumber::genesis());
1146
1147 let new_view_number_for_certificate = ViewNumber::new(50);
1148 let mut new_upgrade_certificate = decide_upgrade_certificate.clone();
1149 new_upgrade_certificate.view_number = new_view_number_for_certificate;
1150
1151 let res = storage
1152 .store_upgrade_certificate(Some(new_upgrade_certificate.clone()))
1153 .await;
1154 assert!(res.is_ok());
1155
1156 let res = storage.load_upgrade_certificate().await.unwrap();
1157 let view_number = res.unwrap().view_number;
1158 assert_eq!(view_number, new_view_number_for_certificate);
1159 }
1160
1161 #[rstest_reuse::apply(persistence_types)]
1164 pub async fn test_arc_storage_append_high_qc2_persists<P: TestablePersistence>(
1165 _p: PhantomData<P>,
1166 ) {
1167 use hotshot_new_protocol::storage::NewProtocolStorage;
1168
1169 let tmp = P::tmp_storage().await;
1170 let storage = P::connect(&tmp).await;
1171 let arc = Arc::new(storage.clone());
1173
1174 assert_eq!(storage.load_high_qc2().await.unwrap(), None);
1175
1176 let mut high_qc = QuorumCertificate2::genesis(
1177 &ValidatedState::default(),
1178 &NodeState::mock(),
1179 TEST_VERSIONS.test,
1180 )
1181 .await;
1182 high_qc.view_number = ViewNumber::new(7);
1183 NewProtocolStorage::append_high_qc2(&arc, high_qc.clone())
1184 .await
1185 .unwrap();
1186 assert_eq!(
1187 storage.load_high_qc2().await.unwrap().unwrap().view_number,
1188 ViewNumber::new(7)
1189 );
1190
1191 let mut stale = high_qc.clone();
1193 stale.view_number = ViewNumber::new(3);
1194 NewProtocolStorage::append_high_qc2(&arc, stale)
1195 .await
1196 .unwrap();
1197 assert_eq!(
1198 storage.load_high_qc2().await.unwrap().unwrap().view_number,
1199 ViewNumber::new(7)
1200 );
1201 }
1202
1203 #[rstest_reuse::apply(persistence_types)]
1208 pub async fn test_load_consensus_state_recovers_high_qc<P: TestablePersistence>(
1209 _p: PhantomData<P>,
1210 ) {
1211 let tmp = P::tmp_storage().await;
1212 let storage = P::connect(&tmp).await;
1213
1214 let mut high_qc = QuorumCertificate2::genesis(
1215 &ValidatedState::default(),
1216 &NodeState::mock(),
1217 TEST_VERSIONS.test,
1218 )
1219 .await;
1220 high_qc.view_number = ViewNumber::new(9);
1221 storage.append_high_qc2(high_qc).await.unwrap();
1222
1223 let (initializer, _anchor_view) = storage
1224 .load_consensus_state(
1225 NodeState::mock(),
1226 Upgrade::trivial(versions::EPOCH_REWARD_VERSION),
1227 )
1228 .await
1229 .unwrap();
1230
1231 assert_eq!(initializer.high_qc().view_number, ViewNumber::new(9));
1233 }
1234
1235 #[rstest_reuse::apply(persistence_types)]
1238 pub async fn test_load_consensus_state_prefers_running_high_qc_over_eqc<
1239 P: TestablePersistence,
1240 >(
1241 _p: PhantomData<P>,
1242 ) {
1243 let tmp = P::tmp_storage().await;
1244 let storage = P::connect(&tmp).await;
1245
1246 let upgrade_lock = UpgradeLock::<SeqTypes>::new(TEST_VERSIONS.test);
1248 let leaf = Leaf2::genesis(
1249 &ValidatedState::default(),
1250 &NodeState::default(),
1251 TEST_VERSIONS.test.base,
1252 )
1253 .await;
1254 let data: NextEpochQuorumData2<SeqTypes> = QuorumData2 {
1255 leaf_commit: leaf.commit(),
1256 epoch: Some(EpochNumber::new(1)),
1257 block_number: Some(leaf.height()),
1258 }
1259 .into();
1260 let versioned_data =
1261 VersionedVoteData::new_infallible(data.clone(), ViewNumber::new(5), &upgrade_lock);
1262 let bytes: [u8; 32] = versioned_data.commit().into();
1263 let next_epoch_qc = NextEpochQuorumCertificate2::new(
1264 data,
1265 Commitment::from_raw(bytes),
1266 ViewNumber::new(5),
1267 None,
1268 PhantomData,
1269 );
1270
1271 let mut eqc_high_qc = QuorumCertificate2::genesis(
1273 &ValidatedState::default(),
1274 &NodeState::mock(),
1275 TEST_VERSIONS.test,
1276 )
1277 .await;
1278 eqc_high_qc.view_number = ViewNumber::new(5);
1279 storage
1280 .store_eqc(eqc_high_qc.clone(), next_epoch_qc.clone())
1281 .await
1282 .unwrap();
1283
1284 let epoch_height = 10;
1286 let mut running_high_qc = eqc_high_qc.clone();
1287 running_high_qc.view_number = ViewNumber::new(9);
1288 running_high_qc.data.block_number = Some(epoch_height);
1289 storage
1290 .append_high_qc2(running_high_qc.clone())
1291 .await
1292 .unwrap();
1293
1294 let (initializer, _anchor_view) = storage
1295 .load_consensus_state(
1296 NodeState::mock().with_epoch_height(epoch_height),
1297 Upgrade::trivial(versions::EPOCH_REWARD_VERSION),
1298 )
1299 .await
1300 .unwrap();
1301
1302 assert_eq!(initializer.high_qc().view_number, ViewNumber::new(9));
1303 }
1304
1305 #[rstest_reuse::apply(persistence_types)]
1306 pub async fn test_decide_with_failing_event_consumer<P: TestablePersistence>(
1307 _p: PhantomData<P>,
1308 ) {
1309 let tmp = P::tmp_storage().await;
1310 let storage = P::connect(&tmp).await;
1311
1312 let mut chain = vec![];
1314
1315 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
1316 let leaf: Leaf2 =
1317 Leaf2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE.base).await;
1318 let leaf_payload = leaf.block_payload().unwrap();
1319 let leaf_payload_bytes_arc = leaf_payload.encode();
1320 let avidm_param = init_avidm_gf2_param(2).unwrap();
1321 let weights = vec![1u32; 2];
1322 let ns_table = parse_ns_table(
1323 leaf_payload.byte_len().as_usize(),
1324 &leaf_payload.ns_table().encode(),
1325 );
1326 let (payload_commitment, common, shares) =
1327 AvidmGf2Scheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
1328 .unwrap();
1329
1330 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
1331 let mut vid = AvidmGf2DisperseShare::<SeqTypes> {
1332 view_number: ViewNumber::new(0),
1333 payload_commitment,
1334 share: shares[0].clone(),
1335 recipient_key: pubkey,
1336 epoch: Some(EpochNumber::new(0)),
1337 target_epoch: Some(EpochNumber::new(0)),
1338 common,
1339 }
1340 .to_proposal(&privkey)
1341 .unwrap()
1342 .clone();
1343 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
1344 proposal: QuorumProposal2::<SeqTypes> {
1345 block_header: leaf.block_header().clone(),
1346 view_number: ViewNumber::genesis(),
1347 justify_qc: QuorumCertificate2::genesis(
1348 &ValidatedState::default(),
1349 &node_state,
1350 TEST_UPGRADE,
1351 )
1352 .await,
1353 upgrade_certificate: None,
1354 view_change_evidence: None,
1355 next_drb_result: None,
1356 next_epoch_justify_qc: None,
1357 epoch: None,
1358 state_cert: None,
1359 },
1360 };
1361 let mut qc =
1362 QuorumCertificate2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE)
1363 .await;
1364
1365 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
1366 .expect("Failed to sign block payload");
1367 let mut da_proposal = Proposal {
1368 data: DaProposal2::<SeqTypes> {
1369 encoded_transactions: leaf_payload_bytes_arc.clone(),
1370 metadata: leaf_payload.ns_table().clone(),
1371 view_number: ViewNumber::new(0),
1372 epoch: Some(EpochNumber::new(0)),
1373 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
1374 },
1375 signature: block_payload_signature,
1376 _pd: Default::default(),
1377 };
1378
1379 let vid_commitment = vid_commitment(
1380 &leaf_payload_bytes_arc,
1381 &leaf.block_header().metadata().encode(),
1382 2,
1383 TEST_UPGRADE.base,
1384 );
1385
1386 for i in 0..4 {
1387 quorum_proposal.proposal.view_number = ViewNumber::new(i);
1388 let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
1389 qc.view_number = leaf.view_number();
1390 qc.data.leaf_commit = Committable::commit(&leaf);
1391 vid.data.view_number = leaf.view_number();
1392 da_proposal.data.view_number = leaf.view_number();
1393 chain.push((leaf.clone(), qc.clone(), vid.clone(), da_proposal.clone()));
1394 }
1395
1396 for (_, _, vid, da) in &chain {
1398 tracing::info!(?da, ?vid, "insert proposal");
1399 storage.append_da2(da, vid_commitment).await.unwrap();
1400 storage
1401 .append_vid(&convert_proposal(vid.clone()))
1402 .await
1403 .unwrap();
1404 }
1405
1406 let leaf_chain = chain
1408 .iter()
1409 .take(2)
1410 .map(|(leaf, qc, ..)| (leaf_info(leaf.clone()), qc.clone()))
1411 .collect::<Vec<_>>();
1412 tracing::info!("decide with event handling failure");
1413 storage
1414 .append_decided_leaves(
1415 ViewNumber::new(1),
1416 leaf_chain
1417 .iter()
1418 .map(|(leaf, qc)| (leaf, CertificatePair::non_epoch_change(qc.clone()))),
1419 &FailConsumer,
1420 )
1421 .await
1422 .unwrap();
1423 for i in 0..4 {
1425 tracing::info!(i, "check proposal availability");
1426 assert!(
1427 storage
1428 .load_vid_share(ViewNumber::new(i))
1429 .await
1430 .unwrap()
1431 .is_some()
1432 );
1433 assert!(
1434 storage
1435 .load_da_proposal(ViewNumber::new(i))
1436 .await
1437 .unwrap()
1438 .is_some()
1439 );
1440 }
1441 tracing::info!("check anchor leaf updated");
1442 assert_eq!(
1443 storage
1444 .load_anchor_leaf()
1445 .await
1446 .unwrap()
1447 .unwrap()
1448 .0
1449 .view_number(),
1450 ViewNumber::new(1)
1451 );
1452 assert_eq!(
1453 storage.load_anchor_view().await.unwrap(),
1454 ViewNumber::new(1)
1455 );
1456
1457 let consumer = EventCollector::default();
1460 let leaf_chain = chain
1461 .iter()
1462 .skip(2)
1463 .map(|(leaf, qc, ..)| (leaf_info(leaf.clone()), qc.clone()))
1464 .collect::<Vec<_>>();
1465 tracing::info!("decide successfully");
1466 storage
1467 .append_decided_leaves(
1468 ViewNumber::new(3),
1469 leaf_chain
1470 .iter()
1471 .map(|(leaf, qc)| (leaf, CertificatePair::non_epoch_change(qc.clone()))),
1472 &consumer,
1473 )
1474 .await
1475 .unwrap();
1476 for i in 0..4 {
1478 tracing::info!(i, "check proposal garbage collected");
1479 assert!(
1480 storage
1481 .load_vid_share(ViewNumber::new(i))
1482 .await
1483 .unwrap()
1484 .is_none()
1485 );
1486 assert!(
1487 storage
1488 .load_da_proposal(ViewNumber::new(i))
1489 .await
1490 .unwrap()
1491 .is_none()
1492 );
1493 }
1494 tracing::info!("check anchor leaf updated");
1495 assert_eq!(
1496 storage
1497 .load_anchor_leaf()
1498 .await
1499 .unwrap()
1500 .unwrap()
1501 .0
1502 .view_number(),
1503 ViewNumber::new(3)
1504 );
1505 assert_eq!(
1506 storage.load_anchor_view().await.unwrap(),
1507 ViewNumber::new(3)
1508 );
1509
1510 tracing::info!("check decide event");
1512 let leaf_chain = consumer.leaf_chain().await;
1513 assert_eq!(leaf_chain.len(), 4, "{leaf_chain:#?}");
1514 for ((leaf, ..), info) in chain.iter().zip(leaf_chain.iter()) {
1515 assert_eq!(info.leaf, *leaf);
1516 let decided_vid_share = info.vid_share.as_ref().unwrap();
1517 assert_eq!(decided_vid_share.view_number(), leaf.view_number());
1518 assert!(info.leaf.block_payload().is_some());
1519 }
1520 }
1521
1522 #[rstest_reuse::apply(persistence_types)]
1526 pub async fn test_deferred_decide_processing<P: TestablePersistence>(_p: PhantomData<P>) {
1527 let tmp = P::tmp_storage().await;
1528 let storage = P::connect(&tmp).await;
1529
1530 let mut chain = vec![];
1532
1533 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
1534 let leaf: Leaf2 =
1535 Leaf2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE.base).await;
1536 let leaf_payload = leaf.block_payload().unwrap();
1537 let leaf_payload_bytes_arc = leaf_payload.encode();
1538 let avidm_param = init_avidm_gf2_param(2).unwrap();
1539 let weights = vec![1u32; 2];
1540 let ns_table = parse_ns_table(
1541 leaf_payload.byte_len().as_usize(),
1542 &leaf_payload.ns_table().encode(),
1543 );
1544 let (payload_commitment, common, shares) =
1545 AvidmGf2Scheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
1546 .unwrap();
1547
1548 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
1549 let mut vid = AvidmGf2DisperseShare::<SeqTypes> {
1550 view_number: ViewNumber::new(0),
1551 payload_commitment,
1552 share: shares[0].clone(),
1553 recipient_key: pubkey,
1554 epoch: Some(EpochNumber::new(0)),
1555 target_epoch: Some(EpochNumber::new(0)),
1556 common,
1557 }
1558 .to_proposal(&privkey)
1559 .unwrap()
1560 .clone();
1561 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
1562 proposal: QuorumProposal2::<SeqTypes> {
1563 block_header: leaf.block_header().clone(),
1564 view_number: ViewNumber::genesis(),
1565 justify_qc: QuorumCertificate2::genesis(
1566 &ValidatedState::default(),
1567 &node_state,
1568 TEST_UPGRADE,
1569 )
1570 .await,
1571 upgrade_certificate: None,
1572 view_change_evidence: None,
1573 next_drb_result: None,
1574 next_epoch_justify_qc: None,
1575 epoch: None,
1576 state_cert: None,
1577 },
1578 };
1579 let mut qc =
1580 QuorumCertificate2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE)
1581 .await;
1582
1583 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
1584 .expect("Failed to sign block payload");
1585 let mut da_proposal = Proposal {
1586 data: DaProposal2::<SeqTypes> {
1587 encoded_transactions: leaf_payload_bytes_arc.clone(),
1588 metadata: leaf_payload.ns_table().clone(),
1589 view_number: ViewNumber::new(0),
1590 epoch: Some(EpochNumber::new(0)),
1591 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
1592 },
1593 signature: block_payload_signature,
1594 _pd: Default::default(),
1595 };
1596
1597 let vid_commitment = vid_commitment(
1598 &leaf_payload_bytes_arc,
1599 &leaf.block_header().metadata().encode(),
1600 2,
1601 TEST_UPGRADE.base,
1602 );
1603
1604 for i in 0..4 {
1605 quorum_proposal.proposal.view_number = ViewNumber::new(i);
1606 let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
1607 qc.view_number = leaf.view_number();
1608 qc.data.leaf_commit = Committable::commit(&leaf);
1609 vid.data.view_number = leaf.view_number();
1610 da_proposal.data.view_number = leaf.view_number();
1611 chain.push((leaf.clone(), qc.clone(), vid.clone(), da_proposal.clone()));
1612 }
1613
1614 for (_, _, vid, da) in &chain {
1616 storage.append_da2(da, vid_commitment).await.unwrap();
1617 storage
1618 .append_vid(&convert_proposal(vid.clone()))
1619 .await
1620 .unwrap();
1621 }
1622
1623 let consumer = EventCollector::default();
1624
1625 for (from, to) in [(0usize, 2usize), (2, 4)] {
1627 let leaf_chain = chain
1628 .iter()
1629 .take(to)
1630 .skip(from)
1631 .map(|(leaf, qc, ..)| (leaf_info(leaf.clone()), qc.clone()))
1632 .collect::<Vec<_>>();
1633 storage
1634 .persist_decided_leaves(
1635 leaf_chain
1636 .iter()
1637 .map(|(leaf, qc)| (leaf, CertificatePair::non_epoch_change(qc.clone()))),
1638 None,
1639 &consumer,
1640 )
1641 .await
1642 .unwrap();
1643 }
1644
1645 assert_eq!(
1647 storage.load_anchor_view().await.unwrap(),
1648 ViewNumber::new(3),
1649 "anchor view should reflect the latest persisted leaf"
1650 );
1651 assert!(
1652 consumer.leaf_chain().await.is_empty(),
1653 "persist_decided_leaves must not emit decide events"
1654 );
1655 for i in 0..4 {
1656 assert!(
1657 storage
1658 .load_vid_share(ViewNumber::new(i))
1659 .await
1660 .unwrap()
1661 .is_some(),
1662 "persist_decided_leaves must not garbage collect VID shares"
1663 );
1664 assert!(
1665 storage
1666 .load_da_proposal(ViewNumber::new(i))
1667 .await
1668 .unwrap()
1669 .is_some(),
1670 "persist_decided_leaves must not garbage collect DA proposals"
1671 );
1672 }
1673
1674 storage
1677 .process_decided_events(ViewNumber::new(3), &FailConsumer)
1678 .await
1679 .unwrap_err();
1680 for i in 0..4 {
1681 assert!(
1682 storage
1683 .load_da_proposal(ViewNumber::new(i))
1684 .await
1685 .unwrap()
1686 .is_some(),
1687 "a failed process pass must not garbage collect anything"
1688 );
1689 }
1690
1691 let processed = storage
1694 .process_decided_events(ViewNumber::new(3), &consumer)
1695 .await
1696 .unwrap();
1697 assert_eq!(
1698 processed,
1699 Some(ViewNumber::new(3)),
1700 "process_decided_events should report the highest processed view"
1701 );
1702
1703 let leaf_chain = consumer.leaf_chain().await;
1705 assert_eq!(leaf_chain.len(), 4, "{leaf_chain:#?}");
1706 for ((leaf, ..), info) in chain.iter().zip(leaf_chain.iter()) {
1707 assert_eq!(info.leaf, *leaf);
1708 let decided_vid_share = info.vid_share.as_ref().unwrap();
1709 assert_eq!(decided_vid_share.view_number(), leaf.view_number());
1710 assert!(info.leaf.block_payload().is_some());
1711 }
1712
1713 for i in 0..4 {
1715 assert!(
1716 storage
1717 .load_vid_share(ViewNumber::new(i))
1718 .await
1719 .unwrap()
1720 .is_none(),
1721 "process_decided_events should have garbage collected VID shares"
1722 );
1723 assert!(
1724 storage
1725 .load_da_proposal(ViewNumber::new(i))
1726 .await
1727 .unwrap()
1728 .is_none(),
1729 "process_decided_events should have garbage collected DA proposals"
1730 );
1731 }
1732
1733 let consumer2 = EventCollector::default();
1735 storage
1736 .process_decided_events(ViewNumber::new(3), &consumer2)
1737 .await
1738 .unwrap();
1739 assert!(
1740 consumer2.leaf_chain().await.is_empty(),
1741 "re-processing an already-drained backlog must emit no events"
1742 );
1743 assert_eq!(
1744 storage.load_anchor_view().await.unwrap(),
1745 ViewNumber::new(3)
1746 );
1747 }
1748
1749 async fn consecutive_height_chain(n: u64) -> Vec<(Leaf2, QuorumCertificate2<SeqTypes>)> {
1752 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
1753 let genesis_leaf = Leaf2::genesis(
1754 &ValidatedState::default(),
1755 &node_state,
1756 TEST_VERSIONS.test.base,
1757 )
1758 .await;
1759 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
1760 proposal: QuorumProposal2::<SeqTypes> {
1761 block_header: genesis_leaf.block_header().clone(),
1762 view_number: ViewNumber::genesis(),
1763 justify_qc: QuorumCertificate2::genesis(
1764 &ValidatedState::default(),
1765 &node_state,
1766 TEST_VERSIONS.test,
1767 )
1768 .await,
1769 upgrade_certificate: None,
1770 view_change_evidence: None,
1771 next_drb_result: None,
1772 next_epoch_justify_qc: None,
1773 epoch: None,
1774 state_cert: None,
1775 },
1776 };
1777 let mut qc = QuorumCertificate2::genesis(
1778 &ValidatedState::default(),
1779 &node_state,
1780 TEST_VERSIONS.test,
1781 )
1782 .await;
1783
1784 let mut chain = vec![];
1785 for i in 0..n {
1786 quorum_proposal.proposal.view_number = ViewNumber::new(i);
1787 *quorum_proposal.proposal.block_header.height_mut() = i;
1788 let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
1789 qc.view_number = leaf.view_number();
1790 qc.data.leaf_commit = Committable::commit(&leaf);
1791 chain.push((leaf, qc.clone()));
1792 }
1793 chain
1794 }
1795
1796 async fn decide_range<P: TestablePersistence>(
1798 storage: &P,
1799 chain: &[(Leaf2, QuorumCertificate2<SeqTypes>)],
1800 range: std::ops::Range<usize>,
1801 consumer: &(impl EventConsumer + 'static),
1802 ) {
1803 let decided_view = ViewNumber::new(range.end as u64 - 1);
1804 let leaf_chain = chain[range]
1805 .iter()
1806 .map(|(leaf, qc)| (leaf_info(leaf.clone()), qc.clone()))
1807 .collect::<Vec<_>>();
1808 storage
1809 .append_decided_leaves(
1810 decided_view,
1811 leaf_chain
1812 .iter()
1813 .map(|(leaf, qc)| (leaf, CertificatePair::non_epoch_change(qc.clone()))),
1814 consumer,
1815 )
1816 .await
1817 .unwrap();
1818 }
1819
1820 #[derive(Clone, Debug, Default)]
1822 struct DecideViewCollector {
1823 views: Arc<RwLock<Vec<u64>>>,
1824 }
1825
1826 #[async_trait]
1827 impl EventConsumer for DecideViewCollector {
1828 async fn handle_event(&self, event: &CoordinatorEvent<SeqTypes>) -> anyhow::Result<()> {
1829 if let CoordinatorEvent::NewDecide { leaf_infos, .. } = event {
1831 self.views.write().await.extend(
1832 leaf_infos
1833 .iter()
1834 .rev()
1835 .map(|info| info.leaf.view_number().u64()),
1836 );
1837 }
1838 Ok(())
1839 }
1840 }
1841
1842 async fn received_views(consumer: &DecideViewCollector) -> Vec<u64> {
1843 consumer.views.read().await.clone()
1844 }
1845
1846 #[rstest::rstest]
1852 #[case::postgres(PhantomData::<crate::persistence::sql::postgres::Persistence>)]
1853 #[case::sqlite(PhantomData::<crate::persistence::sql::sqlite::Persistence>)]
1854 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1855 pub async fn test_decide_gap_holds_events<P: TestablePersistence>(#[case] _p: PhantomData<P>) {
1856 let tmp = P::tmp_storage().await;
1857 let storage = P::connect(&tmp).await;
1858 let consumer = DecideViewCollector::default();
1859 let chain = consecutive_height_chain(5).await;
1860
1861 decide_range(&storage, &chain, 0..2, &consumer).await;
1862 assert_eq!(received_views(&consumer).await, vec![0, 1]);
1863
1864 decide_range(&storage, &chain, 3..5, &consumer).await;
1866 assert_eq!(
1867 received_views(&consumer).await,
1868 vec![0, 1],
1869 "leaves past a height gap must not be delivered before the gap is filled"
1870 );
1871
1872 assert_eq!(
1874 storage
1875 .load_anchor_leaf()
1876 .await
1877 .unwrap()
1878 .unwrap()
1879 .0
1880 .view_number(),
1881 ViewNumber::new(4)
1882 );
1883
1884 storage
1886 .process_decided_events(ViewNumber::new(4), &consumer)
1887 .await
1888 .unwrap();
1889 assert_eq!(received_views(&consumer).await, vec![0, 1]);
1890 }
1891
1892 #[rstest::rstest]
1897 #[case::postgres(PhantomData::<crate::persistence::sql::postgres::Persistence>)]
1898 #[case::sqlite(PhantomData::<crate::persistence::sql::sqlite::Persistence>)]
1899 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1900 pub async fn test_decide_gap_fill_delivers_all<P: TestablePersistence>(
1901 #[case] _p: PhantomData<P>,
1902 ) {
1903 let tmp = P::tmp_storage().await;
1904 let storage = P::connect(&tmp).await;
1905 let consumer = DecideViewCollector::default();
1906 let chain = consecutive_height_chain(5).await;
1907
1908 decide_range(&storage, &chain, 0..2, &consumer).await;
1909 decide_range(&storage, &chain, 3..5, &consumer).await;
1910 assert_eq!(received_views(&consumer).await, vec![0, 1]);
1911
1912 decide_range(&storage, &chain, 2..3, &consumer).await;
1914 storage
1916 .process_decided_events(ViewNumber::new(4), &consumer)
1917 .await
1918 .unwrap();
1919
1920 assert_eq!(
1921 received_views(&consumer).await,
1922 vec![0, 1, 2, 3, 4],
1923 "after the gap fills, every leaf is delivered in order, exactly once"
1924 );
1925 }
1926
1927 type DecidedCert2s = BTreeMap<ViewNumber, Option<Certificate2<SeqTypes>>>;
1928
1929 #[derive(Clone, Debug, Default)]
1931 struct Cert2Collector {
1932 cert2s: Arc<RwLock<DecidedCert2s>>,
1933 }
1934
1935 #[async_trait]
1936 impl EventConsumer for Cert2Collector {
1937 async fn handle_event(&self, event: &CoordinatorEvent<SeqTypes>) -> anyhow::Result<()> {
1938 if let CoordinatorEvent::NewDecide {
1939 leaf_infos, cert2, ..
1940 } = event
1941 {
1942 self.cert2s
1943 .write()
1944 .await
1945 .insert(leaf_infos[0].leaf.view_number(), cert2.clone());
1946 }
1947 Ok(())
1948 }
1949 }
1950
1951 #[rstest_reuse::apply(persistence_types)]
1954 pub async fn test_decide_event_carries_cert2_persisted_with_leaves<P: TestablePersistence>(
1955 _p: PhantomData<P>,
1956 ) {
1957 let tmp = P::tmp_storage().await;
1958 let storage = P::connect(&tmp).await;
1959 let chain = consecutive_height_chain(2).await;
1960 let (newest, _) = &chain[1];
1961
1962 let vote_data = Vote2Data {
1963 leaf_commit: Committable::commit(newest),
1964 epoch: EpochNumber::new(0),
1965 block_number: newest.height(),
1966 };
1967 let cert2 = Certificate2::new(
1968 vote_data.clone(),
1969 vote_data.commit(),
1970 newest.view_number(),
1971 None,
1972 PhantomData,
1973 );
1974
1975 let decide = CoordinatorEvent::NewDecide {
1976 leaf_infos: chain
1977 .iter()
1978 .rev()
1979 .map(|(leaf, _)| leaf_info(leaf.clone()))
1980 .collect(),
1981 cert1: chain[1].1.clone(),
1982 cert2: Some(cert2.clone()),
1983 };
1984 storage
1985 .persist_event(&decide, &NullEventConsumer)
1986 .await
1987 .expect("a new-protocol decide is persisted");
1988
1989 let consumer = Cert2Collector::default();
1990 storage
1991 .process_decided_events(newest.view_number(), &consumer)
1992 .await
1993 .unwrap();
1994
1995 assert_eq!(
1996 consumer.cert2s.read().await.get(&newest.view_number()),
1997 Some(&Some(cert2))
1998 );
1999 }
2000
2001 #[rstest_reuse::apply(persistence_types)]
2002 pub async fn test_pruning<P: TestablePersistence>(_p: PhantomData<P>) {
2003 let tmp = P::tmp_storage().await;
2004
2005 let mut options = P::options(&tmp);
2006 options.set_view_retention(1);
2007 let storage = options.create().await.unwrap();
2008
2009 let leaf = Leaf::genesis(
2011 &ValidatedState::default(),
2012 &NodeState::mock(),
2013 MOCK_UPGRADE.base,
2014 )
2015 .await;
2016 let leaf_payload = leaf.block_payload().unwrap();
2017 let leaf_payload_bytes_arc = leaf_payload.encode();
2018 let avidm_param = init_avidm_param(2).unwrap();
2019 let weights = vec![1u32; 2];
2020
2021 let ns_table = parse_ns_table(
2022 leaf_payload.byte_len().as_usize(),
2023 &leaf_payload.ns_table().encode(),
2024 );
2025 let (payload_commitment, shares) =
2026 AvidMScheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
2027 .unwrap();
2028
2029 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
2030 let vid_share = convert_proposal(
2031 AvidMDisperseShare::<SeqTypes> {
2032 view_number: ViewNumber::new(0),
2033 payload_commitment,
2034 share: shares[0].clone(),
2035 recipient_key: pubkey,
2036 epoch: None,
2037 target_epoch: None,
2038 common: avidm_param,
2039 }
2040 .to_proposal(&privkey)
2041 .unwrap()
2042 .clone(),
2043 );
2044
2045 let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
2046 proposal: QuorumProposal2::<SeqTypes> {
2047 block_header: leaf.block_header().clone(),
2048 view_number: ViewNumber::genesis(),
2049 justify_qc: QuorumCertificate::genesis(
2050 &ValidatedState::default(),
2051 &NodeState::mock(),
2052 TEST_VERSIONS.test,
2053 )
2054 .await
2055 .to_qc2(),
2056 upgrade_certificate: None,
2057 view_change_evidence: None,
2058 next_drb_result: None,
2059 next_epoch_justify_qc: None,
2060 epoch: None,
2061 state_cert: None,
2062 },
2063 };
2064 let quorum_proposal_signature =
2065 BLSPubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
2066 .expect("Failed to sign quorum proposal");
2067 let quorum_proposal = Proposal {
2068 data: quorum_proposal,
2069 signature: quorum_proposal_signature,
2070 _pd: Default::default(),
2071 };
2072
2073 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
2074 .expect("Failed to sign block payload");
2075 let da_proposal = Proposal {
2076 data: DaProposal2::<SeqTypes> {
2077 encoded_transactions: leaf_payload_bytes_arc,
2078 metadata: leaf_payload.ns_table().clone(),
2079 view_number: ViewNumber::new(0),
2080 epoch: None,
2081 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
2082 },
2083 signature: block_payload_signature,
2084 _pd: Default::default(),
2085 };
2086
2087 let leaf2: Leaf2 = leaf.clone().into();
2088 let vote_data = Vote2Data {
2089 leaf_commit: leaf2.commit(),
2090 epoch: EpochNumber::new(0),
2091 block_number: leaf2.height(),
2092 };
2093 let cert2 = Certificate2::new(
2094 vote_data.clone(),
2095 vote_data.commit(),
2096 ViewNumber::new(0),
2097 None,
2098 PhantomData,
2099 );
2100
2101 storage
2102 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
2103 .await
2104 .unwrap();
2105 storage.append_vid(&vid_share).await.unwrap();
2106 storage
2107 .append_quorum_proposal2(&quorum_proposal)
2108 .await
2109 .unwrap();
2110 storage
2111 .append_cert2(ViewNumber::new(0), cert2)
2112 .await
2113 .unwrap();
2114
2115 storage
2117 .append_decided_leaves(ViewNumber::new(1), [], &NullEventConsumer)
2118 .await
2119 .unwrap();
2120
2121 assert_eq!(
2124 storage
2125 .load_da_proposal(ViewNumber::new(0))
2126 .await
2127 .unwrap()
2128 .unwrap(),
2129 da_proposal
2130 );
2131 assert_eq!(
2132 storage
2133 .load_vid_share(ViewNumber::new(0))
2134 .await
2135 .unwrap()
2136 .unwrap(),
2137 vid_share
2138 );
2139 assert_eq!(
2140 storage
2141 .load_quorum_proposal(ViewNumber::new(0))
2142 .await
2143 .unwrap(),
2144 quorum_proposal
2145 );
2146 assert!(
2147 storage
2148 .load_cert2(ViewNumber::new(0))
2149 .await
2150 .unwrap()
2151 .is_some()
2152 );
2153
2154 storage
2156 .append_decided_leaves(ViewNumber::new(2), [], &NullEventConsumer)
2157 .await
2158 .unwrap();
2159 assert!(
2160 storage
2161 .load_da_proposal(ViewNumber::new(0))
2162 .await
2163 .unwrap()
2164 .is_none()
2165 );
2166 assert!(
2167 storage
2168 .load_vid_share(ViewNumber::new(0))
2169 .await
2170 .unwrap()
2171 .is_none()
2172 );
2173 assert!(
2174 storage
2175 .load_quorum_proposal(ViewNumber::new(0))
2176 .await
2177 .is_err()
2178 );
2179 assert!(
2180 storage
2181 .load_cert2(ViewNumber::new(0))
2182 .await
2183 .unwrap()
2184 .is_none()
2185 );
2186 }
2187
2188 async fn assert_events_eq<P: TestablePersistence>(
2189 persistence: &P,
2190 block: u64,
2191 stake_table_fetcher: &Fetcher,
2192 l1_client: &L1Client,
2193 stake_table_contract: Address,
2194 ) -> anyhow::Result<()> {
2195 let (stored_l1, events) = persistence.load_events(0, block).await?;
2197 assert!(!events.is_empty());
2198 assert!(stored_l1.is_some());
2199 assert!(events.iter().all(|((l1_block, _), _)| *l1_block <= block));
2200 let contract_events = Fetcher::fetch_events_from_contract(
2202 l1_client.clone(),
2203 stake_table_contract,
2204 None,
2205 block,
2206 )
2207 .await?;
2208 assert_eq!(
2209 contract_events, events,
2210 "Events from contract and persistence do not match"
2211 );
2212
2213 let fetched_events = stake_table_fetcher
2215 .fetch_and_store_stake_table_events(stake_table_contract, block)
2216 .await?;
2217 assert_eq!(fetched_events, events);
2218
2219 Ok(())
2220 }
2221
2222 #[rstest_reuse::apply(persistence_types)]
2225 pub async fn test_stake_table_fetching_from_persistence<P: TestablePersistence>(
2226 _p: PhantomData<P>,
2227 ) -> anyhow::Result<()> {
2228 let epoch_height = 20;
2229
2230 let network_config = TestConfigBuilder::default()
2231 .epoch_height(epoch_height)
2232 .build();
2233
2234 let anvil_provider = network_config.anvil().unwrap();
2235
2236 let query_service_port =
2237 reserve_tcp_port().expect("OS should have ephemeral ports available");
2238 let query_api_options = Options::with_port(query_service_port);
2239
2240 const NODE_COUNT: usize = 2;
2241
2242 let storage = join_all((0..NODE_COUNT).map(|_| P::tmp_storage())).await;
2243 let persistence_options: [_; NODE_COUNT] = storage
2244 .iter()
2245 .map(P::options)
2246 .collect::<Vec<_>>()
2247 .try_into()
2248 .unwrap();
2249
2250 let persistence = persistence_options[0].clone().create().await.unwrap();
2251
2252 let l1_url = network_config.l1_url();
2253
2254 let testnet_config = TestNetworkConfigBuilder::with_num_nodes()
2255 .api_config(query_api_options)
2256 .network_config(network_config.clone())
2257 .persistences(persistence_options.clone())
2258 .build();
2259
2260 let test_network = TestNetwork::new(testnet_config, TEST_UPGRADE).await;
2262
2263 let client: Client<ClientErr, SequencerApiVersion> = Client::new(
2264 format!("http://localhost:{query_service_port}")
2265 .parse()
2266 .unwrap(),
2267 );
2268 client.connect(None).await;
2269 tracing::info!(query_service_port, "server running");
2270
2271 let _initial_blocks = client
2273 .socket("availability/stream/blocks/0")
2274 .subscribe::<BlockQueryData<SeqTypes>>()
2275 .await
2276 .unwrap()
2277 .take(40)
2278 .try_collect::<Vec<_>>()
2279 .await
2280 .unwrap();
2281 let membership_coordinator = test_network.server.node_state().coordinator;
2283
2284 let l1_client = L1Client::new(vec![l1_url]).unwrap();
2285 let node_state = test_network.server.node_state();
2286 let chain_config = node_state.chain_config;
2287 let stake_table_contract = chain_config.stake_table_contract.unwrap();
2288
2289 let current_membership = membership_coordinator.membership();
2290 {
2291 let membership_state = current_membership;
2292 let stake_table_fetcher = membership_state.fetcher();
2293
2294 let block1 = anvil_provider
2295 .get_block_number()
2296 .await
2297 .expect("latest l1 block");
2298
2299 assert_events_eq(
2300 &persistence,
2301 block1,
2302 stake_table_fetcher,
2303 &l1_client,
2304 stake_table_contract,
2305 )
2306 .await?;
2307 }
2308 let _epoch_4_blocks = client
2309 .socket("availability/stream/blocks/0")
2310 .subscribe::<BlockQueryData<SeqTypes>>()
2311 .await
2312 .unwrap()
2313 .take(65)
2314 .try_collect::<Vec<_>>()
2315 .await
2316 .unwrap();
2317 let block2 = anvil_provider
2318 .get_block_number()
2319 .await
2320 .expect("latest l1 block");
2321
2322 {
2323 let membership_state = current_membership;
2324 let stake_table_fetcher = membership_state.fetcher();
2325
2326 assert_events_eq(
2327 &persistence,
2328 block2,
2329 stake_table_fetcher,
2330 &l1_client,
2331 stake_table_contract,
2332 )
2333 .await?;
2334 }
2335 Ok(())
2336 }
2337
2338 #[rstest::rstest]
2341 #[case::postgres(PhantomData::<crate::api::sql::postgres::DataSource>)]
2342 #[case::sqlite(PhantomData::<crate::api::sql::sqlite::DataSource>)]
2343 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2344 async fn test_legacy_stake_table_events<D>(#[case] _d: PhantomData<D>) -> anyhow::Result<()>
2345 where
2346 D: TestableSqlDataSource,
2347 <D::Options as PersistenceOptions>::Persistence: TestablePersistence,
2348 {
2349 let chain = LegacyChain::load("v4")?;
2350 let replay = chain.replay::<D>().await?;
2351 let node_state = replay.node.node_state();
2352 let contract = node_state.chain_config.stake_table_contract.unwrap();
2353 let l1_client = L1Client::new(vec![replay.l1.endpoint_url()])?;
2354 let block = l1_client.provider.get_block_number().await?;
2355 let membership = node_state.coordinator.membership();
2356
2357 assert_events_eq(
2358 &replay.persistence().await?,
2359 block,
2360 membership.fetcher(),
2361 &l1_client,
2362 contract,
2363 )
2364 .await?;
2365
2366 let (_, events) = replay.persistence().await?.load_events(0, block).await?;
2367 let has = |f: fn(&StakeTableEvent) -> bool| events.iter().any(|(_, event)| f(event));
2368 ensure!(
2369 has(|e| matches!(e, StakeTableEvent::Register(_))),
2370 "V1 registrations"
2371 );
2372 ensure!(
2373 has(|e| matches!(e, StakeTableEvent::CommissionUpdate(_))),
2374 "V2 commissions"
2375 );
2376 ensure!(
2377 has(|e| matches!(e, StakeTableEvent::X25519KeyUpdate(_))),
2378 "V3 network config"
2379 );
2380 Ok(())
2381 }
2382
2383 #[rstest_reuse::apply(persistence_types)]
2384 pub async fn test_stake_table_background_fetching<P: TestablePersistence>(
2385 #[values(
2386 StakeTableContractVersion::V1,
2387 StakeTableContractVersion::V2,
2388 StakeTableContractVersion::V3
2389 )]
2390 stake_table_version: StakeTableContractVersion,
2391 _p: PhantomData<P>,
2392 ) -> anyhow::Result<()> {
2393 use espresso_types::v0_3::ChainConfig;
2394 use hotshot_contract_adapter::stake_table::StakeTableContractVersion;
2395
2396 let blocks_per_epoch = 10;
2397
2398 let network_config = TestConfigBuilder::<1>::default()
2399 .epoch_height(blocks_per_epoch)
2400 .build();
2401
2402 let anvil_provider = network_config.anvil().unwrap();
2403
2404 let (genesis_state, genesis_stake) = light_client_genesis_from_stake_table(
2405 &network_config.hotshot_config().hotshot_stake_table(),
2406 STAKE_TABLE_CAPACITY_FOR_TEST,
2407 )
2408 .unwrap();
2409
2410 let (_, priv_keys): (Vec<_>, Vec<_>) = (0..20)
2411 .map(|i| <PubKey as SignatureKey>::generated_from_seed_indexed([1; 32], i as u64))
2412 .unzip();
2413 let state_key_pairs = (0..20)
2414 .map(|i| StateKeyPair::generate_from_seed_indexed([2; 32], i as u64))
2415 .collect::<Vec<_>>();
2416
2417 let validators = staking_priv_keys(&priv_keys, &state_key_pairs, &[], 20);
2418
2419 let deployer = ProviderBuilder::new()
2420 .wallet(EthereumWallet::from(network_config.signer().clone()))
2421 .connect_http(network_config.l1_url().clone());
2422
2423 let mut contracts = Contracts::new();
2424 let args = DeployerArgsBuilder::default()
2425 .deployer(deployer.clone())
2426 .rpc_url(network_config.l1_url().clone())
2427 .mock_light_client(true)
2428 .genesis_lc_state(genesis_state)
2429 .genesis_st_state(genesis_stake)
2430 .blocks_per_epoch(blocks_per_epoch)
2431 .epoch_start_block(1)
2432 .exit_escrow_period(U256::from(max(
2433 blocks_per_epoch * 15 + 100,
2434 DEFAULT_EXIT_ESCROW_PERIOD_SECONDS,
2435 )))
2436 .multisig_pauser(network_config.signer().address())
2437 .token_name("Espresso".to_string())
2438 .token_symbol("ESP".to_string())
2439 .initial_token_supply(U256::from(3590000000u64))
2440 .ops_timelock_delay(U256::from(0))
2441 .ops_timelock_admin(network_config.signer().address())
2442 .ops_timelock_proposers(vec![network_config.signer().address()])
2443 .ops_timelock_executors(vec![network_config.signer().address()])
2444 .safe_exit_timelock_delay(U256::from(10))
2445 .safe_exit_timelock_admin(network_config.signer().address())
2446 .safe_exit_timelock_proposers(vec![network_config.signer().address()])
2447 .safe_exit_timelock_executors(vec![network_config.signer().address()])
2448 .build()
2449 .unwrap();
2450
2451 match stake_table_version {
2452 StakeTableContractVersion::V1 => args.deploy_to_stake_table_v1(&mut contracts).await,
2453 StakeTableContractVersion::V2 => args.deploy_to_stake_table_v2(&mut contracts).await,
2454 StakeTableContractVersion::V3 => args.deploy_to_stake_table_v3(&mut contracts).await,
2455 }
2456 .expect("contracts deployed");
2457
2458 let st_addr = contracts
2459 .address(Contract::StakeTableProxy)
2460 .expect("StakeTableProxy deployed");
2461 let l1_url = network_config.l1_url().clone();
2462
2463 let mut planned_txns = StakingTransactions::create(
2464 l1_url.clone(),
2465 &deployer,
2466 st_addr,
2467 validators,
2468 None,
2469 DelegationConfig::MultipleDelegators,
2470 )
2471 .await
2472 .expect("stake table setup failed");
2473
2474 planned_txns
2475 .apply_prerequisites()
2476 .await
2477 .expect("prerequisites failed");
2478
2479 planned_txns.apply_one().await.expect("send tx failed");
2481
2482 anvil_provider
2484 .anvil_set_interval_mining(1)
2485 .await
2486 .expect("interval mining");
2487
2488 spawn({
2492 async move {
2493 {
2494 while let Some(receipt) =
2495 planned_txns.apply_one().await.expect("send tx failed")
2496 {
2497 tracing::debug!(?receipt, "transaction finalized");
2498 }
2499 }
2500 }
2501 });
2502
2503 let storage = P::tmp_storage().await;
2504 let persistence = P::options(&storage).create().await.unwrap();
2505
2506 let l1_client = L1ClientOptions {
2507 stake_table_update_interval: Duration::from_secs(7),
2508 l1_retry_delay: Duration::from_millis(10),
2509 l1_events_max_block_range: 10000,
2510 ..Default::default()
2511 }
2512 .connect(vec![l1_url])
2513 .unwrap();
2514 l1_client.spawn_tasks().await;
2515
2516 let fetcher = Fetcher::new(
2517 Arc::new(NullStateCatchup::default()),
2518 Arc::new(Mutex::new(persistence.clone())),
2519 l1_client.clone(),
2520 ChainConfig {
2521 stake_table_contract: Some(st_addr),
2522 base_fee: 0.into(),
2523 ..Default::default()
2524 },
2525 );
2526
2527 sleep(Duration::from_secs(20)).await;
2529
2530 fetcher.spawn_update_loop().await;
2531 let mut prev_l1_block = 0;
2532 let mut prev_events_len = 0;
2533 for _i in 0..10 {
2534 tokio::time::sleep(std::time::Duration::from_secs(8)).await;
2538
2539 let block = anvil_provider
2540 .get_block_number()
2541 .await
2542 .expect("latest l1 block");
2543
2544 let (read_offset, persisted_events) = persistence.load_events(0, block).await?;
2545 let read_offset = read_offset.unwrap();
2546 let l1_block = match read_offset {
2547 EventsPersistenceRead::Complete => block,
2548 EventsPersistenceRead::UntilL1Block(block) => block,
2549 };
2550
2551 tracing::info!("{l1_block:?}, persistence events = {persisted_events:?}.");
2552 assert!(persisted_events.len() > prev_events_len);
2553
2554 assert!(l1_block > prev_l1_block, "events not updated");
2555
2556 let contract_events =
2557 Fetcher::fetch_events_from_contract(l1_client.clone(), st_addr, None, l1_block)
2558 .await?;
2559 assert_eq!(persisted_events, contract_events);
2560
2561 prev_l1_block = l1_block;
2562 prev_events_len = persisted_events.len();
2563 }
2564
2565 Ok(())
2566 }
2567
2568 #[rstest_reuse::apply(persistence_types)]
2569 pub async fn test_prefetch_stake_table_events<P: TestablePersistence>(
2570 _p: PhantomData<P>,
2571 ) -> anyhow::Result<()> {
2572 use espresso_types::v0_3::ChainConfig;
2573
2574 let network_config = TestConfigBuilder::<1>::default().build();
2575
2576 let (_, priv_keys): (Vec<_>, Vec<_>) = (0..1)
2577 .map(|i| <PubKey as SignatureKey>::generated_from_seed_indexed([1; 32], i as u64))
2578 .unzip();
2579 let state_key_pairs = (0..1)
2580 .map(|i| StateKeyPair::generate_from_seed_indexed([2; 32], i as u64))
2581 .collect::<Vec<_>>();
2582 let validators = staking_priv_keys(&priv_keys, &state_key_pairs, &[], 1);
2583
2584 let deployer = ProviderBuilder::new()
2585 .wallet(EthereumWallet::from(network_config.signer().clone()))
2586 .connect_http(network_config.l1_url().clone());
2587
2588 let mut contracts = Contracts::new();
2589 let (genesis_state, genesis_stake) = light_client_genesis_from_stake_table(
2590 &network_config.hotshot_config().hotshot_stake_table(),
2591 STAKE_TABLE_CAPACITY_FOR_TEST,
2592 )
2593 .unwrap();
2594 let args = DeployerArgsBuilder::default()
2595 .deployer(deployer.clone())
2596 .rpc_url(network_config.l1_url().clone())
2597 .mock_light_client(true)
2598 .genesis_lc_state(genesis_state)
2599 .genesis_st_state(genesis_stake)
2600 .blocks_per_epoch(10)
2601 .epoch_start_block(1)
2602 .exit_escrow_period(U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS))
2603 .multisig_pauser(network_config.signer().address())
2604 .token_name("Espresso".to_string())
2605 .token_symbol("ESP".to_string())
2606 .initial_token_supply(U256::from(3590000000u64))
2607 .ops_timelock_delay(U256::from(0))
2608 .ops_timelock_admin(network_config.signer().address())
2609 .ops_timelock_proposers(vec![network_config.signer().address()])
2610 .ops_timelock_executors(vec![network_config.signer().address()])
2611 .safe_exit_timelock_delay(U256::from(10))
2612 .safe_exit_timelock_admin(network_config.signer().address())
2613 .safe_exit_timelock_proposers(vec![network_config.signer().address()])
2614 .safe_exit_timelock_executors(vec![network_config.signer().address()])
2615 .build()
2616 .unwrap();
2617 args.deploy_to_stake_table_v3(&mut contracts)
2618 .await
2619 .expect("contracts deployed");
2620 let st_addr = contracts
2621 .address(Contract::StakeTableProxy)
2622 .expect("StakeTableProxy deployed");
2623
2624 let mut planned_txns = StakingTransactions::create(
2625 network_config.l1_url().clone(),
2626 &deployer,
2627 st_addr,
2628 validators,
2629 None,
2630 DelegationConfig::MultipleDelegators,
2631 )
2632 .await
2633 .expect("stake table setup failed");
2634 planned_txns
2635 .apply_prerequisites()
2636 .await
2637 .expect("prerequisites failed");
2638 planned_txns
2641 .apply_one()
2642 .await
2643 .expect("send tx failed")
2644 .expect("at least one registration transaction is queued");
2645
2646 let storage = P::tmp_storage().await;
2647 let persistence = P::options(&storage).create().await.unwrap();
2648
2649 let l1_client = L1ClientOptions {
2650 l1_retry_delay: Duration::from_millis(10),
2651 ..Default::default()
2652 }
2653 .connect(vec![network_config.l1_url().clone()])
2654 .unwrap();
2655 l1_client.spawn_tasks().await;
2656
2657 let fetcher = Fetcher::new(
2658 Arc::new(NullStateCatchup::default()),
2659 Arc::new(Mutex::new(persistence.clone())),
2660 l1_client.clone(),
2661 ChainConfig {
2662 stake_table_contract: Some(st_addr),
2663 base_fee: 0.into(),
2664 ..Default::default()
2665 },
2666 );
2667
2668 let (offset, events) = persistence.load_events(0, 0).await?;
2669 assert_eq!(offset, None);
2670 assert!(events.is_empty());
2671
2672 let finalized = tokio::time::timeout(Duration::from_secs(30), async {
2673 loop {
2674 if let Some(finalized) = l1_client.snapshot().await.finalized {
2675 return finalized;
2676 }
2677 tokio::time::sleep(Duration::from_millis(50)).await;
2678 }
2679 })
2680 .await
2681 .expect("l1 client finalized a block");
2682
2683 prefetch_stake_table_events(&fetcher, &l1_client, &finalized, Some(st_addr)).await?;
2684
2685 assert_events_eq(
2686 &persistence,
2687 finalized.number,
2688 &fetcher,
2689 &l1_client,
2690 st_addr,
2691 )
2692 .await?;
2693 let (offset, _) = persistence.load_events(0, finalized.number).await?;
2694 assert_eq!(offset, Some(EventsPersistenceRead::Complete));
2695
2696 tokio::time::timeout(
2698 Duration::from_secs(5),
2699 prefetch_stake_table_events(&Fetcher::mock(), &l1_client, &finalized, None),
2700 )
2701 .await
2702 .expect("prefetch without a contract must return immediately")?;
2703
2704 Ok(())
2705 }
2706
2707 #[rstest_reuse::apply(persistence_types)]
2708 pub async fn test_membership_persistence<P: TestablePersistence>(
2709 _p: PhantomData<P>,
2710 ) -> anyhow::Result<()> {
2711 let tmp = P::tmp_storage().await;
2712 let mut opt = P::options(&tmp);
2713
2714 let storage = opt.create().await.unwrap();
2715
2716 let validator = AuthenticatedValidator::mock();
2717 let mut st = IndexMap::new();
2718 st.insert(validator.account, validator);
2719
2720 storage
2721 .store_stake(EpochNumber::new(10), st.clone(), None, None)
2722 .await?;
2723
2724 let (table, ..) = storage.load_stake(EpochNumber::new(10)).await?.unwrap();
2725 assert_eq!(st, table);
2726
2727 let val2 = AuthenticatedValidator::mock();
2728 let mut st2 = IndexMap::new();
2729 st2.insert(val2.account, val2);
2730 storage
2731 .store_stake(EpochNumber::new(11), st2.clone(), None, None)
2732 .await?;
2733
2734 let tables = storage.load_latest_stake(4).await?.unwrap();
2735 let mut iter = tables.iter();
2736 assert_eq!(
2737 Some(&(EpochNumber::new(11), (st2.clone(), None), None)),
2738 iter.next()
2739 );
2740 assert_eq!(Some(&(EpochNumber::new(10), (st, None), None)), iter.next());
2741 assert_eq!(None, iter.next());
2742
2743 for i in 0..=20 {
2744 storage
2745 .store_stake(EpochNumber::new(i), st2.clone(), None, None)
2746 .await?;
2747 }
2748
2749 let tables = storage.load_latest_stake(5).await?.unwrap();
2750 let mut iter = tables.iter();
2751 assert_eq!(
2752 Some(&(EpochNumber::new(20), (st2.clone(), None), None)),
2753 iter.next()
2754 );
2755 assert_eq!(
2756 Some(&(EpochNumber::new(19), (st2.clone(), None), None)),
2757 iter.next()
2758 );
2759 assert_eq!(
2760 Some(&(EpochNumber::new(18), (st2.clone(), None), None)),
2761 iter.next()
2762 );
2763 assert_eq!(
2764 Some(&(EpochNumber::new(17), (st2.clone(), None), None)),
2765 iter.next()
2766 );
2767 assert_eq!(
2768 Some(&(EpochNumber::new(16), (st2, None), None)),
2769 iter.next()
2770 );
2771 assert_eq!(None, iter.next());
2772
2773 Ok(())
2774 }
2775
2776 #[rstest_reuse::apply(persistence_types)]
2782 pub async fn test_load_partially_stored_epoch_data<P: TestablePersistence>(
2783 _p: PhantomData<P>,
2784 ) -> anyhow::Result<()> {
2785 let tmp = P::tmp_storage().await;
2786 let storage = P::connect(&tmp).await;
2787
2788 let instance_state = NodeState::mock();
2789 let validated_state = hotshot_types::traits::ValidatedState::genesis(&instance_state).0;
2790 let leaf: Leaf2 = Leaf::genesis(&validated_state, &instance_state, MOCK_UPGRADE.base)
2791 .await
2792 .into();
2793 let header = leaf.block_header().clone();
2794
2795 let validator = AuthenticatedValidator::mock();
2796 let mut stake = IndexMap::new();
2797 stake.insert(validator.account, validator);
2798
2799 let epoch = EpochNumber::new(1);
2801 assert!(storage.load_stake(epoch).await?.is_none());
2802 assert!(storage.load_drb_result(epoch).await?.is_none());
2803 assert!(storage.load_epoch_root(epoch).await?.is_none());
2804
2805 storage.store_drb_result(epoch, [1; 32]).await?;
2807 assert!(storage.load_stake(epoch).await?.is_none());
2808 assert_eq!(storage.load_drb_result(epoch).await?, Some([1; 32]));
2809 assert!(storage.load_epoch_root(epoch).await?.is_none());
2810
2811 let epoch2 = EpochNumber::new(2);
2813 storage
2814 .store_stake(epoch2, stake.clone(), None, None)
2815 .await?;
2816 assert!(storage.load_stake(epoch2).await?.is_some());
2817 assert!(storage.load_drb_result(epoch2).await?.is_none());
2818 assert!(storage.load_epoch_root(epoch2).await?.is_none());
2819
2820 storage.store_epoch_root(epoch, header.clone()).await?;
2823 storage
2824 .store_stake(epoch, stake.clone(), None, None)
2825 .await?;
2826 let (table, ..) = storage.load_stake(epoch).await?.unwrap();
2827 assert_eq!(stake, table);
2828 assert_eq!(storage.load_drb_result(epoch).await?, Some([1; 32]));
2829 assert_eq!(storage.load_epoch_root(epoch).await?, Some(header));
2830
2831 Ok(())
2832 }
2833
2834 #[rstest_reuse::apply(persistence_types)]
2835 pub async fn test_delete_stake_tables<P: TestablePersistence>(
2836 _p: PhantomData<P>,
2837 ) -> anyhow::Result<()> {
2838 let tmp = P::tmp_storage().await;
2839 let mut opt = P::options(&tmp);
2840 let storage = opt.create().await.unwrap();
2841
2842 let event1 = StakeTableEvent::Delegate(Delegated {
2843 delegator: Address::ZERO,
2844 validator: Address::ZERO,
2845 amount: U256::from(100),
2846 });
2847 let event2 = StakeTableEvent::Delegate(Delegated {
2848 delegator: Address::ZERO,
2849 validator: Address::ZERO,
2850 amount: U256::from(200),
2851 });
2852
2853 let l1_block = 42u64;
2854 let events: Vec<(EventKey, StakeTableEvent)> =
2855 vec![((l1_block, 0), event1), ((l1_block, 1), event2)];
2856
2857 storage.store_events(l1_block, events.clone()).await?;
2858
2859 let (read_offset, loaded_events) = storage.load_events(0_u64, l1_block).await?;
2860 assert!(read_offset.is_some());
2861 assert_eq!(loaded_events.len(), 2);
2862 assert_eq!(loaded_events, events);
2863
2864 let v = RegisteredValidator::mock();
2866 let mut vmap = IndexMap::new();
2867 vmap.insert(v.account, v);
2868 storage
2869 .store_all_validators(EpochNumber::new(1), vmap.clone())
2870 .await?;
2871
2872 let loaded = storage
2873 .load_all_validators(EpochNumber::new(1), 0, 10)
2874 .await?;
2875 assert_eq!(loaded.len(), 1);
2876
2877 storage.delete_stake_tables().await?;
2878
2879 let (read_offset, loaded_events) = storage.load_events(0_u64, l1_block).await?;
2881 assert!(read_offset.is_none());
2882 assert!(loaded_events.is_empty());
2883
2884 let loaded = storage
2886 .load_all_validators(EpochNumber::new(1), 0, 10)
2887 .await
2888 .unwrap_or_default();
2889 assert!(loaded.is_empty());
2890
2891 let event3 = StakeTableEvent::Delegate(Delegated {
2892 delegator: Address::ZERO,
2893 validator: Address::ZERO,
2894 amount: U256::from(300),
2895 });
2896 let new_events: Vec<(EventKey, StakeTableEvent)> = vec![((l1_block, 0), event3)];
2897 storage.store_events(l1_block, new_events.clone()).await?;
2898
2899 let (read_offset, loaded_events) = storage.load_events(0_u64, l1_block).await?;
2900 assert!(read_offset.is_some());
2901 assert_eq!(loaded_events.len(), 1);
2902 assert_eq!(loaded_events, new_events);
2903
2904 Ok(())
2905 }
2906
2907 #[rstest_reuse::apply(persistence_types)]
2908 pub async fn test_store_and_load_all_validators<P: TestablePersistence>(
2909 _p: PhantomData<P>,
2910 ) -> anyhow::Result<()> {
2911 let tmp = P::tmp_storage().await;
2912 let mut opt = P::options(&tmp);
2913 let storage = opt.create().await.unwrap();
2914
2915 let mut vmap1 = IndexMap::new();
2916 for _i in 0..25 {
2917 let v = RegisteredValidator::mock();
2918 vmap1.insert(v.account, v);
2919 }
2920 storage
2921 .store_all_validators(EpochNumber::new(10), vmap1.clone())
2922 .await?;
2923
2924 let mut expected_all: Vec<_> = vmap1.clone().into_values().collect();
2925 expected_all.sort_by_key(|v| v.account);
2926
2927 let loaded_all = storage
2929 .load_all_validators(EpochNumber::new(10), 0, 100)
2930 .await?;
2931 assert_eq!(expected_all, loaded_all);
2933
2934 let loaded_first_10 = storage
2936 .load_all_validators(EpochNumber::new(10), 0, 10)
2937 .await?;
2938
2939 assert_eq!(expected_all[..10], loaded_first_10);
2940
2941 let loaded_next_10 = storage
2943 .load_all_validators(EpochNumber::new(10), 10, 10)
2944 .await?;
2945
2946 assert_eq!(expected_all[10..20], loaded_next_10);
2947
2948 let loaded_last_5 = storage
2950 .load_all_validators(EpochNumber::new(10), 20, 10)
2951 .await?;
2952
2953 assert_eq!(expected_all[20..], loaded_last_5);
2954
2955 let loaded_empty = storage
2957 .load_all_validators(EpochNumber::new(10), 100, 10)
2958 .await?;
2959 assert!(loaded_empty.is_empty());
2960
2961 let validator2 = RegisteredValidator::mock();
2963 let mut vmap2 = IndexMap::new();
2964 vmap2.insert(validator2.account, validator2.clone());
2965
2966 storage
2967 .store_all_validators(EpochNumber::new(11), vmap2.clone())
2968 .await?;
2969
2970 let mut expected_epoch11: Vec<_> = vmap2.clone().into_values().collect();
2971 expected_epoch11.sort_by_key(|v| v.account);
2972
2973 let loaded2 = storage
2974 .load_all_validators(EpochNumber::new(11), 0, 100)
2975 .await?;
2976
2977 assert_eq!(expected_epoch11, loaded2);
2978
2979 let loaded1_again = storage
2981 .load_all_validators(EpochNumber::new(10), 0, 100)
2982 .await?;
2983
2984 assert_eq!(expected_all, loaded1_again);
2985
2986 Ok(())
2987 }
2988
2989 #[rstest_reuse::apply(persistence_types)]
2992 pub async fn test_non_consecutive_decide<P: TestablePersistence>(_p: PhantomData<P>) {
2993 let tmp = P::tmp_storage().await;
2994 let storage = P::connect(&tmp).await;
2995
2996 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
2997 let genesis_leaf: Leaf2 =
2998 Leaf2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE.base).await;
2999 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
3000 proposal: QuorumProposal2::<SeqTypes> {
3001 epoch: None,
3002 block_header: genesis_leaf.block_header().clone(),
3003 view_number: genesis_leaf.view_number(),
3004 justify_qc: QuorumCertificate2::genesis(
3005 &ValidatedState::default(),
3006 &node_state,
3007 TEST_UPGRADE,
3008 )
3009 .await,
3010 upgrade_certificate: None,
3011 view_change_evidence: None,
3012 next_drb_result: None,
3013 next_epoch_justify_qc: None,
3014 state_cert: None,
3015 },
3016 };
3017
3018 let leaf0 = Leaf2::from_quorum_proposal(&quorum_proposal);
3019
3020 quorum_proposal.proposal.view_number = ViewNumber::new(2);
3021 *quorum_proposal.proposal.block_header.height_mut() = 2;
3022 quorum_proposal.proposal.justify_qc.view_number = ViewNumber::new(1);
3023 let leaf2 = Leaf2::from_quorum_proposal(&quorum_proposal);
3024
3025 let newest_view = ViewNumber::new(2 + DECIDE_GAP_FILL_HORIZON);
3026 quorum_proposal.proposal.view_number = newest_view;
3027 *quorum_proposal.proposal.block_header.height_mut() = newest_view.u64();
3028 let newest_leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
3029
3030 let mut qc0 = leaf0.justify_qc().clone();
3031 qc0.data.leaf_commit = Committable::commit(&leaf0);
3032
3033 let mut qc2 = leaf2.justify_qc().clone();
3034 qc2.view_number += 1;
3035 qc2.data.leaf_commit = Committable::commit(&leaf2);
3036
3037 let mut newest_qc = newest_leaf.justify_qc().clone();
3038 newest_qc.view_number = newest_view;
3039 newest_qc.data.leaf_commit = Committable::commit(&newest_leaf);
3040
3041 let cert2 = {
3042 let data = Vote2Data {
3043 leaf_commit: Committable::commit(&leaf2),
3044 epoch: EpochNumber::new(1),
3045 block_number: leaf2.height(),
3046 };
3047 Certificate2::new(
3048 data.clone(),
3049 data.commit(),
3050 leaf2.view_number(),
3051 None,
3052 PhantomData,
3053 )
3054 };
3055
3056 storage
3058 .append_decided_leaves(
3059 ViewNumber::new(0),
3060 [(
3061 &leaf_info(leaf0.clone()),
3062 CertificatePair::non_epoch_change(qc0),
3063 )],
3064 &FailConsumer,
3065 )
3066 .await
3067 .unwrap();
3068
3069 storage
3072 .persist_decided_leaves(
3073 [(
3074 &leaf_info(newest_leaf),
3075 CertificatePair::non_epoch_change(newest_qc),
3076 )],
3077 None,
3078 &NullEventConsumer,
3079 )
3080 .await
3081 .unwrap();
3082 storage
3083 .append_cert2(leaf2.view_number(), cert2.clone())
3084 .await
3085 .unwrap();
3086 let consumer = EventCollector::default();
3087 storage
3088 .append_decided_leaves(
3089 ViewNumber::new(2),
3090 [(
3091 &leaf_info(leaf2.clone()),
3092 CertificatePair::non_epoch_change(qc2),
3093 )],
3094 &consumer,
3095 )
3096 .await
3097 .unwrap();
3098
3099 let events = consumer.events.read().await;
3100 assert_eq!(events.len(), 2, "{events:#?}");
3101
3102 let CoordinatorEvent::NewDecide {
3103 leaf_infos: leaf_infos0,
3104 cert2: cert2_0,
3105 ..
3106 } = &events[0]
3107 else {
3108 panic!("expected decide event, got {:?}", events[0]);
3109 };
3110 assert_eq!(leaf_infos0.len(), 1);
3111 assert_eq!(leaf_infos0[0].leaf, leaf0);
3112 assert_eq!(*cert2_0, None);
3113
3114 let CoordinatorEvent::NewDecide {
3115 leaf_infos: leaf_infos2,
3116 cert2: cert2_2,
3117 ..
3118 } = &events[1]
3119 else {
3120 panic!("expected decide event, got {:?}", events[1]);
3121 };
3122 assert_eq!(leaf_infos2.len(), 1);
3123 assert_eq!(leaf_infos2[0].leaf, leaf2);
3124 assert_eq!(cert2_2.as_ref(), Some(&cert2));
3125 }
3126
3127 #[rstest_reuse::apply(persistence_types)]
3130 pub async fn test_consensus_only_decide<P: TestablePersistence>(_p: PhantomData<P>) {
3131 let tmp = P::tmp_storage().await;
3132
3133 let node_state = NodeState::mock().with_genesis_version(versions::NEW_PROTOCOL_VERSION);
3134 let leaf: Leaf2 =
3135 Leaf2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE.base).await;
3136 let leaf_payload = leaf.block_payload().unwrap();
3137 let leaf_payload_bytes_arc = leaf_payload.encode();
3138 let avidm_param = init_avidm_gf2_param(2).unwrap();
3139 let weights = Vec::from([1u32; 2]);
3140 let ns_table = parse_ns_table(
3141 leaf_payload.byte_len().as_usize(),
3142 &leaf_payload.ns_table().encode(),
3143 );
3144 let (payload_commitment, common, shares) =
3145 AvidmGf2Scheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
3146 .unwrap();
3147
3148 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
3149 let mut vid = AvidmGf2DisperseShare::<SeqTypes> {
3150 view_number: ViewNumber::new(0),
3151 payload_commitment,
3152 share: shares[0].clone(),
3153 recipient_key: pubkey,
3154 epoch: Some(EpochNumber::new(0)),
3155 target_epoch: Some(EpochNumber::new(0)),
3156 common,
3157 }
3158 .to_proposal(&privkey)
3159 .unwrap();
3160 let mut da_proposal = Proposal {
3161 data: DaProposal2::<SeqTypes> {
3162 encoded_transactions: leaf_payload_bytes_arc.clone(),
3163 metadata: leaf_payload.ns_table().clone(),
3164 view_number: ViewNumber::new(0),
3165 epoch: Some(EpochNumber::new(0)),
3166 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
3167 },
3168 signature: BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc).unwrap(),
3169 _pd: Default::default(),
3170 };
3171 let vid_commitment = vid_commitment(
3172 &leaf_payload_bytes_arc,
3173 &leaf.block_header().metadata().encode(),
3174 2,
3175 TEST_UPGRADE.base,
3176 );
3177 let mut quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
3178 proposal: QuorumProposal2::<SeqTypes> {
3179 block_header: leaf.block_header().clone(),
3180 view_number: ViewNumber::genesis(),
3181 justify_qc: QuorumCertificate2::genesis(
3182 &ValidatedState::default(),
3183 &node_state,
3184 TEST_UPGRADE,
3185 )
3186 .await,
3187 upgrade_certificate: None,
3188 view_change_evidence: None,
3189 next_drb_result: None,
3190 next_epoch_justify_qc: None,
3191 epoch: None,
3192 state_cert: None,
3193 },
3194 };
3195 let mut qc =
3196 QuorumCertificate2::genesis(&ValidatedState::default(), &node_state, TEST_UPGRADE)
3197 .await;
3198
3199 let mut chain = Vec::new();
3200 for i in 0..4 {
3201 quorum_proposal.proposal.view_number = ViewNumber::new(i);
3202 *quorum_proposal.proposal.block_header.height_mut() = i;
3203 let leaf = Leaf2::from_quorum_proposal(&quorum_proposal);
3204 qc.view_number = leaf.view_number();
3205 qc.data.leaf_commit = Committable::commit(&leaf);
3206 vid.data.view_number = leaf.view_number();
3207 da_proposal.data.view_number = leaf.view_number();
3208 chain.push((
3209 leaf,
3210 CertificatePair::non_epoch_change(qc.clone()),
3211 convert_proposal(vid.clone()),
3212 da_proposal.clone(),
3213 ));
3214 }
3215 let state_cert = LightClientStateUpdateCertificateV2::<SeqTypes> {
3216 epoch: EpochNumber::new(1),
3217 light_client_state: LightClientState {
3218 view_number: 2,
3219 ..Default::default()
3220 },
3221 next_stake_table_state: StakeTableState::default(),
3222 signatures: Vec::new(),
3223 auth_root: Default::default(),
3224 };
3225
3226 let mut opt = P::options(&tmp);
3227 opt.set_consensus_only();
3228 let storage = opt.create().await.unwrap();
3229 for (_, _, vid, da) in &chain {
3230 storage.append_vid(vid).await.unwrap();
3231 storage.append_da2(da, vid_commitment).await.unwrap();
3232 }
3233 storage.add_state_cert(state_cert.clone()).await.unwrap();
3234 for i in 0..4 {
3235 assert_eq!(
3236 storage.load_da_proposal(ViewNumber::new(i)).await.unwrap(),
3237 None
3238 );
3239 }
3240
3241 let consumer = EventCollector::default();
3242 let leaf_chain = chain[..3]
3243 .iter()
3244 .map(|(leaf, qc, ..)| (leaf_info(leaf.clone()), qc.clone()))
3245 .collect::<Vec<_>>();
3246 storage
3247 .append_decided_leaves(
3248 ViewNumber::new(2),
3249 leaf_chain.iter().map(|(info, qc)| (info, qc.clone())),
3250 &consumer,
3251 )
3252 .await
3253 .unwrap();
3254
3255 assert!(consumer.leaf_chain().await.is_empty());
3256 let (anchor_leaf, anchor_qc, ..) = &chain[2];
3257 assert_eq!(
3258 storage.load_anchor_leaf().await.unwrap(),
3259 Some((anchor_leaf.clone(), anchor_qc.clone()))
3260 );
3261 assert_eq!(
3262 storage.get_state_cert_by_epoch(1).await.unwrap(),
3263 Some(state_cert.clone())
3264 );
3265 for i in 0..2 {
3266 assert_eq!(
3267 storage.load_vid_share(ViewNumber::new(i)).await.unwrap(),
3268 None
3269 );
3270 }
3271 assert!(
3273 storage
3274 .load_vid_share(ViewNumber::new(3))
3275 .await
3276 .unwrap()
3277 .is_some()
3278 );
3279 drop(storage);
3280
3281 let mut opt = P::options(&tmp);
3282 opt.set_consensus_only();
3283 let storage = opt.create().await.unwrap();
3284 let (initializer, anchor_view) = storage
3285 .load_consensus_state(
3286 NodeState::mock(),
3287 Upgrade::trivial(versions::EPOCH_REWARD_VERSION),
3288 )
3289 .await
3290 .unwrap();
3291 assert_eq!(initializer.anchor_leaf(), anchor_leaf);
3292 assert_eq!(anchor_view, Some(ViewNumber::new(2)));
3293 assert_eq!(storage.load_state_cert().await.unwrap(), Some(state_cert));
3294 }
3295}