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