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