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