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