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