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