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