1use std::{
2 cmp::min,
3 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
4 future::Future,
5 ops::Bound,
6 str::FromStr,
7 sync::Arc,
8 time::{Duration, Instant},
9};
10
11use alloy::{
12 eips::{BlockId, BlockNumberOrTag},
13 primitives::{utils::format_ether, Address, U256},
14 providers::Provider,
15 rpc::types::{Filter, Log},
16 sol_types::{SolEvent, SolEventInterface},
17};
18use anyhow::{bail, ensure, Context};
19use ark_ec::AffineRepr;
20use ark_serialize::CanonicalSerialize;
21use async_lock::{Mutex, RwLock, RwLockUpgradableReadGuard};
22use bigdecimal::BigDecimal;
23use committable::{Commitment, Committable, RawCommitmentBuilder};
24use futures::future::BoxFuture;
25use hotshot::types::{BLSPubKey, SchnorrPubKey, SignatureKey as _};
26use hotshot_contract_adapter::sol_types::{
27 EspToken::{self, EspTokenInstance},
28 StakeTableV2::{
29 self, CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated,
30 StakeTableV2Events, Undelegated, ValidatorExit, ValidatorRegistered, ValidatorRegisteredV2,
31 },
32};
33use hotshot_types::{
34 data::{vid_disperse::VID_TARGET_TOTAL_STAKE, EpochNumber},
35 drb::{
36 election::{generate_stake_cdf, select_randomized_leader, RandomizedCommittee},
37 DrbResult,
38 },
39 epoch_membership::EpochMembershipCoordinator,
40 stake_table::{HSStakeTable, StakeTableEntry},
41 traits::{
42 election::Membership,
43 node_implementation::{ConsensusTime, NodeImplementation, NodeType},
44 signature_key::StakeTableEntryType,
45 },
46 utils::{epoch_from_block_number, root_block_in_epoch, transition_block_for_epoch},
47 PeerConfig,
48};
49use humantime::format_duration;
50use indexmap::IndexMap;
51use itertools::Itertools;
52use num_traits::{FromPrimitive, Zero};
53use thiserror::Error;
54use tokio::{spawn, time::sleep};
55use tracing::Instrument;
56use vbs::version::StaticVersionType;
57
58#[cfg(any(test, feature = "testing"))]
59use super::v0_3::DAMembers;
60use super::{
61 traits::{MembershipPersistence, StateCatchup},
62 v0_3::{ChainConfig, EventKey, Fetcher, StakeTableEvent, StakeTableUpdateTask, Validator},
63 Header, L1Client, Leaf2, PubKey, SeqTypes,
64};
65use crate::{
66 traits::EventsPersistenceRead,
67 v0_1::L1Provider,
68 v0_3::{
69 EventSortingError, ExpectedStakeTableError, FetchRewardError, RewardAmount,
70 StakeTableError, ASSUMED_BLOCK_TIME_SECONDS, BLOCKS_PER_YEAR, COMMISSION_BASIS_POINTS,
71 INFLATION_RATE, MILLISECONDS_PER_YEAR,
72 },
73 DrbAndHeaderUpgradeVersion, EpochVersion,
74};
75
76type Epoch = <SeqTypes as NodeType>::Epoch;
77pub type ValidatorMap = IndexMap<Address, Validator<BLSPubKey>>;
78
79pub type StakeTableHash = Commitment<StakeTableState>;
80
81type ApplyEventResult<T> = Result<Result<T, ExpectedStakeTableError>, StakeTableError>;
86
87trait DisplayLog {
89 fn display(&self) -> String;
90}
91
92impl DisplayLog for Log {
93 fn display(&self) -> String {
94 let block = self.block_number.unwrap_or_default();
98 let index = self.log_index.unwrap_or_default();
99 let hash = self.transaction_hash.unwrap_or_default();
100 format!("Log(block={block},index={index},transaction_hash={hash})")
101 }
102}
103
104impl TryFrom<StakeTableV2Events> for StakeTableEvent {
105 type Error = anyhow::Error;
106
107 fn try_from(value: StakeTableV2Events) -> anyhow::Result<Self> {
108 match value {
109 StakeTableV2Events::ValidatorRegistered(v) => Ok(StakeTableEvent::Register(v)),
110 StakeTableV2Events::ValidatorRegisteredV2(v) => Ok(StakeTableEvent::RegisterV2(v)),
111 StakeTableV2Events::ValidatorExit(v) => Ok(StakeTableEvent::Deregister(v)),
112 StakeTableV2Events::Delegated(v) => Ok(StakeTableEvent::Delegate(v)),
113 StakeTableV2Events::Undelegated(v) => Ok(StakeTableEvent::Undelegate(v)),
114 StakeTableV2Events::ConsensusKeysUpdated(v) => Ok(StakeTableEvent::KeyUpdate(v)),
115 StakeTableV2Events::ConsensusKeysUpdatedV2(v) => Ok(StakeTableEvent::KeyUpdateV2(v)),
116 StakeTableV2Events::CommissionUpdated(v) => Ok(StakeTableEvent::CommissionUpdate(v)),
117 StakeTableV2Events::ExitEscrowPeriodUpdated(v) => Err(anyhow::anyhow!(
118 "Unsupported StakeTableV2Events::ExitEscrowPeriodUpdated({v:?})"
119 )),
120 StakeTableV2Events::Initialized(v) => Err(anyhow::anyhow!(
121 "Unsupported StakeTableV2Events::Initialized({v:?})"
122 )),
123 StakeTableV2Events::MaxCommissionIncreaseUpdated(v) => Err(anyhow::anyhow!(
124 "Unsupported StakeTableV2Events::MaxCommissionIncreaseUpdated({v:?})"
125 )),
126 StakeTableV2Events::MinCommissionUpdateIntervalUpdated(v) => Err(anyhow::anyhow!(
127 "Unsupported StakeTableV2Events::MinCommissionUpdateIntervalUpdated({v:?})"
128 )),
129 StakeTableV2Events::OwnershipTransferred(v) => Err(anyhow::anyhow!(
130 "Unsupported StakeTableV2Events::OwnershipTransferred({v:?})"
131 )),
132 StakeTableV2Events::Paused(v) => Err(anyhow::anyhow!(
133 "Unsupported StakeTableV2Events::Paused({v:?})"
134 )),
135 StakeTableV2Events::RoleAdminChanged(v) => Err(anyhow::anyhow!(
136 "Unsupported StakeTableV2Events::RoleAdminChanged({v:?})"
137 )),
138 StakeTableV2Events::RoleGranted(v) => Err(anyhow::anyhow!(
139 "Unsupported StakeTableV2Events::RoleGranted({v:?})"
140 )),
141 StakeTableV2Events::RoleRevoked(v) => Err(anyhow::anyhow!(
142 "Unsupported StakeTableV2Events::RoleRevoked({v:?})"
143 )),
144 StakeTableV2Events::Unpaused(v) => Err(anyhow::anyhow!(
145 "Unsupported StakeTableV2Events::Unpaused({v:?})"
146 )),
147 StakeTableV2Events::Upgraded(v) => Err(anyhow::anyhow!(
148 "Unsupported StakeTableV2Events::Upgraded({v:?})"
149 )),
150 StakeTableV2Events::Withdrawal(v) => Err(anyhow::anyhow!(
151 "Unsupported StakeTableV2Events::Withdrawal({v:?})"
152 )),
153 }
154 }
155}
156
157fn sort_stake_table_events(
158 event_logs: Vec<(StakeTableV2Events, Log)>,
159) -> Result<Vec<(EventKey, StakeTableEvent)>, EventSortingError> {
160 let mut events: Vec<(EventKey, StakeTableEvent)> = Vec::new();
161
162 let key = |log: &Log| -> Result<EventKey, EventSortingError> {
163 let block_number = log
164 .block_number
165 .ok_or(EventSortingError::MissingBlockNumber)?;
166 let log_index = log.log_index.ok_or(EventSortingError::MissingLogIndex)?;
167 Ok((block_number, log_index))
168 };
169
170 for (e, log) in event_logs {
171 let k = key(&log)?;
172 let evt: StakeTableEvent = e
173 .try_into()
174 .map_err(|_| EventSortingError::InvalidStakeTableV2Event)?;
175 events.push((k, evt));
176 }
177
178 events.sort_by_key(|(key, _)| *key);
179 Ok(events)
180}
181
182#[derive(Debug, Default)]
183pub struct StakeTableState {
184 validators: ValidatorMap,
185 validator_exits: HashSet<Address>,
186 used_bls_keys: HashSet<BLSPubKey>,
187 used_schnorr_keys: HashSet<SchnorrPubKey>,
188}
189
190impl Committable for StakeTableState {
191 fn commit(&self) -> committable::Commitment<Self> {
192 let mut builder = RawCommitmentBuilder::new(&Self::tag());
193
194 for (_, validator) in self.validators.iter().sorted_by_key(|(a, _)| *a) {
195 builder = builder.field("validator", validator.commit());
196 }
197
198 builder = builder.constant_str("used_bls_keys");
199 for key in self.used_bls_keys.iter().sorted() {
200 builder = builder.var_size_bytes(&key.to_bytes());
201 }
202
203 builder = builder.constant_str("used_schnorr_keys");
204 for key in self
205 .used_schnorr_keys
206 .iter()
207 .sorted_by(|a, b| a.to_affine().xy().cmp(&b.to_affine().xy()))
208 {
209 let mut schnorr_key_bytes = vec![];
210 key.serialize_with_mode(&mut schnorr_key_bytes, ark_serialize::Compress::Yes)
211 .unwrap();
212 builder = builder.var_size_bytes(&schnorr_key_bytes);
213 }
214
215 builder = builder.constant_str("validator_exits");
216
217 for key in self.validator_exits.iter().sorted() {
218 builder = builder.fixed_size_bytes(&key.into_array());
219 }
220
221 builder.finalize()
222 }
223
224 fn tag() -> String {
225 "STAKE_TABLE".to_string()
226 }
227}
228
229impl StakeTableState {
230 pub fn new() -> Self {
231 Self {
232 validators: IndexMap::new(),
233 validator_exits: HashSet::new(),
234 used_bls_keys: HashSet::new(),
235 used_schnorr_keys: HashSet::new(),
236 }
237 }
238
239 pub fn into_validators(self) -> ValidatorMap {
240 self.validators
241 }
242
243 pub fn validators(&self) -> &ValidatorMap {
244 &self.validators
245 }
246
247 pub fn apply_event(&mut self, event: StakeTableEvent) -> ApplyEventResult<()> {
248 match event {
249 StakeTableEvent::Register(ValidatorRegistered {
250 account,
251 blsVk,
252 schnorrVk,
253 commission,
254 }) => {
255 let stake_table_key: BLSPubKey = blsVk.into();
256 let state_ver_key: SchnorrPubKey = schnorrVk.into();
257
258 if self.validator_exits.contains(&account) {
259 return Err(StakeTableError::ValidatorAlreadyExited(account));
260 }
261
262 let entry = self.validators.entry(account);
263 if let indexmap::map::Entry::Occupied(_) = entry {
264 return Err(StakeTableError::AlreadyRegistered(account));
265 }
266
267 if !self.used_bls_keys.insert(stake_table_key) {
269 return Err(StakeTableError::BlsKeyAlreadyUsed(
270 stake_table_key.to_string(),
271 ));
272 }
273
274 if !self.used_schnorr_keys.insert(state_ver_key.clone()) {
277 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
278 state_ver_key.to_string(),
279 )));
280 }
281
282 entry.or_insert(Validator {
283 account,
284 stake_table_key,
285 state_ver_key,
286 stake: U256::ZERO,
287 commission,
288 delegators: HashMap::new(),
289 });
290 },
291
292 StakeTableEvent::RegisterV2(reg) => {
293 reg.authenticate()
296 .map_err(|e| StakeTableError::AuthenticationFailed(e.to_string()))?;
297
298 let ValidatorRegisteredV2 {
299 account,
300 blsVK,
301 schnorrVK,
302 commission,
303 ..
304 } = reg;
305
306 let stake_table_key: BLSPubKey = blsVK.into();
307 let state_ver_key: SchnorrPubKey = schnorrVK.into();
308
309 if self.validator_exits.contains(&account) {
311 return Err(StakeTableError::ValidatorAlreadyExited(account));
312 }
313
314 let entry = self.validators.entry(account);
315 if let indexmap::map::Entry::Occupied(_) = entry {
316 return Err(StakeTableError::AlreadyRegistered(account));
317 }
318
319 if !self.used_bls_keys.insert(stake_table_key) {
321 return Err(StakeTableError::BlsKeyAlreadyUsed(
322 stake_table_key.to_string(),
323 ));
324 }
325
326 if !self.used_schnorr_keys.insert(state_ver_key.clone()) {
328 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
329 state_ver_key.to_string(),
330 ));
331 }
332
333 entry.or_insert(Validator {
334 account,
335 stake_table_key,
336 state_ver_key,
337 stake: U256::ZERO,
338 commission,
339 delegators: HashMap::new(),
340 });
341 },
342
343 StakeTableEvent::Deregister(exit) => {
344 self.validator_exits.insert(exit.validator);
345 self.validators
346 .shift_remove(&exit.validator)
347 .ok_or(StakeTableError::ValidatorNotFound(exit.validator))?;
348 },
349
350 StakeTableEvent::Delegate(delegated) => {
351 let Delegated {
352 delegator,
353 validator,
354 amount,
355 } = delegated;
356
357 let val = self
358 .validators
359 .get_mut(&validator)
360 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
361
362 if amount.is_zero() {
363 return Err(StakeTableError::ZeroDelegatorStake(delegator));
364 }
365
366 val.stake += amount;
367 val.delegators
370 .entry(delegator)
371 .and_modify(|stake| *stake += amount)
372 .or_insert(amount);
373 },
374
375 StakeTableEvent::Undelegate(undelegated) => {
376 let Undelegated {
377 delegator,
378 validator,
379 amount,
380 } = undelegated;
381
382 let val = self
383 .validators
384 .get_mut(&validator)
385 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
386
387 val.stake = val
388 .stake
389 .checked_sub(amount)
390 .ok_or(StakeTableError::InsufficientStake)?;
391
392 let delegator_stake = val
393 .delegators
394 .get_mut(&delegator)
395 .ok_or(StakeTableError::DelegatorNotFound(delegator))?;
396
397 *delegator_stake = delegator_stake
398 .checked_sub(amount)
399 .ok_or(StakeTableError::InsufficientStake)?;
400
401 if delegator_stake.is_zero() {
402 val.delegators.remove(&delegator);
403 }
404 },
405
406 StakeTableEvent::KeyUpdate(update) => {
407 let ConsensusKeysUpdated {
408 account,
409 blsVK,
410 schnorrVK,
411 } = update;
412
413 let validator = self
414 .validators
415 .get_mut(&account)
416 .ok_or(StakeTableError::ValidatorNotFound(account))?;
417
418 let stake_table_key: BLSPubKey = blsVK.into();
419 let state_ver_key: SchnorrPubKey = schnorrVK.into();
420
421 if !self.used_bls_keys.insert(stake_table_key) {
422 return Err(StakeTableError::BlsKeyAlreadyUsed(
423 stake_table_key.to_string(),
424 ));
425 }
426
427 if !self.used_schnorr_keys.insert(state_ver_key.clone()) {
430 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
431 state_ver_key.to_string(),
432 )));
433 }
434
435 validator.stake_table_key = stake_table_key;
436 validator.state_ver_key = state_ver_key;
437 },
438
439 StakeTableEvent::KeyUpdateV2(update) => {
440 update
443 .authenticate()
444 .map_err(|e| StakeTableError::AuthenticationFailed(e.to_string()))?;
445
446 let ConsensusKeysUpdatedV2 {
447 account,
448 blsVK,
449 schnorrVK,
450 ..
451 } = update;
452
453 let validator = self
454 .validators
455 .get_mut(&account)
456 .ok_or(StakeTableError::ValidatorNotFound(account))?;
457
458 let stake_table_key: BLSPubKey = blsVK.into();
459 let state_ver_key: SchnorrPubKey = schnorrVK.into();
460
461 if !self.used_bls_keys.insert(stake_table_key) {
463 return Err(StakeTableError::BlsKeyAlreadyUsed(
464 stake_table_key.to_string(),
465 ));
466 }
467
468 if !self.used_schnorr_keys.insert(state_ver_key.clone()) {
470 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
471 state_ver_key.to_string(),
472 ));
473 }
474
475 validator.stake_table_key = stake_table_key;
476 validator.state_ver_key = state_ver_key;
477 },
478
479 StakeTableEvent::CommissionUpdate(CommissionUpdated {
480 validator,
481 newCommission,
482 ..
483 }) => {
484 if newCommission > COMMISSION_BASIS_POINTS {
487 return Err(StakeTableError::InvalidCommission(validator, newCommission));
488 }
489
490 let val = self
495 .validators
496 .get_mut(&validator)
497 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
498
499 val.commission = newCommission;
500 },
501 }
502
503 Ok(Ok(()))
504 }
505}
506
507pub fn validators_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
508 events: I,
509) -> Result<(ValidatorMap, StakeTableHash), StakeTableError> {
510 let mut state = StakeTableState::new();
511 for event in events {
512 match state.apply_event(event.clone()) {
513 Ok(Ok(())) => (), Ok(Err(expected_err)) => {
515 tracing::warn!("Expected error while applying event {event:?}: {expected_err}");
517 },
518 Err(err) => {
519 tracing::error!("Fatal error in applying event {event:?}: {err}");
521 return Err(err);
522 },
523 }
524 }
525 let commit = state.commit();
526 Ok((state.into_validators(), commit))
527}
528
529pub(crate) fn select_active_validator_set(
533 validators: &mut ValidatorMap,
534) -> Result<(), StakeTableError> {
535 let total_validators = validators.len();
536
537 validators.retain(|address, validator| {
539 if validator.delegators.is_empty() {
540 tracing::info!("Validator {address:?} does not have any delegator");
541 return false;
542 }
543
544 if validator.stake.is_zero() {
545 tracing::info!("Validator {address:?} does not have any stake");
546 return false;
547 }
548
549 true
550 });
551
552 tracing::debug!(
553 total_validators,
554 filtered = validators.len(),
555 "Filtered out invalid validators"
556 );
557
558 if validators.is_empty() {
559 tracing::warn!("Validator selection failed: no validators passed minimum criteria");
560 return Err(StakeTableError::NoValidValidators);
561 }
562
563 let maximum_stake = validators.values().map(|v| v.stake).max().ok_or_else(|| {
564 tracing::error!("Could not compute maximum stake from filtered validators");
565 StakeTableError::MissingMaximumStake
566 })?;
567
568 let minimum_stake = maximum_stake
569 .checked_div(U256::from(VID_TARGET_TOTAL_STAKE))
570 .ok_or_else(|| {
571 tracing::error!("Overflow while calculating minimum stake threshold");
572 StakeTableError::MinimumStakeOverflow
573 })?;
574
575 let mut valid_stakers: Vec<_> = validators
576 .iter()
577 .filter(|(_, v)| v.stake >= minimum_stake)
578 .map(|(addr, v)| (*addr, v.stake))
579 .collect();
580
581 tracing::info!(
582 count = valid_stakers.len(),
583 "Number of validators above minimum stake threshold"
584 );
585
586 valid_stakers.sort_by_key(|(_, stake)| std::cmp::Reverse(*stake));
588
589 if valid_stakers.len() > 100 {
590 valid_stakers.truncate(100);
591 }
592
593 let selected_addresses: HashSet<_> = valid_stakers.iter().map(|(addr, _)| *addr).collect();
595 validators.retain(|address, _| selected_addresses.contains(address));
596
597 tracing::info!(
598 final_count = validators.len(),
599 "Selected active validator set"
600 );
601
602 Ok(())
603}
604
605#[derive(Clone, Debug)]
606pub struct ValidatorSet {
607 pub all_validators: ValidatorMap,
608 pub active_validators: ValidatorMap,
609 pub stake_table_hash: Option<StakeTableHash>,
610}
611
612pub(crate) fn validator_set_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
614 events: I,
615) -> Result<ValidatorSet, StakeTableError> {
616 let (all_validators, stake_table_hash) = validators_from_l1_events(events)?;
617 let mut active_validators = all_validators.clone();
618 select_active_validator_set(&mut active_validators)?;
619
620 let validator_set = ValidatorSet {
621 all_validators,
622 active_validators,
623 stake_table_hash: Some(stake_table_hash),
624 };
625
626 Ok(validator_set)
627}
628
629impl std::fmt::Debug for StakeTableEvent {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 match self {
632 StakeTableEvent::Register(event) => write!(f, "Register({:?})", event.account),
633 StakeTableEvent::RegisterV2(event) => write!(f, "RegisterV2({:?})", event.account),
634 StakeTableEvent::Deregister(event) => write!(f, "Deregister({:?})", event.validator),
635 StakeTableEvent::Delegate(event) => write!(f, "Delegate({:?})", event.delegator),
636 StakeTableEvent::Undelegate(event) => write!(f, "Undelegate({:?})", event.delegator),
637 StakeTableEvent::KeyUpdate(event) => write!(f, "KeyUpdate({:?})", event.account),
638 StakeTableEvent::KeyUpdateV2(event) => write!(f, "KeyUpdateV2({:?})", event.account),
639 StakeTableEvent::CommissionUpdate(event) => {
640 write!(f, "CommissionUpdate({:?})", event.validator)
641 },
642 }
643 }
644}
645
646#[derive(Clone, derive_more::derive::Debug)]
647pub struct EpochCommittees {
649 non_epoch_committee: NonEpochCommittee,
651 state: HashMap<Epoch, EpochCommittee>,
653 randomized_committees: BTreeMap<Epoch, RandomizedCommittee<StakeTableEntry<PubKey>>>,
655 da_committees: BTreeMap<u64, DaCommittee>,
657 first_epoch: Option<Epoch>,
658 epoch_height: u64,
659 fixed_block_reward: Option<RewardAmount>,
662 fetcher: Arc<Fetcher>,
663}
664
665#[derive(Debug, Clone)]
666struct DaCommittee {
667 committee: Vec<PeerConfig<SeqTypes>>,
668 indexed_committee: HashMap<PubKey, PeerConfig<SeqTypes>>,
669}
670
671impl Fetcher {
672 pub fn new(
673 peers: Arc<dyn StateCatchup>,
674 persistence: Arc<Mutex<dyn MembershipPersistence>>,
675 l1_client: L1Client,
676 chain_config: ChainConfig,
677 ) -> Self {
678 Self {
679 peers,
680 persistence,
681 l1_client,
682 chain_config: Arc::new(Mutex::new(chain_config)),
683 update_task: StakeTableUpdateTask(Mutex::new(None)).into(),
684 initial_supply: Arc::new(RwLock::new(None)),
685 }
686 }
687
688 pub async fn spawn_update_loop(&self) {
689 let mut update_task = self.update_task.0.lock().await;
690 if update_task.is_none() {
691 *update_task = Some(spawn(self.update_loop()));
692 }
693 }
694
695 fn update_loop(&self) -> impl Future<Output = ()> {
700 let span = tracing::warn_span!("Stake table update loop");
701 let self_clone = self.clone();
702 let state = self.l1_client.state.clone();
703 let l1_retry = self.l1_client.options().l1_retry_delay;
704 let update_delay = self.l1_client.options().stake_table_update_interval;
705 let chain_config = self.chain_config.clone();
706
707 async move {
708 let stake_contract_address = loop {
713 let contract = chain_config.lock().await.stake_table_contract;
714 match contract {
715 Some(addr) => break addr,
716 None => {
717 tracing::debug!(
718 "Stake table contract address not found. Retrying in {l1_retry:?}...",
719 );
720 },
721 }
722 sleep(l1_retry).await;
723 };
724
725 loop {
727 let finalized_block = loop {
728 let last_finalized = state.lock().await.last_finalized;
729 if let Some(block) = last_finalized {
730 break block;
731 }
732 tracing::debug!("Finalized block not yet available. Retrying in {l1_retry:?}",);
733 sleep(l1_retry).await;
734 };
735
736 tracing::debug!("Attempting to fetch stake table at L1 block {finalized_block:?}",);
737
738 loop {
739 match self_clone
740 .fetch_and_store_stake_table_events(stake_contract_address, finalized_block)
741 .await
742 {
743 Ok(events) => {
744 tracing::info!(
745 "Successfully fetched and stored stake table events at \
746 block={finalized_block:?}"
747 );
748 tracing::debug!("events={events:?}");
749 break;
750 },
751 Err(e) => {
752 tracing::error!(
753 "Error fetching stake table at block {finalized_block:?}. err= \
754 {e:#}",
755 );
756 sleep(l1_retry).await;
757 },
758 }
759 }
760
761 tracing::debug!("Waiting {update_delay:?} before next stake table update...",);
762 sleep(update_delay).await;
763 }
764 }
765 .instrument(span)
766 }
767
768 pub async fn fetch_and_store_stake_table_events(
775 &self,
776 contract: Address,
777 to_block: u64,
778 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
779 let (read_l1_offset, persistence_events) = {
780 let persistence_lock = self.persistence.lock().await;
781 persistence_lock.load_events(to_block).await?
782 };
783
784 tracing::info!("loaded events from storage to_block={to_block:?}");
785
786 if let Some(EventsPersistenceRead::Complete) = read_l1_offset {
789 return Ok(persistence_events);
790 }
791
792 let from_block = read_l1_offset
793 .map(|read| match read {
794 EventsPersistenceRead::UntilL1Block(block) => Ok(block + 1),
795 EventsPersistenceRead::Complete => Err(anyhow::anyhow!(
796 "Unexpected state. offset is complete after returning early"
797 )),
798 })
799 .transpose()?;
800
801 ensure!(
802 Some(to_block) >= from_block,
803 "to_block {to_block:?} is less than from_block {from_block:?}"
804 );
805
806 tracing::info!(%to_block, from_block = ?from_block, "Fetching events from contract");
807
808 let contract_events = Self::fetch_events_from_contract(
809 self.l1_client.clone(),
810 contract,
811 from_block,
812 to_block,
813 )
814 .await?;
815
816 if !contract_events.is_empty() {
818 tracing::info!(
819 "storing {} new events in storage to_block={to_block:?}",
820 contract_events.len()
821 );
822 {
823 let persistence_lock = self.persistence.lock().await;
824 persistence_lock
825 .store_events(to_block, contract_events.clone())
826 .await
827 .inspect_err(|e| tracing::error!("failed to store events. err={e}"))?;
828 }
829 }
830
831 let mut events = match from_block {
832 Some(_) => persistence_events
833 .into_iter()
834 .chain(contract_events)
835 .collect(),
836 None => contract_events,
837 };
838
839 let len_before_dedup = events.len();
844 events.dedup();
845 let len_after_dedup = events.len();
846 if len_before_dedup != len_after_dedup {
847 tracing::warn!("Duplicate events found and removed. This should not normally happen.")
848 }
849
850 Ok(events)
851 }
852
853 fn validate_event(event: &StakeTableV2Events, log: &Log) -> Result<bool, StakeTableError> {
860 match event {
861 StakeTableV2Events::ValidatorRegisteredV2(evt) => {
862 if let Err(err) = evt.authenticate() {
863 tracing::warn!(
864 %err,
865 "Failed to authenticate ValidatorRegisteredV2 event: {}",
866 log.display()
867 );
868 return Ok(false);
869 }
870 },
871 StakeTableV2Events::ConsensusKeysUpdatedV2(evt) => {
872 if let Err(err) = evt.authenticate() {
873 tracing::warn!(
874 %err,
875 "Failed to authenticate ConsensusKeysUpdatedV2 event: {}",
876 log.display()
877 );
878 return Ok(false);
879 }
880 },
881 StakeTableV2Events::CommissionUpdated(CommissionUpdated {
882 validator,
883 newCommission,
884 ..
885 }) => {
886 if *newCommission > COMMISSION_BASIS_POINTS {
887 return Err(StakeTableError::InvalidCommission(
888 *validator,
889 *newCommission,
890 ));
891 }
892 },
893 _ => {},
894 }
895
896 Ok(true)
897 }
898
899 fn block_range_chunks(
901 from_block: u64,
902 to_block: u64,
903 chunk_size: u64,
904 ) -> impl Iterator<Item = (u64, u64)> {
905 let mut start = from_block;
906 let end = to_block;
907 std::iter::from_fn(move || {
908 let chunk_end = min(start + chunk_size - 1, end);
909 if chunk_end < start {
910 return None;
911 }
912 let chunk = (start, chunk_end);
913 start = chunk_end + 1;
914 Some(chunk)
915 })
916 }
917
918 pub async fn fetch_events_from_contract(
920 l1_client: L1Client,
921 contract: Address,
922 from_block: Option<u64>,
923 to_block: u64,
924 ) -> Result<Vec<(EventKey, StakeTableEvent)>, StakeTableError> {
925 let stake_table_contract = StakeTableV2::new(contract, l1_client.provider.clone());
926 let max_retry_duration = l1_client.options().l1_events_max_retry_duration;
927 let retry_delay = l1_client.options().l1_retry_delay;
928 let from_block = match from_block {
931 Some(block) => block,
932 None => {
933 let start = Instant::now();
934 loop {
935 match stake_table_contract.initializedAtBlock().call().await {
936 Ok(init_block) => break init_block.to::<u64>(),
937 Err(err) => {
938 if start.elapsed() >= max_retry_duration {
939 panic!(
940 "Failed to retrieve initial block after `{}`: {err}",
941 format_duration(max_retry_duration)
942 );
943 }
944 tracing::warn!(%err, "Failed to retrieve initial block, retrying...");
945 sleep(retry_delay).await;
946 },
947 }
948 }
949 },
950 };
951
952 let chunk_size = l1_client.options().l1_events_max_block_range;
956 let chunks = Self::block_range_chunks(from_block, to_block, chunk_size);
957
958 let mut events = vec![];
959
960 for (from, to) in chunks {
961 let provider = l1_client.provider.clone();
962
963 tracing::debug!(from, to, "fetch all stake table events in range");
964 let logs: Vec<Log> = retry(
967 retry_delay,
968 max_retry_duration,
969 "stake table events fetch",
970 move || {
971 let provider = provider.clone();
972
973 Box::pin(async move {
974 let filter = Filter::new()
975 .events([
976 ValidatorRegistered::SIGNATURE,
977 ValidatorRegisteredV2::SIGNATURE,
978 ValidatorExit::SIGNATURE,
979 Delegated::SIGNATURE,
980 Undelegated::SIGNATURE,
981 ConsensusKeysUpdated::SIGNATURE,
982 ConsensusKeysUpdatedV2::SIGNATURE,
983 CommissionUpdated::SIGNATURE,
984 ])
985 .address(contract)
986 .from_block(from)
987 .to_block(to);
988 provider.get_logs(&filter).await
989 })
990 },
991 )
992 .await;
993
994 let chunk_events = logs
995 .into_iter()
996 .filter_map(|log| {
997 let event =
998 StakeTableV2Events::decode_raw_log(log.topics(), &log.data().data).ok()?;
999 match Self::validate_event(&event, &log) {
1000 Ok(true) => Some(Ok((event, log))),
1001 Ok(false) => None,
1002 Err(e) => Some(Err(e)),
1003 }
1004 })
1005 .collect::<Result<Vec<_>, _>>()?;
1006
1007 events.extend(chunk_events);
1008 }
1009
1010 sort_stake_table_events(events).map_err(Into::into)
1011 }
1012
1013 pub async fn fetch_all_validators_from_contract(
1015 l1_client: L1Client,
1016 contract: Address,
1017 to_block: u64,
1018 ) -> anyhow::Result<(ValidatorMap, StakeTableHash)> {
1019 let events = Self::fetch_events_from_contract(l1_client, contract, None, to_block).await?;
1020
1021 validators_from_l1_events(events.into_iter().map(|(_, e)| e))
1023 .context("failed to construct validators set from l1 events")
1024 }
1025
1026 pub async fn fetch_fixed_block_reward(&self) -> Result<RewardAmount, FetchRewardError> {
1030 let initial_supply = *self.initial_supply.read().await;
1032 let initial_supply = match initial_supply {
1033 Some(supply) => supply,
1034 None => self.fetch_and_update_initial_supply().await?,
1035 };
1036
1037 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
1038 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
1039 .ok_or(FetchRewardError::DivisionByZero(
1040 "COMMISSION_BASIS_POINTS is zero",
1041 ))?;
1042
1043 Ok(RewardAmount(reward))
1044 }
1045
1046 pub async fn fetch_and_update_initial_supply(&self) -> Result<U256, FetchRewardError> {
1063 tracing::info!("Fetching token initial supply");
1064 let chain_config = *self.chain_config.lock().await;
1065
1066 let stake_table_contract = chain_config
1067 .stake_table_contract
1068 .ok_or(FetchRewardError::MissingStakeTableContract)?;
1069
1070 let provider = self.l1_client.provider.clone();
1071 let stake_table = StakeTableV2::new(stake_table_contract, provider.clone());
1072
1073 let stake_table_init_block = stake_table
1077 .initializedAtBlock()
1078 .block(BlockId::finalized())
1079 .call()
1080 .await
1081 .map_err(FetchRewardError::ContractCall)?
1082 .to::<u64>();
1083
1084 tracing::info!("stake table init block ={stake_table_init_block}");
1085
1086 let token_address = stake_table
1087 .token()
1088 .block(BlockId::finalized())
1089 .call()
1090 .await
1091 .map_err(FetchRewardError::TokenAddressFetch)?;
1092
1093 let token = EspToken::new(token_address, provider.clone());
1094
1095 let init_logs = token
1102 .Initialized_filter()
1103 .from_block(0u64)
1104 .to_block(BlockNumberOrTag::Finalized)
1105 .query()
1106 .await;
1107
1108 let init_log = match init_logs {
1109 Ok(init_logs) => {
1110 if init_logs.is_empty() {
1111 tracing::error!(
1112 "Token Initialized event logs are empty. This should never happen"
1113 );
1114 return Err(FetchRewardError::MissingInitializedEvent);
1115 }
1116
1117 let (_, init_log) = init_logs[0].clone();
1118
1119 tracing::debug!(tx_hash = ?init_log.transaction_hash, "Found token `Initialized` event");
1120 init_log
1121 },
1122 Err(err) => {
1123 tracing::warn!(
1124 "RPC returned error {err:?}. will fallback to scanning over fixed block range"
1125 );
1126 self.scan_token_contract_initialized_event_log(stake_table_init_block, token)
1127 .await?
1128 },
1129 };
1130
1131 let tx_hash =
1133 init_log
1134 .transaction_hash
1135 .ok_or_else(|| FetchRewardError::MissingTransactionHash {
1136 init_log: init_log.clone().into(),
1137 })?;
1138
1139 let init_tx = provider
1141 .get_transaction_receipt(tx_hash)
1142 .await
1143 .map_err(FetchRewardError::Rpc)?
1144 .ok_or_else(|| FetchRewardError::MissingTransactionReceipt {
1145 tx_hash: tx_hash.to_string(),
1146 })?;
1147
1148 let mint_transfer = init_tx.decoded_log::<EspToken::Transfer>().ok_or(
1149 FetchRewardError::DecodeTransferLog {
1150 tx_hash: tx_hash.to_string(),
1151 },
1152 )?;
1153
1154 tracing::debug!("mint transfer event ={mint_transfer:?}");
1155 if mint_transfer.from != Address::ZERO {
1156 return Err(FetchRewardError::InvalidMintFromAddress);
1157 }
1158
1159 let initial_supply = mint_transfer.value;
1160
1161 tracing::info!("Initial token amount: {} ESP", format_ether(initial_supply));
1162
1163 let mut writer = self.initial_supply.write().await;
1164 *writer = Some(initial_supply);
1165
1166 Ok(initial_supply)
1167 }
1168
1169 pub async fn scan_token_contract_initialized_event_log(
1177 &self,
1178 stake_table_init_block: u64,
1179 token: EspTokenInstance<L1Provider>,
1180 ) -> Result<Log, FetchRewardError> {
1181 let max_events_range = self.l1_client.options().l1_events_max_block_range;
1182 const MAX_BLOCKS_SCANNED: u64 = 200_000;
1183 let mut total_scanned = 0;
1184
1185 let mut from_block = stake_table_init_block.saturating_sub(max_events_range);
1186 let mut to_block = stake_table_init_block;
1187
1188 loop {
1189 if total_scanned >= MAX_BLOCKS_SCANNED {
1190 tracing::error!(
1191 total_scanned,
1192 "Exceeded maximum scan range while searching for token Initialized event"
1193 );
1194 return Err(FetchRewardError::ExceededMaxScanRange(MAX_BLOCKS_SCANNED));
1195 }
1196
1197 let init_logs = token
1198 .Initialized_filter()
1199 .from_block(from_block)
1200 .to_block(to_block)
1201 .query()
1202 .await
1203 .map_err(FetchRewardError::ScanQueryFailed)?;
1204
1205 if !init_logs.is_empty() {
1206 let (_, init_log) = init_logs[0].clone();
1207 tracing::info!(
1208 from_block,
1209 tx_hash = ?init_log.transaction_hash,
1210 "Found token Initialized event during scan"
1211 );
1212 return Ok(init_log);
1213 }
1214
1215 total_scanned += max_events_range;
1216 from_block = from_block.saturating_sub(max_events_range);
1217 to_block = to_block.saturating_sub(max_events_range);
1218 }
1219 }
1220
1221 pub async fn update_chain_config(&self, header: &Header) -> anyhow::Result<()> {
1222 let chain_config = self.get_chain_config(header).await?;
1223 *self.chain_config.lock().await = chain_config;
1225
1226 Ok(())
1227 }
1228
1229 pub async fn fetch(&self, epoch: Epoch, header: &Header) -> anyhow::Result<ValidatorSet> {
1230 let chain_config = *self.chain_config.lock().await;
1231 let Some(address) = chain_config.stake_table_contract else {
1232 bail!("No stake table contract address found in Chain config");
1233 };
1234
1235 let Some(l1_finalized_block_info) = header.l1_finalized() else {
1236 bail!(
1237 "The epoch root for epoch {epoch} is missing the L1 finalized block info. This is \
1238 a fatal error. Consensus is blocked and will not recover."
1239 );
1240 };
1241
1242 let events = match self
1243 .fetch_and_store_stake_table_events(address, l1_finalized_block_info.number())
1244 .await
1245 .map_err(GetStakeTablesError::L1ClientFetchError)
1246 {
1247 Ok(events) => events,
1248 Err(e) => {
1249 bail!("failed to fetch stake table events {e:?}");
1250 },
1251 };
1252
1253 match validator_set_from_l1_events(events.into_iter().map(|(_, e)| e)) {
1254 Ok(res) => Ok(res),
1255 Err(e) => {
1256 bail!("failed to construct stake table {e:?}");
1257 },
1258 }
1259 }
1260
1261 pub(crate) async fn get_chain_config(&self, header: &Header) -> anyhow::Result<ChainConfig> {
1264 let chain_config = self.chain_config.lock().await;
1265 let peers = self.peers.clone();
1266 let header_cf = header.chain_config();
1267 if chain_config.commit() == header_cf.commit() {
1268 return Ok(*chain_config);
1269 }
1270
1271 let cf = match header_cf.resolve() {
1272 Some(cf) => cf,
1273 None => peers
1274 .fetch_chain_config(header_cf.commit())
1275 .await
1276 .inspect_err(|err| {
1277 tracing::error!("failed to get chain_config from peers. err: {err:?}");
1278 })?,
1279 };
1280
1281 Ok(cf)
1282 }
1283
1284 #[cfg(any(test, feature = "testing"))]
1285 pub fn mock() -> Self {
1286 use crate::{mock, v0_1::NoStorage};
1287 let chain_config = ChainConfig::default();
1288 let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
1289 .expect("Failed to create L1 client");
1290
1291 let peers = Arc::new(mock::MockStateCatchup::default());
1292 let persistence = NoStorage;
1293
1294 Self::new(peers, Arc::new(Mutex::new(persistence)), l1, chain_config)
1295 }
1296}
1297
1298async fn retry<F, T, E>(
1299 retry_delay: Duration,
1300 max_duration: Duration,
1301 operation_name: &str,
1302 mut operation: F,
1303) -> T
1304where
1305 F: FnMut() -> BoxFuture<'static, Result<T, E>>,
1306 E: std::fmt::Display,
1307{
1308 let start = Instant::now();
1309 loop {
1310 match operation().await {
1311 Ok(result) => return result,
1312 Err(err) => {
1313 if start.elapsed() >= max_duration {
1314 panic!(
1315 r#"
1316 Failed to complete operation `{operation_name}` after `{}`.
1317 error: {err}
1318
1319
1320 This might be caused by:
1321 - The current block range being too large for your RPC provider.
1322 - The event query returning more data than your RPC allows as
1323 some RPC providers limit the number of events returned.
1324 - RPC provider outage
1325
1326 Suggested solution:
1327 - Reduce the value of the environment variable
1328 `ESPRESSO_SEQUENCER_L1_EVENTS_MAX_BLOCK_RANGE` to query smaller ranges.
1329 - Add multiple RPC providers
1330 - Use a different RPC provider with higher rate limits."#,
1331 format_duration(max_duration)
1332 );
1333 }
1334 tracing::warn!(%err, "Retrying `{operation_name}` after error");
1335 sleep(retry_delay).await;
1336 },
1337 }
1338 }
1339}
1340
1341#[derive(Clone, Debug)]
1343struct NonEpochCommittee {
1344 eligible_leaders: Vec<PeerConfig<SeqTypes>>,
1348
1349 stake_table: Vec<PeerConfig<SeqTypes>>,
1351
1352 da_committee: DaCommittee,
1353
1354 indexed_stake_table: HashMap<PubKey, PeerConfig<SeqTypes>>,
1356}
1357
1358#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
1360pub struct EpochCommittee {
1361 eligible_leaders: Vec<PeerConfig<SeqTypes>>,
1365 stake_table: IndexMap<PubKey, PeerConfig<SeqTypes>>,
1367 validators: ValidatorMap,
1368 address_mapping: HashMap<BLSPubKey, Address>,
1369 block_reward: Option<RewardAmount>,
1370 stake_table_hash: Option<StakeTableHash>,
1371 header: Option<Header>,
1372}
1373
1374impl EpochCommittees {
1375 pub fn first_epoch(&self) -> Option<Epoch> {
1376 self.first_epoch
1377 }
1378
1379 pub fn fetcher(&self) -> &Fetcher {
1380 &self.fetcher
1381 }
1382
1383 pub fn fixed_block_reward(&self) -> Option<RewardAmount> {
1384 self.fixed_block_reward
1385 }
1386
1387 async fn fetch_and_update_fixed_block_reward(
1392 membership: Arc<RwLock<Self>>,
1393 epoch: EpochNumber,
1394 ) -> anyhow::Result<RewardAmount> {
1395 let membership_reader = membership.upgradable_read().await;
1396 let fetcher = membership_reader.fetcher.clone();
1397 match membership_reader.fixed_block_reward {
1398 Some(reward) => Ok(reward),
1399 None => {
1400 tracing::warn!(%epoch,
1401 "Block reward is None. attempting to fetch it from L1",
1402
1403 );
1404 let block_reward = fetcher
1405 .fetch_fixed_block_reward()
1406 .await
1407 .inspect_err(|err| {
1408 tracing::error!(?epoch, ?err, "failed to fetch block_reward");
1409 })?;
1410 let mut writer = RwLockUpgradableReadGuard::upgrade(membership_reader).await;
1411 writer.fixed_block_reward = Some(block_reward);
1412 Ok(block_reward)
1413 },
1414 }
1415 }
1416
1417 pub fn compute_block_reward(
1418 epoch: &EpochNumber,
1419 total_supply: U256,
1420 total_stake: U256,
1421 avg_block_time_ms: u64,
1422 ) -> anyhow::Result<RewardAmount> {
1423 let total_stake_bd = BigDecimal::from_str(&total_stake.to_string())?;
1425 let total_supply_bd = BigDecimal::from_str(&(total_supply.to_string()))?;
1426
1427 tracing::debug!(?epoch, "total_stake={total_stake}");
1428 tracing::debug!(?epoch, "total_supply_bd={total_supply_bd}");
1429
1430 let (proportion, reward_rate) =
1431 calculate_proportion_staked_and_reward_rate(&total_stake_bd, &total_supply_bd)?;
1432 let inflation_rate = proportion * reward_rate;
1433
1434 tracing::debug!(?epoch, "inflation_rate={inflation_rate:?}");
1435
1436 let blocks_per_year = MILLISECONDS_PER_YEAR
1437 .checked_div(avg_block_time_ms.into())
1438 .context("avg_block_time_ms is zero")?;
1439
1440 tracing::debug!(?epoch, "blocks_per_year={blocks_per_year:?}");
1441
1442 ensure!(!blocks_per_year.is_zero(), "blocks per year is zero");
1443 let block_reward = (total_supply_bd * inflation_rate) / blocks_per_year;
1444
1445 let block_reward_u256 = U256::from_str(&block_reward.round(0).to_string())?;
1446
1447 Ok(block_reward_u256.into())
1448 }
1449
1450 pub async fn fetch_and_calculate_block_reward(
1460 current_epoch: Epoch,
1461 coordinator: EpochMembershipCoordinator<SeqTypes>,
1462 ) -> anyhow::Result<RewardAmount> {
1463 let membership_read = coordinator.membership().read().await;
1464 let epoch_height = membership_read.epoch_height;
1465 let fixed_block_reward = membership_read.fixed_block_reward;
1466
1467 let committee = membership_read
1468 .state
1469 .get(¤t_epoch)
1470 .context(format!("committee not found for epoch={current_epoch:?}"))?
1471 .clone();
1472
1473 if let Some(reward) = committee.block_reward {
1475 return Ok(reward);
1476 }
1477
1478 let first_epoch = *membership_read.first_epoch().context(format!(
1479 "First epoch not initialized (current_epoch={current_epoch})"
1480 ))?;
1481
1482 drop(membership_read);
1483
1484 if *current_epoch <= first_epoch + 1 {
1485 bail!(
1486 "epoch is in first two epochs: current_epoch={current_epoch}, \
1487 first_epoch={first_epoch}"
1488 );
1489 }
1490
1491 let header = match committee.header.clone() {
1492 Some(header) => header,
1493 None => {
1494 let root_epoch = current_epoch.checked_sub(2).context(format!(
1495 "Epoch calculation underflow (current_epoch={current_epoch})"
1496 ))?;
1497
1498 tracing::info!(?root_epoch, "catchup epoch root header");
1499
1500 let membership = coordinator.membership();
1501 let leaf = Self::get_epoch_root(
1502 membership.clone(),
1503 root_block_in_epoch(root_epoch, epoch_height),
1504 current_epoch,
1505 )
1506 .await
1507 .with_context(|| format!("Failed to get epoch root for root_epoch={root_epoch}"))?;
1508 leaf.block_header().clone()
1509 },
1510 };
1511
1512 if header.version() <= EpochVersion::version() {
1513 return fixed_block_reward.context(format!(
1514 "Fixed block reward not found for current_epoch={current_epoch}"
1515 ));
1516 }
1517
1518 let prev_epoch_u64 = current_epoch.checked_sub(1).context(format!(
1519 "Underflow: cannot compute previous epoch when current_epoch={current_epoch}"
1520 ))?;
1521
1522 let prev_epoch = EpochNumber::new(prev_epoch_u64);
1523
1524 if *prev_epoch > first_epoch + 1 {
1527 if let Err(err) = coordinator.stake_table_for_epoch(Some(prev_epoch)).await {
1528 tracing::info!("failed to get membership for epoch={prev_epoch:?}: {err:#}");
1529
1530 coordinator
1531 .wait_for_catchup(prev_epoch)
1532 .await
1533 .context(format!("failed to catch up for epoch={prev_epoch}"))?;
1534 }
1535 }
1536
1537 let membership_read = coordinator.membership().read().await;
1538
1539 membership_read
1540 .calculate_dynamic_block_reward(¤t_epoch, &header, &committee.validators)
1541 .await
1542 .with_context(|| {
1543 format!("dynamic block reward calculation failed for epoch={current_epoch}")
1544 })?
1545 .with_context(|| format!("dynamic block reward returned None. epoch={current_epoch}"))
1546 }
1547
1548 async fn calculate_dynamic_block_reward(
1555 &self,
1556 epoch: &Epoch,
1557 header: &Header,
1558 validators: &ValidatorMap,
1559 ) -> anyhow::Result<Option<RewardAmount>> {
1560 let epoch_height = self.epoch_height;
1561 let current_epoch = epoch_from_block_number(header.height(), epoch_height);
1562 let previous_epoch = current_epoch
1563 .checked_sub(1)
1564 .context("underflow: cannot get previous epoch when current_epoch is 0")?;
1565 tracing::debug!(?epoch, "previous_epoch={previous_epoch:?}");
1566
1567 let first_epoch = *self.first_epoch().context("first epoch is None")?;
1568
1569 if previous_epoch > first_epoch + 1
1572 && !self.has_stake_table(EpochNumber::new(previous_epoch))
1573 {
1574 tracing::warn!(?previous_epoch, "missing stake table for previous epoch");
1575 return Ok(None);
1576 }
1577
1578 let fetcher = self.fetcher.clone();
1579
1580 let previous_reward_distributed = header
1581 .total_reward_distributed()
1582 .context("Invalid block header: missing total_reward_distributed field")?;
1583
1584 let total_stake: U256 = validators.values().map(|v| v.stake).sum();
1586 let initial_supply = *fetcher.initial_supply.read().await;
1587 let initial_supply = match initial_supply {
1588 Some(supply) => supply,
1589 None => fetcher.fetch_and_update_initial_supply().await?,
1590 };
1591 let total_supply = initial_supply
1592 .checked_add(previous_reward_distributed.0)
1593 .context("initial_supply + previous_reward_distributed overflow")?;
1594
1595 let curr_ts = header.timestamp_millis_internal();
1597 tracing::debug!(?epoch, "curr_ts={curr_ts:?}");
1598
1599 let average_block_time_ms = if previous_epoch <= first_epoch + 1 {
1603 ASSUMED_BLOCK_TIME_SECONDS as u64 * 1000 } else {
1605 let prev_stake_table = self
1606 .get_stake_table(&Some(EpochNumber::new(previous_epoch)))
1607 .context("Stake table not found")?
1608 .into();
1609
1610 let success_threshold = self.success_threshold(Some(*epoch));
1611
1612 let root_height = header.height().checked_sub(epoch_height).context(
1613 "Epoch height is greater than block height. cannot compute previous epoch root \
1614 height",
1615 )?;
1616
1617 let prev_root = fetcher
1618 .peers
1619 .fetch_leaf(root_height, prev_stake_table, success_threshold)
1620 .await
1621 .context("Epoch root leaf not found")?;
1622
1623 let prev_ts = prev_root.block_header().timestamp_millis_internal();
1624 let time_diff = curr_ts.checked_sub(prev_ts).context(
1625 "Current timestamp is earlier than previous. underflow in block time calculation",
1626 )?;
1627
1628 time_diff
1629 .checked_div(epoch_height)
1630 .context("Epoch height is zero. cannot compute average block time")?
1631 };
1632 tracing::info!(?epoch, %total_supply, %total_stake, %average_block_time_ms,
1633 "dynamic block reward parameters");
1634
1635 let block_reward =
1636 Self::compute_block_reward(epoch, total_supply, total_stake, average_block_time_ms)?;
1637
1638 Ok(Some(block_reward))
1639 }
1640
1641 pub fn epoch_block_reward(&self, epoch: EpochNumber) -> Option<RewardAmount> {
1643 self.state
1644 .get(&epoch)
1645 .and_then(|committee| committee.block_reward)
1646 }
1647 fn insert_committee(
1653 &mut self,
1654 epoch: EpochNumber,
1655 validators: ValidatorMap,
1656 block_reward: Option<RewardAmount>,
1657 hash: Option<StakeTableHash>,
1658 header: Option<Header>,
1659 ) {
1660 let mut address_mapping = HashMap::new();
1661 let stake_table: IndexMap<PubKey, PeerConfig<SeqTypes>> = validators
1662 .values()
1663 .map(|v| {
1664 address_mapping.insert(v.stake_table_key, v.account);
1665 (
1666 v.stake_table_key,
1667 PeerConfig {
1668 stake_table_entry: BLSPubKey::stake_table_entry(
1669 &v.stake_table_key,
1670 v.stake,
1671 ),
1672 state_ver_key: v.state_ver_key.clone(),
1673 },
1674 )
1675 })
1676 .collect();
1677
1678 let eligible_leaders: Vec<PeerConfig<SeqTypes>> =
1679 stake_table.iter().map(|(_, l)| l.clone()).collect();
1680
1681 self.state.insert(
1682 epoch,
1683 EpochCommittee {
1684 eligible_leaders,
1685 stake_table,
1686 validators,
1687 address_mapping,
1688 block_reward,
1689 stake_table_hash: hash,
1690 header,
1691 },
1692 );
1693 }
1694
1695 pub fn active_validators(&self, epoch: &Epoch) -> anyhow::Result<ValidatorMap> {
1696 Ok(self
1697 .state
1698 .get(epoch)
1699 .context("state for found")?
1700 .validators
1701 .clone())
1702 }
1703
1704 pub fn address(&self, epoch: &Epoch, bls_key: BLSPubKey) -> anyhow::Result<Address> {
1705 let mapping = self
1706 .state
1707 .get(epoch)
1708 .context("state for found")?
1709 .address_mapping
1710 .clone();
1711
1712 Ok(*mapping.get(&bls_key).context(format!(
1713 "failed to get ethereum address for bls key {bls_key}. epoch={epoch}"
1714 ))?)
1715 }
1716
1717 pub fn get_validator_config(
1718 &self,
1719 epoch: &Epoch,
1720 key: BLSPubKey,
1721 ) -> anyhow::Result<Validator<BLSPubKey>> {
1722 let address = self.address(epoch, key)?;
1723 let validators = self.active_validators(epoch)?;
1724 validators
1725 .get(&address)
1726 .context("validator not found")
1727 .cloned()
1728 }
1729
1730 pub fn new_stake(
1732 committee_members: Vec<PeerConfig<SeqTypes>>,
1735 da_members: Vec<PeerConfig<SeqTypes>>,
1736 fixed_block_reward: Option<RewardAmount>,
1737 fetcher: Fetcher,
1738 epoch_height: u64,
1739 ) -> Self {
1740 let stake_table: Vec<_> = committee_members
1742 .iter()
1743 .filter(|&peer_config| peer_config.stake_table_entry.stake() > U256::ZERO)
1744 .cloned()
1745 .collect();
1746
1747 let eligible_leaders = stake_table.clone();
1748 let da_members: Vec<_> = da_members
1750 .iter()
1751 .filter(|&peer_config| peer_config.stake_table_entry.stake() > U256::ZERO)
1752 .cloned()
1753 .collect();
1754
1755 let indexed_stake_table: HashMap<PubKey, _> = stake_table
1757 .iter()
1758 .map(|peer_config| {
1759 (
1760 PubKey::public_key(&peer_config.stake_table_entry),
1761 peer_config.clone(),
1762 )
1763 })
1764 .collect();
1765
1766 let indexed_da_members: HashMap<PubKey, _> = da_members
1768 .iter()
1769 .map(|peer_config| {
1770 (
1771 PubKey::public_key(&peer_config.stake_table_entry),
1772 peer_config.clone(),
1773 )
1774 })
1775 .collect();
1776
1777 let da_committee = DaCommittee {
1778 committee: da_members,
1779 indexed_committee: indexed_da_members,
1780 };
1781
1782 let members = NonEpochCommittee {
1783 eligible_leaders,
1784 stake_table,
1785 indexed_stake_table,
1786 da_committee,
1787 };
1788
1789 let mut map = HashMap::new();
1790 let epoch_committee = EpochCommittee {
1791 eligible_leaders: members.eligible_leaders.clone(),
1792 stake_table: members
1793 .stake_table
1794 .iter()
1795 .map(|x| (PubKey::public_key(&x.stake_table_entry), x.clone()))
1796 .collect(),
1797 validators: Default::default(),
1798 address_mapping: HashMap::new(),
1799 block_reward: Default::default(),
1800 stake_table_hash: None,
1801 header: None,
1802 };
1803 map.insert(Epoch::genesis(), epoch_committee.clone());
1804 map.insert(Epoch::genesis() + 1u64, epoch_committee.clone());
1806
1807 Self {
1808 non_epoch_committee: members,
1809 da_committees: BTreeMap::new(),
1810 state: map,
1811 randomized_committees: BTreeMap::new(),
1812 first_epoch: None,
1813 fixed_block_reward,
1814 fetcher: Arc::new(fetcher),
1815 epoch_height,
1816 }
1817 }
1818
1819 pub async fn reload_stake(&mut self, limit: u64) {
1820 match self.fetcher.fetch_fixed_block_reward().await {
1821 Ok(block_reward) => {
1822 tracing::info!("Fetched block reward: {block_reward}");
1823 self.fixed_block_reward = Some(block_reward);
1824 },
1825 Err(err) => {
1826 tracing::warn!(
1827 "Failed to fetch the block reward when reloading the stake tables: {err}"
1828 );
1829 },
1830 }
1831
1832 let loaded_stake = match self
1834 .fetcher
1835 .persistence
1836 .lock()
1837 .await
1838 .load_latest_stake(limit)
1839 .await
1840 {
1841 Ok(Some(loaded)) => loaded,
1842 Ok(None) => {
1843 tracing::warn!("No stake table history found in persistence!");
1844 return;
1845 },
1846 Err(e) => {
1847 tracing::error!("Failed to load stake table history from persistence: {e}");
1848 return;
1849 },
1850 };
1851
1852 for (epoch, (stake_table, block_reward), stake_table_hash) in loaded_stake {
1853 self.insert_committee(epoch, stake_table, block_reward, stake_table_hash, None);
1854 }
1855 }
1856
1857 fn get_stake_table(&self, epoch: &Option<Epoch>) -> Option<Vec<PeerConfig<SeqTypes>>> {
1858 if let Some(epoch) = epoch {
1859 self.state
1860 .get(epoch)
1861 .map(|committee| committee.stake_table.clone().into_values().collect())
1862 } else {
1863 Some(self.non_epoch_committee.stake_table.clone())
1864 }
1865 }
1866
1867 fn get_da_committee(&self, epoch: Option<Epoch>) -> DaCommittee {
1868 if let Some(e) = epoch {
1869 self.da_committees
1871 .range((Bound::Included(&0), Bound::Included(&*e)))
1872 .last()
1873 .map(|(_, committee)| committee.clone())
1874 .unwrap_or(self.non_epoch_committee.da_committee.clone())
1875 } else {
1876 self.non_epoch_committee.da_committee.clone()
1877 }
1878 }
1879}
1880
1881fn calculate_proportion_staked_and_reward_rate(
1891 total_stake: &BigDecimal,
1892 total_supply: &BigDecimal,
1893) -> anyhow::Result<(BigDecimal, BigDecimal)> {
1894 if total_supply.is_zero() {
1895 return Err(anyhow::anyhow!("Total supply cannot be zero"));
1896 }
1897
1898 let proportion_staked = total_stake / total_supply;
1899
1900 if proportion_staked < BigDecimal::from(0) || proportion_staked > BigDecimal::from(1) {
1901 return Err(anyhow::anyhow!("Stake ratio p must be in the range [0, 1]"));
1902 }
1903
1904 let two = BigDecimal::from_u32(2).unwrap();
1905 let min_stake_ratio = BigDecimal::from_str("0.01")?;
1906 let numerator = BigDecimal::from_str("0.03")?;
1907
1908 let denominator = (&two * (&proportion_staked).max(&min_stake_ratio))
1909 .sqrt()
1910 .context("Failed to compute sqrt in R(p)")?;
1911
1912 let reward_rate = numerator / denominator;
1913
1914 tracing::debug!("rp={reward_rate}");
1915
1916 Ok((proportion_staked, reward_rate))
1917}
1918
1919#[derive(Error, Debug)]
1920enum GetStakeTablesError {
1922 #[error("Error fetching from L1: {0}")]
1923 L1ClientFetchError(anyhow::Error),
1924}
1925
1926#[derive(Error, Debug)]
1927#[error("Could not lookup leader")] pub struct LeaderLookupError;
1929
1930impl Membership<SeqTypes> for EpochCommittees {
1932 type Error = LeaderLookupError;
1933 type Storage = ();
1934 type StakeTableHash = StakeTableState;
1935
1936 fn new<I: NodeImplementation<SeqTypes>>(
1938 _committee_members: Vec<PeerConfig<SeqTypes>>,
1941 _da_members: Vec<PeerConfig<SeqTypes>>,
1942 _storage: Self::Storage,
1943 _network: Arc<<I as NodeImplementation<SeqTypes>>::Network>,
1944 _public_key: <SeqTypes as NodeType>::SignatureKey,
1945 _epoch_height: u64,
1946 ) -> Self {
1947 panic!("This function has been replaced with new_stake()");
1948 }
1949
1950 fn stake_table(&self, epoch: Option<Epoch>) -> HSStakeTable<SeqTypes> {
1952 self.get_stake_table(&epoch).unwrap_or_default().into()
1953 }
1954 fn da_stake_table(&self, epoch: Option<Epoch>) -> HSStakeTable<SeqTypes> {
1956 self.get_da_committee(epoch).committee.clone().into()
1957 }
1958
1959 fn committee_members(
1961 &self,
1962 _view_number: <SeqTypes as NodeType>::View,
1963 epoch: Option<Epoch>,
1964 ) -> BTreeSet<PubKey> {
1965 let stake_table = self.stake_table(epoch);
1966 stake_table
1967 .iter()
1968 .map(|x| PubKey::public_key(&x.stake_table_entry))
1969 .collect()
1970 }
1971
1972 fn da_committee_members(
1974 &self,
1975 _view_number: <SeqTypes as NodeType>::View,
1976 epoch: Option<Epoch>,
1977 ) -> BTreeSet<PubKey> {
1978 self.da_stake_table(epoch)
1979 .iter()
1980 .map(|peer_config| PubKey::public_key(&peer_config.stake_table_entry))
1981 .collect()
1982 }
1983
1984 fn stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> Option<PeerConfig<SeqTypes>> {
1986 if let Some(epoch) = epoch {
1988 self.state
1989 .get(&epoch)
1990 .and_then(|h| h.stake_table.get(pub_key))
1991 .cloned()
1992 } else {
1993 self.non_epoch_committee
1994 .indexed_stake_table
1995 .get(pub_key)
1996 .cloned()
1997 }
1998 }
1999
2000 fn da_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> Option<PeerConfig<SeqTypes>> {
2002 self.get_da_committee(epoch)
2003 .indexed_committee
2004 .get(pub_key)
2005 .cloned()
2006 }
2007
2008 fn has_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> bool {
2010 self.stake(pub_key, epoch)
2011 .map(|x| x.stake_table_entry.stake() > U256::ZERO)
2012 .unwrap_or_default()
2013 }
2014
2015 fn has_da_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> bool {
2017 self.da_stake(pub_key, epoch)
2018 .map(|x| x.stake_table_entry.stake() > U256::ZERO)
2019 .unwrap_or_default()
2020 }
2021
2022 fn lookup_leader(
2035 &self,
2036 view_number: <SeqTypes as NodeType>::View,
2037 epoch: Option<Epoch>,
2038 ) -> Result<PubKey, Self::Error> {
2039 match (self.first_epoch(), epoch) {
2040 (Some(first_epoch), Some(epoch)) => {
2041 if epoch < first_epoch {
2042 tracing::error!(
2043 "lookup_leader called with epoch {} before first epoch {}",
2044 epoch,
2045 first_epoch,
2046 );
2047 return Err(LeaderLookupError);
2048 }
2049 let Some(randomized_committee) = self.randomized_committees.get(&epoch) else {
2050 tracing::error!(
2051 "We are missing the randomized committee for epoch {}",
2052 epoch
2053 );
2054 return Err(LeaderLookupError);
2055 };
2056
2057 Ok(PubKey::public_key(&select_randomized_leader(
2058 randomized_committee,
2059 *view_number,
2060 )))
2061 },
2062 (_, None) => {
2063 let leaders = &self.non_epoch_committee.eligible_leaders;
2064
2065 let index = *view_number as usize % leaders.len();
2066 let res = leaders[index].clone();
2067 Ok(PubKey::public_key(&res.stake_table_entry))
2068 },
2069 (None, Some(epoch)) => {
2070 tracing::error!(
2071 "lookup_leader called with epoch {} but we don't have a first epoch",
2072 epoch,
2073 );
2074 Err(LeaderLookupError)
2075 },
2076 }
2077 }
2078
2079 fn total_nodes(&self, epoch: Option<Epoch>) -> usize {
2081 self.stake_table(epoch).len()
2082 }
2083
2084 fn da_total_nodes(&self, epoch: Option<Epoch>) -> usize {
2086 self.da_stake_table(epoch).len()
2087 }
2088
2089 async fn add_epoch_root(
2093 membership: Arc<RwLock<Self>>,
2094 epoch: Epoch,
2095 block_header: Header,
2096 ) -> anyhow::Result<()> {
2097 tracing::info!(
2098 ?epoch,
2099 "adding epoch root. height={:?}",
2100 block_header.height()
2101 );
2102
2103 let fetcher = { membership.read().await.fetcher.clone() };
2104 let version = block_header.version();
2105 fetcher.update_chain_config(&block_header).await?;
2107
2108 let mut block_reward = None;
2109 if version == EpochVersion::version() {
2112 let reward =
2113 Self::fetch_and_update_fixed_block_reward(membership.clone(), epoch).await?;
2114 block_reward = Some(reward);
2115 }
2116
2117 let epoch_committee = {
2118 let membership_reader = membership.read().await;
2119 membership_reader.state.get(&epoch).cloned()
2120 };
2121
2122 let (active_validators, all_validators, stake_table_hash) = match epoch_committee {
2128 Some(committee)
2129 if committee.block_reward.is_some()
2130 && committee.header.is_some()
2131 && committee.stake_table_hash.is_some() =>
2132 {
2133 tracing::info!(
2134 ?epoch,
2135 "committee already has block reward, header, and stake table hash; skipping \
2136 add_epoch_root"
2137 );
2138 return Ok(());
2139 },
2140
2141 Some(committee) => {
2142 if let Some(reward) = committee.block_reward {
2143 block_reward = Some(reward);
2144 }
2145
2146 if let Some(hash) = committee.stake_table_hash {
2147 (committee.validators.clone(), Default::default(), Some(hash))
2148 } else {
2149 tracing::info!(
2151 "Stake table hash missing for epoch {epoch}. recalculating by fetching \
2152 from l1."
2153 );
2154 let set = fetcher.fetch(epoch, &block_header).await?;
2155 (
2156 set.active_validators,
2157 set.all_validators,
2158 set.stake_table_hash,
2159 )
2160 }
2161 },
2162
2163 None => {
2164 tracing::info!("Stake table missing for epoch {epoch}. Fetching from L1.");
2165 let set = fetcher.fetch(epoch, &block_header).await?;
2166 (
2167 set.active_validators,
2168 set.all_validators,
2169 set.stake_table_hash,
2170 )
2171 },
2172 };
2173
2174 if block_reward.is_none() && version >= DrbAndHeaderUpgradeVersion::version() {
2178 tracing::info!(?epoch, "calculating dynamic block reward");
2179 let reader = membership.read().await;
2180 let reward = reader
2181 .calculate_dynamic_block_reward(&epoch, &block_header, &active_validators)
2182 .await?;
2183
2184 tracing::info!(?epoch, "calculated dynamic block reward = {reward:?}");
2185 block_reward = reward;
2186 }
2187
2188 let mut membership_writer = membership.write().await;
2189 membership_writer.insert_committee(
2190 epoch,
2191 active_validators.clone(),
2192 block_reward,
2193 stake_table_hash,
2194 Some(block_header),
2195 );
2196 drop(membership_writer);
2197
2198 let persistence_lock = fetcher.persistence.lock().await;
2199 if let Err(e) = persistence_lock
2200 .store_stake(epoch, active_validators, block_reward, stake_table_hash)
2201 .await
2202 {
2203 tracing::error!(?e, ?epoch, "`add_epoch_root`, error storing stake table");
2204 }
2205
2206 if let Err(e) = persistence_lock
2207 .store_all_validators(epoch, all_validators)
2208 .await
2209 {
2210 tracing::error!(?e, ?epoch, "`add_epoch_root`, error storing all validators");
2211 }
2212
2213 Ok(())
2214 }
2215
2216 fn has_stake_table(&self, epoch: Epoch) -> bool {
2217 self.state.contains_key(&epoch)
2218 }
2219
2220 fn has_randomized_stake_table(&self, epoch: Epoch) -> anyhow::Result<bool> {
2232 let Some(first_epoch) = self.first_epoch else {
2233 bail!(
2234 "Called has_randomized_stake_table with epoch {} but first_epoch is None",
2235 epoch
2236 );
2237 };
2238 ensure!(
2239 epoch >= first_epoch,
2240 "Called has_randomized_stake_table with epoch {} but first_epoch is {}",
2241 epoch,
2242 first_epoch
2243 );
2244 Ok(self.randomized_committees.contains_key(&epoch))
2245 }
2246
2247 async fn get_epoch_root(
2248 membership: Arc<RwLock<Self>>,
2249 block_height: u64,
2250 epoch: Epoch,
2251 ) -> anyhow::Result<Leaf2> {
2252 let membership_reader = membership.read().await;
2253 let peers = membership_reader.fetcher.peers.clone();
2254 let stake_table = membership_reader.stake_table(Some(epoch)).clone();
2255 let success_threshold = membership_reader.success_threshold(Some(epoch));
2256 drop(membership_reader);
2257
2258 let leaf: Leaf2 = peers
2260 .fetch_leaf(block_height, stake_table.clone(), success_threshold)
2261 .await?;
2262
2263 Ok(leaf)
2264 }
2265
2266 async fn get_epoch_drb(
2267 membership: Arc<RwLock<Self>>,
2268 epoch: Epoch,
2269 ) -> anyhow::Result<DrbResult> {
2270 let membership_reader = membership.read().await;
2271 let peers = membership_reader.fetcher.peers.clone();
2272
2273 if let Some(randomized_committee) = membership_reader.randomized_committees.get(&epoch) {
2275 return Ok(randomized_committee.drb_result());
2276 }
2277
2278 let previous_epoch = match epoch.checked_sub(1) {
2280 Some(epoch) => EpochNumber::new(epoch),
2281 None => {
2282 return membership_reader
2283 .randomized_committees
2284 .get(&epoch)
2285 .map(|committee| committee.drb_result())
2286 .context(format!("Missing randomized committee for epoch {epoch}"))
2287 },
2288 };
2289
2290 let stake_table = membership_reader.stake_table(Some(previous_epoch)).clone();
2291 let success_threshold = membership_reader.success_threshold(Some(previous_epoch));
2292
2293 let block_height =
2294 transition_block_for_epoch(*previous_epoch, membership_reader.epoch_height);
2295
2296 drop(membership_reader);
2297
2298 tracing::debug!(
2299 "Getting DRB for epoch {}, block height {}",
2300 epoch,
2301 block_height
2302 );
2303 let drb_leaf = peers
2304 .try_fetch_leaf(1, block_height, stake_table, success_threshold)
2305 .await?;
2306
2307 let Some(drb) = drb_leaf.next_drb_result else {
2308 tracing::error!(
2309 "We received a leaf that should contain a DRB result, but the DRB result is \
2310 missing: {:?}",
2311 drb_leaf
2312 );
2313
2314 bail!("DRB leaf is missing the DRB result.");
2315 };
2316
2317 Ok(drb)
2318 }
2319
2320 fn add_drb_result(&mut self, epoch: Epoch, drb: DrbResult) {
2321 let Some(raw_stake_table) = self.state.get(&epoch) else {
2322 tracing::error!(
2323 "add_drb_result({epoch}, {drb:?}) was called, but we do not yet have the stake \
2324 table for epoch {epoch}"
2325 );
2326 return;
2327 };
2328
2329 let leaders = raw_stake_table
2330 .eligible_leaders
2331 .clone()
2332 .into_iter()
2333 .map(|peer_config| peer_config.stake_table_entry)
2334 .collect::<Vec<_>>();
2335 let randomized_committee = generate_stake_cdf(leaders, drb);
2336
2337 self.randomized_committees
2338 .insert(epoch, randomized_committee);
2339 }
2340
2341 fn set_first_epoch(&mut self, epoch: Epoch, initial_drb_result: DrbResult) {
2342 self.first_epoch = Some(epoch);
2343
2344 let epoch_committee = self.state.get(&Epoch::genesis()).unwrap().clone();
2345 self.state.insert(epoch, epoch_committee.clone());
2346 self.state.insert(epoch + 1, epoch_committee);
2347 self.add_drb_result(epoch, initial_drb_result);
2348 self.add_drb_result(epoch + 1, initial_drb_result);
2349 }
2350
2351 fn first_epoch(&self) -> Option<<SeqTypes as NodeType>::Epoch> {
2352 self.first_epoch
2353 }
2354
2355 fn stake_table_hash(&self, epoch: Epoch) -> Option<StakeTableHash> {
2356 let committee = self.state.get(&epoch)?;
2357 committee.stake_table_hash
2358 }
2359
2360 fn add_da_committee(&mut self, first_epoch: u64, committee: Vec<PeerConfig<SeqTypes>>) {
2361 let indexed_committee: HashMap<PubKey, _> = committee
2362 .iter()
2363 .map(|peer_config| {
2364 (
2365 PubKey::public_key(&peer_config.stake_table_entry),
2366 peer_config.clone(),
2367 )
2368 })
2369 .collect();
2370
2371 let da_committee = DaCommittee {
2372 committee,
2373 indexed_committee,
2374 };
2375
2376 self.da_committees.insert(first_epoch, da_committee);
2377 }
2378}
2379
2380#[cfg(any(test, feature = "testing"))]
2381impl super::v0_3::StakeTable {
2382 pub fn mock(n: u64) -> Self {
2384 [..n]
2385 .iter()
2386 .map(|_| PeerConfig::default())
2387 .collect::<Vec<PeerConfig<SeqTypes>>>()
2388 .into()
2389 }
2390}
2391
2392#[cfg(any(test, feature = "testing"))]
2393impl DAMembers {
2394 pub fn mock(n: u64) -> Self {
2396 [..n]
2397 .iter()
2398 .map(|_| PeerConfig::default())
2399 .collect::<Vec<PeerConfig<SeqTypes>>>()
2400 .into()
2401 }
2402}
2403
2404#[cfg(any(test, feature = "testing"))]
2405pub mod testing {
2406 use alloy::primitives::Bytes;
2407 use hotshot_contract_adapter::{
2408 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
2409 stake_table::{sign_address_bls, sign_address_schnorr, StateSignatureSol},
2410 };
2411 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
2412 use rand::{Rng as _, RngCore as _};
2413
2414 use super::*;
2415
2416 #[derive(Debug, Clone)]
2419 pub struct TestValidator {
2420 pub account: Address,
2421 pub bls_vk: G2PointSol,
2422 pub schnorr_vk: EdOnBN254PointSol,
2423 pub commission: u16,
2424 pub bls_sig: G1PointSol,
2425 pub schnorr_sig: Bytes,
2426 }
2427
2428 impl TestValidator {
2429 pub fn random() -> Self {
2430 let account = Address::random();
2431 let commission = rand::thread_rng().gen_range(0..10000);
2432 Self::random_update_keys(account, commission)
2433 }
2434
2435 pub fn randomize_keys(&self) -> Self {
2436 Self::random_update_keys(self.account, self.commission)
2437 }
2438
2439 pub fn random_update_keys(account: Address, commission: u16) -> Self {
2440 let mut rng = &mut rand::thread_rng();
2441 let mut seed = [0u8; 32];
2442 rng.fill_bytes(&mut seed);
2443 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2444 let bls_sig = sign_address_bls(&bls_key_pair, account);
2445 let schnorr_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
2446 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, account);
2447 Self {
2448 account,
2449 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2450 schnorr_vk: schnorr_key_pair.ver_key().to_affine().into(),
2451 commission,
2452 bls_sig: bls_sig.into(),
2453 schnorr_sig: StateSignatureSol::from(schnorr_sig).into(),
2454 }
2455 }
2456 }
2457
2458 impl From<&TestValidator> for ValidatorRegistered {
2459 fn from(value: &TestValidator) -> Self {
2460 Self {
2461 account: value.account,
2462 blsVk: value.bls_vk,
2463 schnorrVk: value.schnorr_vk,
2464 commission: value.commission,
2465 }
2466 }
2467 }
2468
2469 impl From<&TestValidator> for ValidatorRegisteredV2 {
2470 fn from(value: &TestValidator) -> Self {
2471 Self {
2472 account: value.account,
2473 blsVK: value.bls_vk,
2474 schnorrVK: value.schnorr_vk,
2475 commission: value.commission,
2476 blsSig: value.bls_sig.into(),
2477 schnorrSig: value.schnorr_sig.clone(),
2478 }
2479 }
2480 }
2481
2482 impl From<&TestValidator> for ConsensusKeysUpdated {
2483 fn from(value: &TestValidator) -> Self {
2484 Self {
2485 account: value.account,
2486 blsVK: value.bls_vk,
2487 schnorrVK: value.schnorr_vk,
2488 }
2489 }
2490 }
2491
2492 impl From<&TestValidator> for ConsensusKeysUpdatedV2 {
2493 fn from(value: &TestValidator) -> Self {
2494 Self {
2495 account: value.account,
2496 blsVK: value.bls_vk,
2497 schnorrVK: value.schnorr_vk,
2498 blsSig: value.bls_sig.into(),
2499 schnorrSig: value.schnorr_sig.clone(),
2500 }
2501 }
2502 }
2503
2504 impl From<&TestValidator> for ValidatorExit {
2505 fn from(value: &TestValidator) -> Self {
2506 Self {
2507 validator: value.account,
2508 }
2509 }
2510 }
2511
2512 impl Validator<BLSPubKey> {
2513 pub fn mock() -> Validator<BLSPubKey> {
2514 let val = TestValidator::random();
2515 let rng = &mut rand::thread_rng();
2516 let mut seed = [1u8; 32];
2517 rng.fill_bytes(&mut seed);
2518 let mut validator_stake = alloy::primitives::U256::from(0);
2519 let mut delegators = HashMap::new();
2520 for _i in 0..=5000 {
2521 let stake: u64 = rng.gen_range(0..10000);
2522 delegators.insert(Address::random(), alloy::primitives::U256::from(stake));
2523 validator_stake += alloy::primitives::U256::from(stake);
2524 }
2525
2526 let stake_table_key = val.bls_vk.into();
2527 let state_ver_key = val.schnorr_vk.into();
2528
2529 Validator {
2530 account: val.account,
2531 stake_table_key,
2532 state_ver_key,
2533 stake: validator_stake,
2534 commission: val.commission,
2535 delegators,
2536 }
2537 }
2538 }
2539}
2540
2541#[cfg(test)]
2542mod tests {
2543
2544 use alloy::{primitives::Address, rpc::types::Log};
2545 use hotshot_contract_adapter::stake_table::{sign_address_bls, StakeTableContractVersion};
2546 use hotshot_types::signature_key::BLSKeyPair;
2547 use pretty_assertions::assert_matches;
2548 use rstest::rstest;
2549
2550 use super::*;
2551 use crate::{v0::impls::testing::*, L1ClientOptions};
2552
2553 #[test_log::test]
2554 fn test_from_l1_events() -> anyhow::Result<()> {
2555 let val_1 = TestValidator::random();
2557 let val_1_new_keys = val_1.randomize_keys();
2558 let val_2 = TestValidator::random();
2559 let val_2_new_keys = val_2.randomize_keys();
2560 let delegator = Address::random();
2561 let mut events: Vec<StakeTableEvent> = [
2562 ValidatorRegistered::from(&val_1).into(),
2563 ValidatorRegisteredV2::from(&val_2).into(),
2564 Delegated {
2565 delegator,
2566 validator: val_1.account,
2567 amount: U256::from(10),
2568 }
2569 .into(),
2570 ConsensusKeysUpdated::from(&val_1_new_keys).into(),
2571 ConsensusKeysUpdatedV2::from(&val_2_new_keys).into(),
2572 Undelegated {
2573 delegator,
2574 validator: val_1.account,
2575 amount: U256::from(7),
2576 }
2577 .into(),
2578 Delegated {
2580 delegator,
2581 validator: val_1.account,
2582 amount: U256::from(5),
2583 }
2584 .into(),
2585 Delegated {
2587 delegator: Address::random(),
2588 validator: val_2.account,
2589 amount: U256::from(3),
2590 }
2591 .into(),
2592 ]
2593 .to_vec();
2594
2595 let validators_set = validator_set_from_l1_events(events.iter().cloned())?;
2596 let st = validators_set.active_validators;
2597 let st_val_1 = st.get(&val_1.account).unwrap();
2598 assert_eq!(st_val_1.stake, U256::from(8));
2600 assert_eq!(st_val_1.commission, val_1.commission);
2601 assert_eq!(st_val_1.delegators.len(), 1);
2602 assert_eq!(*st_val_1.delegators.get(&delegator).unwrap(), U256::from(8));
2604
2605 let st_val_2 = st.get(&val_2.account).unwrap();
2606 assert_eq!(st_val_2.stake, U256::from(3));
2607 assert_eq!(st_val_2.commission, val_2.commission);
2608 assert_eq!(st_val_2.delegators.len(), 1);
2609
2610 events.push(ValidatorExit::from(&val_1).into());
2611
2612 let validator_set = validator_set_from_l1_events(events.iter().cloned())?;
2613 let st = validator_set.active_validators;
2614 assert_eq!(st.get(&val_1.account), None);
2616
2617 let st_val_2 = st.get(&val_2.account).unwrap();
2619 assert_eq!(st_val_2.stake, U256::from(3));
2620 assert_eq!(st_val_2.commission, val_2.commission);
2621 assert_eq!(st_val_2.delegators.len(), 1);
2622
2623 events.push(ValidatorExit::from(&val_2).into());
2625
2626 assert!(validator_set_from_l1_events(events.iter().cloned()).is_err());
2628
2629 Ok(())
2630 }
2631
2632 #[test]
2633 fn test_from_l1_events_failures() -> anyhow::Result<()> {
2634 let val = TestValidator::random();
2635 let delegator = Address::random();
2636
2637 let register: StakeTableEvent = ValidatorRegistered::from(&val).into();
2638 let register_v2: StakeTableEvent = ValidatorRegisteredV2::from(&val).into();
2639 let delegate: StakeTableEvent = Delegated {
2640 delegator,
2641 validator: val.account,
2642 amount: U256::from(10),
2643 }
2644 .into();
2645 let key_update: StakeTableEvent = ConsensusKeysUpdated::from(&val).into();
2646 let key_update_v2: StakeTableEvent = ConsensusKeysUpdatedV2::from(&val).into();
2647 let undelegate: StakeTableEvent = Undelegated {
2648 delegator,
2649 validator: val.account,
2650 amount: U256::from(7),
2651 }
2652 .into();
2653
2654 let exit: StakeTableEvent = ValidatorExit::from(&val).into();
2655
2656 let cases = [
2657 vec![exit],
2658 vec![undelegate.clone()],
2659 vec![delegate.clone()],
2660 vec![key_update],
2661 vec![key_update_v2],
2662 vec![register.clone(), register.clone()],
2663 vec![register_v2.clone(), register_v2.clone()],
2664 vec![register.clone(), register_v2.clone()],
2665 vec![register_v2.clone(), register.clone()],
2666 vec![
2667 register,
2668 delegate.clone(),
2669 undelegate.clone(),
2670 undelegate.clone(),
2671 ],
2672 vec![register_v2, delegate, undelegate.clone(), undelegate],
2673 ];
2674
2675 for events in cases.iter() {
2676 let res = validators_from_l1_events(events.iter().cloned());
2680 assert!(
2681 res.is_err(),
2682 "events {res:?}, not a valid sequence of events"
2683 );
2684 }
2685 Ok(())
2686 }
2687
2688 #[test]
2689 fn test_validators_selection() {
2690 let mut validators = IndexMap::new();
2691 let mut highest_stake = alloy::primitives::U256::ZERO;
2692
2693 for _i in 0..3000 {
2694 let validator = Validator::mock();
2695 validators.insert(validator.account, validator.clone());
2696
2697 if validator.stake > highest_stake {
2698 highest_stake = validator.stake;
2699 }
2700 }
2701
2702 let minimum_stake = highest_stake / U256::from(VID_TARGET_TOTAL_STAKE);
2703
2704 select_active_validator_set(&mut validators).expect("Failed to select validators");
2705 assert!(
2706 validators.len() <= 100,
2707 "validators len is {}, expected at most 100",
2708 validators.len()
2709 );
2710
2711 let mut selected_validators_highest_stake = alloy::primitives::U256::ZERO;
2712 for (address, validator) in &validators {
2714 assert!(
2715 validator.stake >= minimum_stake,
2716 "Validator {:?} has stake below minimum: {}",
2717 address,
2718 validator.stake
2719 );
2720
2721 if validator.stake > selected_validators_highest_stake {
2722 selected_validators_highest_stake = validator.stake;
2723 }
2724 }
2725 }
2726
2727 #[rstest::rstest]
2730 fn test_regression_non_unique_bls_keys_not_discarded(
2731 #[values(StakeTableContractVersion::V1, StakeTableContractVersion::V2)]
2732 version: StakeTableContractVersion,
2733 ) {
2734 let val = TestValidator::random();
2735 let register: StakeTableEvent = match version {
2736 StakeTableContractVersion::V1 => ValidatorRegistered::from(&val).into(),
2737 StakeTableContractVersion::V2 => ValidatorRegisteredV2::from(&val).into(),
2738 };
2739 let delegate: StakeTableEvent = Delegated {
2740 delegator: Address::random(),
2741 validator: val.account,
2742 amount: U256::from(10),
2743 }
2744 .into();
2745
2746 assert!(
2748 validator_set_from_l1_events(vec![register.clone(), delegate.clone()].into_iter())
2749 .is_ok()
2750 );
2751
2752 let key_update = ConsensusKeysUpdated::from(&val).into();
2754 let err = validator_set_from_l1_events(vec![register, delegate, key_update].into_iter())
2755 .unwrap_err();
2756
2757 let bls: BLSPubKey = val.bls_vk.into();
2758 assert!(matches!(err, StakeTableError::BlsKeyAlreadyUsed(addr) if addr == bls.to_string()));
2759 }
2760
2761 #[test]
2764 fn test_regression_reregister_eth_account() {
2765 let val1 = TestValidator::random();
2766 let val2 = val1.randomize_keys();
2767 let account = val1.account;
2768
2769 let register1 = ValidatorRegisteredV2::from(&val1).into();
2770 let deregister1 = ValidatorExit::from(&val1).into();
2771 let register2 = ValidatorRegisteredV2::from(&val2).into();
2772 let events = [register1, deregister1, register2];
2773 let error = validators_from_l1_events(events.iter().cloned()).unwrap_err();
2774 assert_matches!(error, StakeTableError::ValidatorAlreadyExited(addr) if addr == account);
2775 }
2776
2777 #[test]
2778 fn test_display_log() {
2779 let serialized = r#"{"address":"0x0000000000000000000000000000000000000069",
2780 "topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],
2781 "data":"0x69",
2782 "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2783 "blockNumber":"0x69","blockTimestamp":"0x69",
2784 "transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2785 "transactionIndex":"0x69","logIndex":"0x70","removed":false}"#;
2786 let log: Log = serde_json::from_str(serialized).unwrap();
2787 assert_eq!(
2788 log.display(),
2789 "Log(block=105,index=112,\
2790 transaction_hash=0x0000000000000000000000000000000000000000000000000000000000000069)"
2791 )
2792 }
2793
2794 #[rstest]
2795 #[case::v1(StakeTableContractVersion::V1)]
2796 #[case::v2(StakeTableContractVersion::V2)]
2797 fn test_register_validator(#[case] version: StakeTableContractVersion) {
2798 let mut state = StakeTableState::new();
2799 let validator = TestValidator::random();
2800
2801 let event = match version {
2802 StakeTableContractVersion::V1 => StakeTableEvent::Register((&validator).into()),
2803 StakeTableContractVersion::V2 => StakeTableEvent::RegisterV2((&validator).into()),
2804 };
2805
2806 assert!(state.apply_event(event).unwrap().is_ok());
2807
2808 let stored = state.validators.get(&validator.account).unwrap();
2809 assert_eq!(stored.account, validator.account);
2810 }
2811
2812 #[rstest]
2813 #[case::v1(StakeTableContractVersion::V1)]
2814 #[case::v2(StakeTableContractVersion::V2)]
2815 fn test_validator_already_registered(#[case] version: StakeTableContractVersion) {
2816 let mut stake_table_state = StakeTableState::new();
2817
2818 let test_validator = TestValidator::random();
2819
2820 let first_registration_result =
2822 match version {
2823 StakeTableContractVersion::V1 => stake_table_state
2824 .apply_event(StakeTableEvent::Register((&test_validator).into())),
2825 StakeTableContractVersion::V2 => stake_table_state
2826 .apply_event(StakeTableEvent::RegisterV2((&test_validator).into())),
2827 };
2828
2829 assert!(first_registration_result.unwrap().is_ok());
2831
2832 let v1_already_registered_result =
2834 stake_table_state.apply_event(StakeTableEvent::Register((&test_validator).into()));
2835
2836 pretty_assertions::assert_matches!(
2837 v1_already_registered_result, Err(StakeTableError::AlreadyRegistered(account))
2838 if account == test_validator.account,
2839 "Expected AlreadyRegistered error. version ={version:?} result={v1_already_registered_result:?}",
2840 );
2841
2842 let v2_already_registered_result =
2844 stake_table_state.apply_event(StakeTableEvent::RegisterV2((&test_validator).into()));
2845
2846 pretty_assertions::assert_matches!(
2847 v2_already_registered_result,
2848 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2849 "Expected AlreadyRegistered error. version ={version:?} result={v2_already_registered_result:?}",
2850
2851 );
2852 }
2853
2854 #[test]
2855 fn test_register_validator_v2_auth_fails() {
2856 let mut state = StakeTableState::new();
2857 let mut val = TestValidator::random();
2858 val.bls_sig = Default::default();
2859 let event = StakeTableEvent::RegisterV2((&val).into());
2860
2861 let result = state.apply_event(event);
2862 assert!(matches!(
2863 result,
2864 Err(StakeTableError::AuthenticationFailed(_))
2865 ));
2866 }
2867
2868 #[test]
2869 fn test_deregister_validator() {
2870 let mut state = StakeTableState::new();
2871 let val = TestValidator::random();
2872
2873 let reg = StakeTableEvent::Register((&val).into());
2874 state.apply_event(reg).unwrap().unwrap();
2875
2876 let dereg = StakeTableEvent::Deregister((&val).into());
2877 assert!(state.apply_event(dereg).unwrap().is_ok());
2878 assert!(!state.validators.contains_key(&val.account));
2879 }
2880
2881 #[test]
2882 fn test_delegate_and_undelegate() {
2883 let mut state = StakeTableState::new();
2884 let val = TestValidator::random();
2885 state
2886 .apply_event(StakeTableEvent::Register((&val).into()))
2887 .unwrap()
2888 .unwrap();
2889
2890 let delegator = Address::random();
2891 let amount = U256::from(1000);
2892 let delegate_event = StakeTableEvent::Delegate(Delegated {
2893 delegator,
2894 validator: val.account,
2895 amount,
2896 });
2897 assert!(state.apply_event(delegate_event).unwrap().is_ok());
2898
2899 let validator = state.validators.get(&val.account).unwrap();
2900 assert_eq!(validator.delegators.get(&delegator).cloned(), Some(amount));
2901
2902 let undelegate_event = StakeTableEvent::Undelegate(Undelegated {
2903 delegator,
2904 validator: val.account,
2905 amount,
2906 });
2907 assert!(state.apply_event(undelegate_event).unwrap().is_ok());
2908 let validator = state.validators.get(&val.account).unwrap();
2909 assert!(!validator.delegators.contains_key(&delegator));
2910 }
2911
2912 #[rstest]
2913 #[case::v1(StakeTableContractVersion::V1)]
2914 #[case::v2(StakeTableContractVersion::V2)]
2915 fn test_key_update_event(#[case] version: StakeTableContractVersion) {
2916 let mut state = StakeTableState::new();
2917 let val = TestValidator::random();
2918
2919 state
2921 .apply_event(StakeTableEvent::Register((&val).into()))
2922 .unwrap()
2923 .unwrap();
2924
2925 let new_keys = val.randomize_keys();
2926
2927 let event = match version {
2928 StakeTableContractVersion::V1 => StakeTableEvent::KeyUpdate((&new_keys).into()),
2929 StakeTableContractVersion::V2 => StakeTableEvent::KeyUpdateV2((&new_keys).into()),
2930 };
2931
2932 assert!(state.apply_event(event).unwrap().is_ok());
2933
2934 let updated = state.validators.get(&val.account).unwrap();
2935 assert_eq!(updated.stake_table_key, new_keys.bls_vk.into());
2936 assert_eq!(updated.state_ver_key, new_keys.schnorr_vk.into());
2937 }
2938
2939 #[test]
2940 fn test_duplicate_bls_key() {
2941 let mut state = StakeTableState::new();
2942 let val = TestValidator::random();
2943 let event1 = StakeTableEvent::Register((&val).into());
2944 let mut val2 = TestValidator::random();
2945 val2.bls_vk = val.bls_vk;
2946 val2.account = Address::random();
2947
2948 let event2 = StakeTableEvent::Register((&val2).into());
2949 assert!(state.apply_event(event1).unwrap().is_ok());
2950 let result = state.apply_event(event2);
2951
2952 let expected_bls_key = BLSPubKey::from(val.bls_vk).to_string();
2953
2954 assert_matches!(
2955 result,
2956 Err(StakeTableError::BlsKeyAlreadyUsed(key))
2957 if key == expected_bls_key,
2958 "Expected BlsKeyAlreadyUsed({expected_bls_key}), but got: {result:?}",
2959 );
2960 }
2961
2962 #[test]
2963 fn test_duplicate_schnorr_key() {
2964 let mut state = StakeTableState::new();
2965 let val = TestValidator::random();
2966 let event1 = StakeTableEvent::Register((&val).into());
2967 let mut val2 = TestValidator::random();
2968 val2.schnorr_vk = val.schnorr_vk;
2969 val2.account = Address::random();
2970 val2.bls_vk = val2.randomize_keys().bls_vk;
2971
2972 let event2 = StakeTableEvent::Register((&val2).into());
2973 assert!(state.apply_event(event1).unwrap().is_ok());
2974 let result = state.apply_event(event2);
2975
2976 let schnorr: SchnorrPubKey = val.schnorr_vk.into();
2977 assert_matches!(
2978 result,
2979 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
2980 if key == schnorr.to_string(),
2981 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2982
2983 );
2984 }
2985
2986 #[test]
2987 fn test_duplicate_schnorr_key_v2_during_update() {
2988 let mut state = StakeTableState::new();
2989
2990 let val1 = TestValidator::random();
2991
2992 let mut rng = &mut rand::thread_rng();
2993 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2994
2995 let val2 = TestValidator {
2996 account: val1.account,
2997 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2998 schnorr_vk: val1.schnorr_vk,
2999 commission: val1.commission,
3000 bls_sig: sign_address_bls(&bls_key_pair, val1.account).into(),
3001 schnorr_sig: val1.clone().schnorr_sig,
3002 };
3003 let event1 = StakeTableEvent::RegisterV2((&val1).into());
3004 let event2 = StakeTableEvent::KeyUpdateV2((&val2).into());
3005
3006 assert!(state.apply_event(event1).unwrap().is_ok());
3007 let result = state.apply_event(event2);
3008
3009 let schnorr: SchnorrPubKey = val1.schnorr_vk.into();
3010 assert_matches!(
3011 result,
3012 Err(StakeTableError::SchnorrKeyAlreadyUsed(key))
3013 if key == schnorr.to_string(),
3014 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
3015 );
3016 }
3017
3018 #[test]
3019 fn test_register_and_deregister_validator() {
3020 let mut state = StakeTableState::new();
3021 let validator = TestValidator::random();
3022 let event = StakeTableEvent::Register((&validator).into());
3023 assert!(state.apply_event(event).unwrap().is_ok());
3024
3025 let deregister_event = StakeTableEvent::Deregister((&validator).into());
3026 assert!(state.apply_event(deregister_event).unwrap().is_ok());
3027 }
3028
3029 #[test]
3030 fn test_commission_validation_exceeds_basis_points() {
3031 let validator = TestValidator::random();
3033 let mut stake_table = StakeTableState::new();
3034
3035 let registration_event = ValidatorRegistered::from(&validator).into();
3037 stake_table
3038 .apply_event(registration_event)
3039 .unwrap()
3040 .unwrap();
3041
3042 let valid_commission_event = CommissionUpdated {
3044 validator: validator.account,
3045 timestamp: Default::default(),
3046 oldCommission: 0,
3047 newCommission: COMMISSION_BASIS_POINTS, }
3049 .into();
3050 assert!(stake_table.apply_event(valid_commission_event).is_ok());
3051
3052 let invalid_commission = COMMISSION_BASIS_POINTS + 1;
3053 let invalid_commission_event = CommissionUpdated {
3054 validator: validator.account,
3055 timestamp: Default::default(),
3056 oldCommission: 0,
3057 newCommission: invalid_commission,
3058 }
3059 .into();
3060
3061 let err = stake_table
3062 .apply_event(invalid_commission_event)
3063 .unwrap_err();
3064
3065 assert_matches!(
3066 err,
3067 StakeTableError::InvalidCommission(addr, invalid_commission)
3068 if addr == addr && invalid_commission == invalid_commission);
3069 }
3070
3071 #[test]
3072 fn test_delegate_zero_amount_is_rejected() {
3073 let mut state = StakeTableState::new();
3074 let validator = TestValidator::random();
3075 let account = validator.account;
3076 state
3077 .apply_event(StakeTableEvent::Register((&validator).into()))
3078 .unwrap()
3079 .unwrap();
3080
3081 let delegator = Address::random();
3082 let amount = U256::ZERO;
3083 let event = StakeTableEvent::Delegate(Delegated {
3084 delegator,
3085 validator: account,
3086 amount,
3087 });
3088 let result = state.apply_event(event);
3089
3090 assert_matches!(
3091 result,
3092 Err(StakeTableError::ZeroDelegatorStake(addr))
3093 if addr == delegator,
3094 "delegator stake is zero"
3095
3096 );
3097 }
3098
3099 #[test]
3100 fn test_undelegate_more_than_stake_fails() {
3101 let mut state = StakeTableState::new();
3102 let validator = TestValidator::random();
3103 let account = validator.account;
3104 state
3105 .apply_event(StakeTableEvent::Register((&validator).into()))
3106 .unwrap()
3107 .unwrap();
3108
3109 let delegator = Address::random();
3110 let event = StakeTableEvent::Delegate(Delegated {
3111 delegator,
3112 validator: account,
3113 amount: U256::from(10u64),
3114 });
3115 state.apply_event(event).unwrap().unwrap();
3116
3117 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3118 delegator,
3119 validator: account,
3120 amount: U256::from(20u64),
3121 }));
3122 assert_matches!(
3123 result,
3124 Err(StakeTableError::InsufficientStake),
3125 "Expected InsufficientStake error, got: {result:?}",
3126 );
3127 }
3128
3129 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3130 async fn test_decaf_stake_table() {
3131 let events_json =
3167 std::fs::read_to_string("../data/v3/decaf_stake_table_events.json").unwrap();
3168 let events: Vec<(EventKey, StakeTableEvent)> = serde_json::from_str(&events_json).unwrap();
3169
3170 let reconstructed_stake_table =
3172 validator_set_from_l1_events(events.into_iter().map(|(_, e)| e))
3173 .unwrap()
3174 .active_validators;
3175
3176 let stake_table_json =
3177 std::fs::read_to_string("../data/v3/decaf_stake_table.json").unwrap();
3178 let expected: IndexMap<Address, Validator<BLSPubKey>> =
3179 serde_json::from_str(&stake_table_json).unwrap();
3180
3181 assert_eq!(
3182 reconstructed_stake_table, expected,
3183 "Stake table reconstructed from events does not match the expected stake table "
3184 );
3185 }
3186
3187 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3188 #[should_panic]
3189 async fn test_large_max_events_range_panic() {
3190 let contract_address = "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037";
3192
3193 let l1 = L1ClientOptions {
3194 l1_events_max_retry_duration: Duration::from_secs(30),
3195 l1_events_max_block_range: 10_u64.pow(9),
3197 l1_retry_delay: Duration::from_secs(1),
3198 ..Default::default()
3199 }
3200 .connect(vec!["https://ethereum-sepolia.publicnode.com"
3201 .parse()
3202 .unwrap()])
3203 .expect("unable to construct l1 client");
3204
3205 let latest_block = l1.provider.get_block_number().await.unwrap();
3206 let _events = Fetcher::fetch_events_from_contract(
3207 l1,
3208 contract_address.parse().unwrap(),
3209 None,
3210 latest_block,
3211 )
3212 .await
3213 .unwrap();
3214 }
3215
3216 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3217 async fn sanity_check_block_reward_v3() {
3218 let initial_supply = U256::from_str("10000000000000000000000000000").unwrap();
3220
3221 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
3222 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
3223 .unwrap();
3224
3225 println!("Calculated reward: {reward}");
3226 assert!(reward > U256::ZERO);
3227 }
3228
3229 #[test]
3230 fn sanity_check_p_and_rp() {
3231 let total_stake = BigDecimal::from_str("1000").unwrap();
3232 let total_supply = BigDecimal::from_str("10000").unwrap(); let (p, rp) =
3235 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3236
3237 assert!(p > BigDecimal::from(0));
3238 assert!(p < BigDecimal::from(1));
3239 assert!(rp > BigDecimal::from(0));
3240 }
3241
3242 #[test]
3243 fn test_p_out_of_range() {
3244 let total_stake = BigDecimal::from_str("1000").unwrap();
3245 let total_supply = BigDecimal::from_str("500").unwrap(); let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3248 assert!(result.is_err());
3249 }
3250
3251 #[test]
3252 fn test_zero_total_supply() {
3253 let total_stake = BigDecimal::from_str("1000").unwrap();
3254 let total_supply = BigDecimal::from(0);
3255
3256 let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3257 assert!(result.is_err());
3258 }
3259
3260 #[test]
3261 fn test_valid_p_and_rp() {
3262 let total_stake = BigDecimal::from_str("5000").unwrap();
3263 let total_supply = BigDecimal::from_str("10000").unwrap();
3264
3265 let (p, rp) =
3266 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3267
3268 assert_eq!(p, BigDecimal::from_str("0.5").unwrap());
3269 assert!(rp > BigDecimal::from_str("0.0").unwrap());
3270 }
3271
3272 #[test]
3273 fn test_very_small_p() {
3274 let total_stake = BigDecimal::from_str("1").unwrap(); let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap(); let (p, rp) =
3278 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3279
3280 assert!(p > BigDecimal::from_str("0").unwrap());
3281 assert!(p < BigDecimal::from_str("1e-18").unwrap()); assert!(rp > BigDecimal::from(0));
3283 }
3284
3285 #[test]
3286 fn test_p_very_close_to_one() {
3287 let total_stake = BigDecimal::from_str("9999999999999999999999999999").unwrap();
3288 let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap();
3289
3290 let (p, rp) =
3291 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3292
3293 assert!(p < BigDecimal::from(1));
3294 assert!(p > BigDecimal::from_str("0.999999999999999999999999999").unwrap());
3295 assert!(rp > BigDecimal::from(0));
3296 }
3297
3298 #[test]
3302 fn test_reward_rate_rp() {
3303 let test_cases = [
3304 ("0.0000", "0.2121"), ("0.0050", "0.2121"), ("0.0100", "0.2121"), ("0.0250", "0.1342"), ("0.0500", "0.0949"), ("0.1000", "0.0671"), ("0.2500", "0.0424"), ("0.5000", "0.0300"), ("0.7500", "0.0245"), ("1.0000", "0.0212"), ];
3316
3317 let tolerance = BigDecimal::from_str("0.0001").unwrap();
3318
3319 let total_supply = BigDecimal::from_u32(10_000).unwrap();
3320
3321 for (p, rp) in test_cases {
3322 let p = BigDecimal::from_str(p).unwrap();
3323 let expected_rp = BigDecimal::from_str(rp).unwrap();
3324
3325 let total_stake = &p * &total_supply;
3326
3327 let (computed_p, computed_rp) =
3328 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3329
3330 assert!(
3331 (&computed_p - &p).abs() < tolerance,
3332 "p mismatch: got {computed_p}, expected {p}"
3333 );
3334
3335 assert!(
3336 (&computed_rp - &expected_rp).abs() < tolerance,
3337 "R(p) mismatch for p={p}: got {computed_rp}, expected {expected_rp}"
3338 );
3339 }
3340 }
3341
3342 #[tokio::test(flavor = "multi_thread")]
3343 async fn test_dynamic_block_reward_with_expected_values() {
3344 let total_supply = U256::from_str("10000000000000000000000000000").unwrap();
3346 let total_supply_bd = BigDecimal::from_str(&total_supply.to_string()).unwrap();
3347
3348 let test_cases = [
3349 ("0.0000", "0.2121", 1, "0"), ("0.0050", "0.2121", 1, "3362823439878234"), ("0.0100", "0.2121", 1, "6725646879756468"), ("0.0250", "0.1342", 1, "10638635210553018"), ("0.0500", "0.0949", 1, "15046296296296296"), ("0.1000", "0.0671", 1, "21277270421106037"), ("0.2500", "0.0424", 1, "33612379502790461"), ("0.5000", "0.0300", 1, "47564687975646879"), ("0.7500", "0.0245", 1, "58266742770167427"), ("1.0000", "0.0212", 1, "67224759005580923"), ("0.0000", "0.2121", 2000, "0"), ("0.0050", "0.2121", 2000, "672564687975646880"), ("0.0100", "0.2121", 2000, "1345129375951293760"), ("0.0250", "0.1342", 2000, "2127727042110603754"), ("0.0500", "0.0949", 2000, "3009259259259259259"), ("0.1000", "0.0671", 2000, "4255454084221207509"), ("0.2500", "0.0424", 2000, "6722475900558092339"), ("0.5000", "0.0300", 2000, "9512937595129375951"), ("0.7500", "0.0245", 2000, "11653348554033485540"), ("1.0000", "0.0212", 2000, "13444951801116184678"), ("0.0000", "0.2121", 10000, "0"), ("0.0050", "0.2121", 10000, "3362823439878234400"), ("0.0100", "0.2121", 10000, "6725646879756468800"), ("0.0250", "0.1342", 10000, "10638635210553018770"), ("0.0500", "0.0949", 10000, "15046296296296296295"), ("0.1000", "0.0671", 10000, "21277270421106037545"), ("0.2500", "0.0424", 10000, "33612379502790461695"), ("0.5000", "0.0300", 10000, "47564687975646879755"), ("0.7500", "0.0245", 10000, "58266742770167427700"), ("1.0000", "0.0212", 10000, "67224759005580923390"), ];
3383
3384 let tolerance = U256::from(100_000_000_000_000_000u128); for (p, rp, avg_block_time_ms, expected_reward) in test_cases {
3387 let p = BigDecimal::from_str(p).unwrap();
3388 let total_stake_bd = (&p * &total_supply_bd).round(0);
3389 println!("total_stake_bd={total_stake_bd}");
3390
3391 let total_stake = U256::from_str(&total_stake_bd.to_plain_string()).unwrap();
3392 let expected_reward = U256::from_str(expected_reward).unwrap();
3393
3394 let epoch = EpochNumber::new(0);
3395 let actual_reward = EpochCommittees::compute_block_reward(
3396 &epoch,
3397 total_supply,
3398 total_stake,
3399 avg_block_time_ms,
3400 )
3401 .unwrap()
3402 .0;
3403
3404 let diff = if actual_reward > expected_reward {
3405 actual_reward - expected_reward
3406 } else {
3407 expected_reward - actual_reward
3408 };
3409
3410 assert!(
3411 diff <= tolerance,
3412 "Reward mismatch for p = {p}, R(p) = {rp}, block_time = {avg_block_time_ms}: \
3413 expected = {expected_reward}, actual = {actual_reward}, diff = {diff}"
3414 );
3415 }
3416 }
3417}