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