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