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