espresso_types/v0/impls/
stake_table.rs

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