espresso_types/v0/impls/
stake_table.rs

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