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