1use std::{
2 cmp::min,
3 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
4 future::Future,
5 ops::Bound,
6 str::FromStr,
7 sync::Arc,
8 time::{Duration, Instant},
9};
10
11use alloy::{
12 eips::{BlockId, BlockNumberOrTag},
13 primitives::{Address, U256, utils::format_ether},
14 providers::Provider,
15 rpc::types::{Filter, Log},
16 sol_types::{SolEvent, SolEventInterface},
17};
18use anyhow::{Context, bail, ensure};
19use ark_ec::AffineRepr;
20use ark_serialize::CanonicalSerialize;
21use ark_std::One;
22use async_lock::{Mutex, RwLock, RwLockUpgradableReadGuard};
23use bigdecimal::BigDecimal;
24use committable::{Commitment, Committable, RawCommitmentBuilder};
25use futures::future::BoxFuture;
26use hotshot::types::{BLSPubKey, SchnorrPubKey, SignatureKey as _};
27use hotshot_contract_adapter::sol_types::{
28 EspToken::{self, EspTokenInstance},
29 StakeTableV2::{
30 self, CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated,
31 StakeTableV2Events, Undelegated, UndelegatedV2, ValidatorExit, ValidatorExitV2,
32 ValidatorRegistered, ValidatorRegisteredV2,
33 },
34};
35use hotshot_types::{
36 PeerConfig, PeerConnectInfo,
37 data::{EpochNumber, ViewNumber, vid_disperse::VID_TARGET_TOTAL_STAKE},
38 drb::{
39 DrbResult,
40 election::{RandomizedCommittee, generate_stake_cdf, select_randomized_leader},
41 },
42 epoch_membership::EpochMembershipCoordinator,
43 stake_table::{HSStakeTable, StakeTableEntry},
44 traits::{
45 block_contents::BlockHeader,
46 election::Membership,
47 node_implementation::{NodeImplementation, NodeType},
48 signature_key::StakeTableEntryType,
49 },
50 utils::{
51 epoch_from_block_number, is_epoch_root, root_block_in_epoch, transition_block_for_epoch,
52 },
53};
54use humantime::format_duration;
55use indexmap::IndexMap;
56use itertools::Itertools;
57use num_traits::{FromPrimitive, Zero};
58use thiserror::Error;
59use tokio::{spawn, time::sleep};
60use tracing::Instrument;
61use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION};
62
63#[cfg(any(test, feature = "testing"))]
64use super::v0_3::DAMembers;
65use super::{
66 Header, L1Client, Leaf2, PubKey, SeqTypes,
67 traits::{MembershipPersistence, StateCatchup},
68 v0_3::{
69 AuthenticatedValidator, ChainConfig, EventKey, Fetcher, MAX_VALIDATORS,
70 RegisteredValidator, StakeTableEvent, StakeTableUpdateTask,
71 },
72};
73use crate::{
74 traits::EventsPersistenceRead,
75 v0_1::L1Provider,
76 v0_3::{
77 ASSUMED_BLOCK_TIME_SECONDS, BLOCKS_PER_YEAR, COMMISSION_BASIS_POINTS, EventSortingError,
78 ExpectedStakeTableError, FetchRewardError, INFLATION_RATE, MILLISECONDS_PER_YEAR,
79 RewardAmount, StakeTableError,
80 },
81};
82
83type Epoch = EpochNumber;
84pub type RegisteredValidatorMap = IndexMap<Address, RegisteredValidator<BLSPubKey>>;
85pub type AuthenticatedValidatorMap = IndexMap<Address, AuthenticatedValidator<BLSPubKey>>;
86
87pub fn to_registered_validator_map(
88 validators: &AuthenticatedValidatorMap,
89) -> RegisteredValidatorMap {
90 validators
91 .iter()
92 .map(|(addr, v)| (*addr, v.clone().into()))
93 .collect()
94}
95
96pub type StakeTableHash = Commitment<StakeTableState>;
97
98type ApplyEventResult<T> = Result<Result<T, ExpectedStakeTableError>, StakeTableError>;
103
104trait DisplayLog {
106 fn display(&self) -> String;
107}
108
109impl DisplayLog for Log {
110 fn display(&self) -> String {
111 let block = self.block_number.unwrap_or_default();
115 let index = self.log_index.unwrap_or_default();
116 let hash = self.transaction_hash.unwrap_or_default();
117 format!("Log(block={block},index={index},transaction_hash={hash})")
118 }
119}
120
121impl TryFrom<StakeTableV2Events> for StakeTableEvent {
122 type Error = anyhow::Error;
123
124 fn try_from(value: StakeTableV2Events) -> anyhow::Result<Self> {
125 match value {
126 StakeTableV2Events::ValidatorRegistered(v) => Ok(StakeTableEvent::Register(v)),
127 StakeTableV2Events::ValidatorRegisteredV2(v) => Ok(StakeTableEvent::RegisterV2(v)),
128 StakeTableV2Events::ValidatorExit(v) => Ok(StakeTableEvent::Deregister(v)),
129 StakeTableV2Events::ValidatorExitV2(v) => Ok(StakeTableEvent::DeregisterV2(v)),
130 StakeTableV2Events::Delegated(v) => Ok(StakeTableEvent::Delegate(v)),
131 StakeTableV2Events::Undelegated(v) => Ok(StakeTableEvent::Undelegate(v)),
132 StakeTableV2Events::UndelegatedV2(v) => Ok(StakeTableEvent::UndelegateV2(v)),
133 StakeTableV2Events::ConsensusKeysUpdated(v) => Ok(StakeTableEvent::KeyUpdate(v)),
134 StakeTableV2Events::ConsensusKeysUpdatedV2(v) => Ok(StakeTableEvent::KeyUpdateV2(v)),
135 StakeTableV2Events::CommissionUpdated(v) => Ok(StakeTableEvent::CommissionUpdate(v)),
136 StakeTableV2Events::ExitEscrowPeriodUpdated(v) => Err(anyhow::anyhow!(
137 "Unsupported StakeTableV2Events::ExitEscrowPeriodUpdated({v:?})"
138 )),
139 StakeTableV2Events::Initialized(v) => Err(anyhow::anyhow!(
140 "Unsupported StakeTableV2Events::Initialized({v:?})"
141 )),
142 StakeTableV2Events::MaxCommissionIncreaseUpdated(v) => Err(anyhow::anyhow!(
143 "Unsupported StakeTableV2Events::MaxCommissionIncreaseUpdated({v:?})"
144 )),
145 StakeTableV2Events::MinCommissionUpdateIntervalUpdated(v) => Err(anyhow::anyhow!(
146 "Unsupported StakeTableV2Events::MinCommissionUpdateIntervalUpdated({v:?})"
147 )),
148 StakeTableV2Events::OwnershipTransferred(v) => Err(anyhow::anyhow!(
149 "Unsupported StakeTableV2Events::OwnershipTransferred({v:?})"
150 )),
151 StakeTableV2Events::Paused(v) => Err(anyhow::anyhow!(
152 "Unsupported StakeTableV2Events::Paused({v:?})"
153 )),
154 StakeTableV2Events::RoleAdminChanged(v) => Err(anyhow::anyhow!(
155 "Unsupported StakeTableV2Events::RoleAdminChanged({v:?})"
156 )),
157 StakeTableV2Events::RoleGranted(v) => Err(anyhow::anyhow!(
158 "Unsupported StakeTableV2Events::RoleGranted({v:?})"
159 )),
160 StakeTableV2Events::RoleRevoked(v) => Err(anyhow::anyhow!(
161 "Unsupported StakeTableV2Events::RoleRevoked({v:?})"
162 )),
163 StakeTableV2Events::Unpaused(v) => Err(anyhow::anyhow!(
164 "Unsupported StakeTableV2Events::Unpaused({v:?})"
165 )),
166 StakeTableV2Events::Upgraded(v) => Err(anyhow::anyhow!(
167 "Unsupported StakeTableV2Events::Upgraded({v:?})"
168 )),
169 StakeTableV2Events::WithdrawalClaimed(v) => Err(anyhow::anyhow!(
170 "Unsupported StakeTableV2Events::WithdrawalClaimed({v:?})"
171 )),
172 StakeTableV2Events::ValidatorExitClaimed(v) => Err(anyhow::anyhow!(
173 "Unsupported StakeTableV2Events::ValidatorExitClaimed({v:?})"
174 )),
175 StakeTableV2Events::Withdrawal(v) => Err(anyhow::anyhow!(
176 "Unsupported StakeTableV2Events::Withdrawal({v:?})"
177 )),
178 StakeTableV2Events::MetadataUriUpdated(v) => Err(anyhow::anyhow!(
179 "Unsupported StakeTableV2Events::MetadataUriUpdated({v:?})"
180 )),
181 StakeTableV2Events::MinDelegateAmountUpdated(v) => Err(anyhow::anyhow!(
182 "Unsupported StakeTableV2Events::MinDelegateAmountUpdated({v:?})"
183 )),
184 }
185 }
186}
187
188fn sort_stake_table_events(
189 event_logs: Vec<(StakeTableV2Events, Log)>,
190) -> Result<Vec<(EventKey, StakeTableEvent)>, EventSortingError> {
191 let mut events: Vec<(EventKey, StakeTableEvent)> = Vec::new();
192
193 let key = |log: &Log| -> Result<EventKey, EventSortingError> {
194 let block_number = log
195 .block_number
196 .ok_or(EventSortingError::MissingBlockNumber)?;
197 let log_index = log.log_index.ok_or(EventSortingError::MissingLogIndex)?;
198 Ok((block_number, log_index))
199 };
200
201 for (e, log) in event_logs {
202 let k = key(&log)?;
203 let evt: StakeTableEvent = e
204 .try_into()
205 .map_err(|_| EventSortingError::InvalidStakeTableV2Event)?;
206 events.push((k, evt));
207 }
208
209 events.sort_by_key(|(key, _)| *key);
210 Ok(events)
211}
212
213#[derive(Clone, Debug, Default, PartialEq)]
214pub struct StakeTableState {
215 validators: RegisteredValidatorMap,
216 validator_exits: HashSet<Address>,
217 used_bls_keys: HashSet<BLSPubKey>,
218 used_schnorr_keys: HashSet<SchnorrPubKey>,
219}
220
221impl Committable for StakeTableState {
222 fn commit(&self) -> committable::Commitment<Self> {
223 let mut builder = RawCommitmentBuilder::new(&Self::tag());
224
225 for (_, validator) in self.validators.iter().sorted_by_key(|(a, _)| *a) {
226 builder = builder.field("validator", validator.commit());
227 }
228
229 builder = builder.constant_str("used_bls_keys");
230 for key in self.used_bls_keys.iter().sorted() {
231 builder = builder.var_size_bytes(&key.to_bytes());
232 }
233
234 builder = builder.constant_str("used_schnorr_keys");
235 for key in self
236 .used_schnorr_keys
237 .iter()
238 .sorted_by(|a, b| a.to_affine().xy().cmp(&b.to_affine().xy()))
239 {
240 let mut schnorr_key_bytes = vec![];
241 key.serialize_with_mode(&mut schnorr_key_bytes, ark_serialize::Compress::Yes)
242 .unwrap();
243 builder = builder.var_size_bytes(&schnorr_key_bytes);
244 }
245
246 builder = builder.constant_str("validator_exits");
247
248 for key in self.validator_exits.iter().sorted() {
249 builder = builder.fixed_size_bytes(&key.into_array());
250 }
251
252 builder.finalize()
253 }
254
255 fn tag() -> String {
256 "STAKE_TABLE".to_string()
257 }
258}
259
260impl StakeTableState {
261 pub fn new(
262 validators: RegisteredValidatorMap,
263 validator_exits: HashSet<Address>,
264 used_bls_keys: HashSet<BLSPubKey>,
265 used_schnorr_keys: HashSet<SchnorrPubKey>,
266 ) -> Self {
267 Self {
268 validators,
269 validator_exits,
270 used_bls_keys,
271 used_schnorr_keys,
272 }
273 }
274
275 pub fn validators(&self) -> &RegisteredValidatorMap {
276 &self.validators
277 }
278
279 pub fn into_validators(self) -> RegisteredValidatorMap {
280 self.validators
281 }
282
283 pub fn used_bls_keys(&self) -> &HashSet<BLSPubKey> {
284 &self.used_bls_keys
285 }
286
287 pub fn used_schnorr_keys(&self) -> &HashSet<SchnorrPubKey> {
288 &self.used_schnorr_keys
289 }
290
291 pub fn validator_exits(&self) -> &HashSet<Address> {
292 &self.validator_exits
293 }
294
295 pub fn apply_event(&mut self, event: StakeTableEvent) -> ApplyEventResult<()> {
301 match event {
302 StakeTableEvent::Register(ValidatorRegistered {
303 account,
304 blsVk,
305 schnorrVk,
306 commission,
307 }) => {
308 let stake_table_key: BLSPubKey = blsVk.into();
309 let state_ver_key: SchnorrPubKey = schnorrVk.into();
310
311 if self.validator_exits.contains(&account) {
312 return Err(StakeTableError::ValidatorAlreadyExited(account));
313 }
314
315 let entry = self.validators.entry(account);
316 if let indexmap::map::Entry::Occupied(_) = entry {
317 return Err(StakeTableError::AlreadyRegistered(account));
318 }
319
320 if self.used_bls_keys.contains(&stake_table_key) {
322 return Err(StakeTableError::BlsKeyAlreadyUsed(
323 stake_table_key.to_string(),
324 ));
325 }
326
327 if self.used_schnorr_keys.contains(&state_ver_key) {
329 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
330 state_ver_key.to_string(),
331 )));
332 }
333
334 self.used_bls_keys.insert(stake_table_key);
336 self.used_schnorr_keys.insert(state_ver_key.clone());
337
338 entry.or_insert(RegisteredValidator {
339 account,
340 stake_table_key,
341 state_ver_key,
342 stake: U256::ZERO,
343 commission,
344 delegators: HashMap::new(),
345 authenticated: true,
346 x25519_key: None,
347 p2p_addr: None,
348 });
349 },
350
351 StakeTableEvent::RegisterV2(ref reg) => {
352 let authenticated = reg.authenticate().is_ok();
353 if !authenticated {
354 tracing::warn!(
355 account = ?reg.account,
356 "Validator registered with invalid signature"
357 );
358 }
359
360 let ValidatorRegisteredV2 {
361 account,
362 blsVK,
363 schnorrVK,
364 commission,
365 ..
366 } = reg;
367
368 let stake_table_key: BLSPubKey = (*blsVK).into();
369 let state_ver_key: SchnorrPubKey = (*schnorrVK).into();
370
371 if self.validator_exits.contains(account) {
373 return Err(StakeTableError::ValidatorAlreadyExited(*account));
374 }
375
376 let entry = self.validators.entry(*account);
377 if let indexmap::map::Entry::Occupied(_) = entry {
378 return Err(StakeTableError::AlreadyRegistered(*account));
379 }
380
381 if self.used_bls_keys.contains(&stake_table_key) {
383 return Err(StakeTableError::BlsKeyAlreadyUsed(
384 stake_table_key.to_string(),
385 ));
386 }
387
388 if self.used_schnorr_keys.contains(&state_ver_key) {
390 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
391 state_ver_key.to_string(),
392 ));
393 }
394
395 self.used_bls_keys.insert(stake_table_key);
397 self.used_schnorr_keys.insert(state_ver_key.clone());
398
399 entry.or_insert(RegisteredValidator {
400 account: *account,
401 stake_table_key,
402 state_ver_key,
403 stake: U256::ZERO,
404 commission: *commission,
405 delegators: HashMap::new(),
406 authenticated,
407 x25519_key: None,
408 p2p_addr: None,
409 });
410 },
411
412 StakeTableEvent::Deregister(ValidatorExit { validator })
413 | StakeTableEvent::DeregisterV2(ValidatorExitV2 { validator, .. }) => {
414 if !self.validators.contains_key(&validator) {
415 return Err(StakeTableError::ValidatorNotFound(validator));
416 }
417
418 self.validator_exits.insert(validator);
420 self.validators.shift_remove(&validator);
421 },
422
423 StakeTableEvent::Delegate(delegated) => {
424 let Delegated {
425 delegator,
426 validator,
427 amount,
428 } = delegated;
429
430 if amount.is_zero() {
432 return Err(StakeTableError::ZeroDelegatorStake(delegator));
433 }
434
435 let val = self
436 .validators
437 .get_mut(&validator)
438 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
439
440 val.stake = val.stake.checked_add(amount).unwrap_or_else(|| {
443 panic!(
444 "validator stake overflow: validator={validator}, stake={}, \
445 amount={amount}",
446 val.stake
447 )
448 });
449 val.delegators
452 .entry(delegator)
453 .and_modify(|stake| {
454 *stake = stake.checked_add(amount).unwrap_or_else(|| {
455 panic!(
456 "delegator stake overflow: delegator={delegator}, stake={stake}, \
457 amount={amount}"
458 )
459 });
460 })
461 .or_insert(amount);
462 },
463
464 StakeTableEvent::Undelegate(Undelegated {
465 delegator,
466 validator,
467 amount,
468 })
469 | StakeTableEvent::UndelegateV2(UndelegatedV2 {
470 delegator,
471 validator,
472 amount,
473 ..
474 }) => {
475 let val = self
476 .validators
477 .get_mut(&validator)
478 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
479
480 if val.stake < amount {
481 tracing::warn!("validator_stake={}, amount={amount}", val.stake);
482 return Err(StakeTableError::InsufficientStake);
483 }
484
485 let delegator_stake = val
486 .delegators
487 .get_mut(&delegator)
488 .ok_or(StakeTableError::DelegatorNotFound(delegator))?;
489
490 if *delegator_stake < amount {
491 tracing::warn!("delegator_stake={delegator_stake}, amount={amount}");
492 return Err(StakeTableError::InsufficientStake);
493 }
494
495 let new_delegator_stake = delegator_stake.checked_sub(amount).unwrap();
497
498 val.stake = val.stake.checked_sub(amount).unwrap();
501
502 if new_delegator_stake.is_zero() {
503 val.delegators.remove(&delegator);
504 } else {
505 *delegator_stake = new_delegator_stake;
506 }
507 },
508
509 StakeTableEvent::KeyUpdate(update) => {
510 let ConsensusKeysUpdated {
511 account,
512 blsVK,
513 schnorrVK,
514 } = update;
515
516 let stake_table_key: BLSPubKey = blsVK.into();
517 let state_ver_key: SchnorrPubKey = schnorrVK.into();
518
519 if !self.validators.contains_key(&account) {
520 return Err(StakeTableError::ValidatorNotFound(account));
521 }
522
523 if self.used_bls_keys.contains(&stake_table_key) {
524 return Err(StakeTableError::BlsKeyAlreadyUsed(
525 stake_table_key.to_string(),
526 ));
527 }
528
529 if self.used_schnorr_keys.contains(&state_ver_key) {
532 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
533 state_ver_key.to_string(),
534 )));
535 }
536
537 self.used_bls_keys.insert(stake_table_key);
539 self.used_schnorr_keys.insert(state_ver_key.clone());
540 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
542 panic!("validator {account} must exist after contains_key check")
543 });
544 validator.stake_table_key = stake_table_key;
545 validator.state_ver_key = state_ver_key;
546 },
547
548 StakeTableEvent::KeyUpdateV2(update) => {
549 update
552 .authenticate()
553 .map_err(|e| StakeTableError::AuthenticationFailed(e.to_string()))?;
554
555 let ConsensusKeysUpdatedV2 {
556 account,
557 blsVK,
558 schnorrVK,
559 ..
560 } = update;
561
562 let stake_table_key: BLSPubKey = blsVK.into();
563 let state_ver_key: SchnorrPubKey = schnorrVK.into();
564
565 if !self.validators.contains_key(&account) {
566 return Err(StakeTableError::ValidatorNotFound(account));
567 }
568
569 if self.used_bls_keys.contains(&stake_table_key) {
571 return Err(StakeTableError::BlsKeyAlreadyUsed(
572 stake_table_key.to_string(),
573 ));
574 }
575
576 if self.used_schnorr_keys.contains(&state_ver_key) {
578 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
579 state_ver_key.to_string(),
580 ));
581 }
582
583 self.used_bls_keys.insert(stake_table_key);
585 self.used_schnorr_keys.insert(state_ver_key.clone());
586
587 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
589 panic!("validator {account} must exist after contains_key check")
590 });
591 validator.stake_table_key = stake_table_key;
592 validator.state_ver_key = state_ver_key;
593 },
594
595 StakeTableEvent::CommissionUpdate(CommissionUpdated {
596 validator,
597 newCommission,
598 ..
599 }) => {
600 if newCommission > COMMISSION_BASIS_POINTS {
603 return Err(StakeTableError::InvalidCommission(validator, newCommission));
604 }
605
606 let val = self
610 .validators
611 .get_mut(&validator)
612 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
613 val.commission = newCommission;
614 },
615 }
616
617 Ok(Ok(()))
618 }
619}
620
621pub fn validators_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
622 events: I,
623) -> Result<(RegisteredValidatorMap, StakeTableHash), StakeTableError> {
624 let mut state = StakeTableState::default();
625 for event in events {
626 match state.apply_event(event.clone()) {
627 Ok(Ok(())) => {
628 },
630 Ok(Err(expected_err)) => {
631 tracing::warn!("Expected error while applying event {event:?}: {expected_err}");
633 },
634 Err(err) => {
635 tracing::error!("Fatal error in applying event {event:?}: {err}");
636 return Err(err);
637 },
638 }
639 }
640 let commit = state.commit();
641 Ok((state.into_validators(), commit))
642}
643
644pub fn select_active_validator_set(
650 candidates: &RegisteredValidatorMap,
651) -> Result<AuthenticatedValidatorMap, StakeTableError> {
652 let total_candidates = candidates.len();
653
654 let valid_validators: AuthenticatedValidatorMap = candidates
655 .iter()
656 .filter_map(
657 |(address, validator)| match AuthenticatedValidator::try_from(validator) {
658 Err(e) => {
659 tracing::debug!("{e}");
660 None
661 },
662 Ok(cv) => {
663 if cv.delegators.is_empty() {
664 tracing::info!("Validator {address:?} does not have any delegator");
665 return None;
666 }
667 if cv.stake.is_zero() {
668 tracing::info!("Validator {address:?} does not have any stake");
669 return None;
670 }
671 Some((*address, cv))
672 },
673 },
674 )
675 .collect();
676
677 tracing::debug!(
678 total_candidates,
679 filtered = valid_validators.len(),
680 "Filtered out invalid validators"
681 );
682
683 if valid_validators.is_empty() {
684 tracing::warn!("Validator selection failed: no validators passed minimum criteria");
685 return Err(StakeTableError::NoValidValidators);
686 }
687
688 let maximum_stake = valid_validators.values().map(|v| v.stake).max().unwrap();
689
690 let minimum_stake = maximum_stake
691 .checked_div(U256::from(VID_TARGET_TOTAL_STAKE))
692 .ok_or_else(|| {
693 tracing::error!("Overflow while calculating minimum stake threshold");
694 StakeTableError::MinimumStakeOverflow
695 })?;
696
697 let mut valid_stakers: Vec<_> = valid_validators
698 .iter()
699 .filter(|(_, v)| v.stake >= minimum_stake)
700 .map(|(addr, v)| (*addr, v.stake))
701 .collect();
702
703 tracing::info!(
704 count = valid_stakers.len(),
705 "Number of validators above minimum stake threshold"
706 );
707
708 valid_stakers.sort_by_key(|(_, stake)| std::cmp::Reverse(*stake));
710
711 if valid_stakers.len() > MAX_VALIDATORS {
712 valid_stakers.truncate(MAX_VALIDATORS);
713 }
714
715 let selected_addresses: HashSet<_> = valid_stakers.iter().map(|(addr, _)| *addr).collect();
716 let selected_validators: AuthenticatedValidatorMap = valid_validators
717 .into_iter()
718 .filter(|(address, _)| selected_addresses.contains(address))
719 .collect();
720
721 tracing::info!(
722 final_count = selected_validators.len(),
723 "Selected active validator set"
724 );
725
726 Ok(selected_validators)
727}
728
729#[derive(Clone, Debug)]
730pub struct ValidatorSet {
731 pub all_validators: RegisteredValidatorMap,
732 pub active_validators: AuthenticatedValidatorMap,
733 pub stake_table_hash: Option<StakeTableHash>,
734}
735
736pub(crate) fn validator_set_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
738 events: I,
739) -> Result<ValidatorSet, StakeTableError> {
740 let (all_validators, stake_table_hash) = validators_from_l1_events(events)?;
741 let active_validators = select_active_validator_set(&all_validators)?;
742
743 let validator_set = ValidatorSet {
744 all_validators,
745 active_validators,
746 stake_table_hash: Some(stake_table_hash),
747 };
748
749 Ok(validator_set)
750}
751
752impl std::fmt::Debug for StakeTableEvent {
753 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
754 match self {
755 StakeTableEvent::Register(event) => write!(f, "Register({:?})", event.account),
756 StakeTableEvent::RegisterV2(event) => write!(f, "RegisterV2({:?})", event.account),
757 StakeTableEvent::Deregister(event) => write!(f, "Deregister({:?})", event.validator),
758 StakeTableEvent::DeregisterV2(event) => {
759 write!(f, "DeregisterV2({:?})", event.validator)
760 },
761 StakeTableEvent::Delegate(event) => write!(f, "Delegate({:?})", event.delegator),
762 StakeTableEvent::Undelegate(event) => write!(f, "Undelegate({:?})", event.delegator),
763 StakeTableEvent::UndelegateV2(event) => {
764 write!(f, "UndelegateV2({:?})", event.delegator)
765 },
766 StakeTableEvent::KeyUpdate(event) => write!(f, "KeyUpdate({:?})", event.account),
767 StakeTableEvent::KeyUpdateV2(event) => write!(f, "KeyUpdateV2({:?})", event.account),
768 StakeTableEvent::CommissionUpdate(event) => {
769 write!(f, "CommissionUpdate({:?})", event.validator)
770 },
771 }
772 }
773}
774
775#[derive(Clone, derive_more::derive::Debug)]
776pub struct EpochCommittees {
778 non_epoch_committee: NonEpochCommittee,
780 state: HashMap<Epoch, EpochCommittee>,
782 all_validators: BTreeMap<Epoch, RegisteredValidatorMap>,
784 randomized_committees: BTreeMap<Epoch, RandomizedCommittee<StakeTableEntry<PubKey>>>,
786 da_committees: BTreeMap<u64, DaCommittee>,
788 first_epoch: Option<Epoch>,
789 epoch_height: u64,
790 fixed_block_reward: Option<RewardAmount>,
793 fetcher: Arc<Fetcher>,
794}
795
796#[derive(Debug, Clone)]
797struct DaCommittee {
798 committee: Vec<PeerConfig<SeqTypes>>,
799 indexed_committee: HashMap<PubKey, PeerConfig<SeqTypes>>,
800}
801
802impl Fetcher {
803 pub fn new(
804 peers: Arc<dyn StateCatchup>,
805 persistence: Arc<Mutex<dyn MembershipPersistence>>,
806 l1_client: L1Client,
807 chain_config: ChainConfig,
808 ) -> Self {
809 Self {
810 peers,
811 persistence,
812 l1_client,
813 chain_config: Arc::new(Mutex::new(chain_config)),
814 update_task: StakeTableUpdateTask(Mutex::new(None)).into(),
815 initial_supply: Arc::new(RwLock::new(None)),
816 }
817 }
818
819 pub async fn spawn_update_loop(&self) {
820 let mut update_task = self.update_task.0.lock().await;
821 if update_task.is_none() {
822 *update_task = Some(spawn(self.update_loop()));
823 }
824 }
825
826 fn update_loop(&self) -> impl Future<Output = ()> + use<> {
831 let span = tracing::warn_span!("Stake table update loop");
832 let self_clone = self.clone();
833 let state = self.l1_client.state.clone();
834 let l1_retry = self.l1_client.options().l1_retry_delay;
835 let update_delay = self.l1_client.options().stake_table_update_interval;
836 let chain_config = self.chain_config.clone();
837
838 async move {
839 let stake_contract_address = loop {
844 let contract = chain_config.lock().await.stake_table_contract;
845 match contract {
846 Some(addr) => break addr,
847 None => {
848 tracing::debug!(
849 "Stake table contract address not found. Retrying in {l1_retry:?}...",
850 );
851 },
852 }
853 sleep(l1_retry).await;
854 };
855
856 loop {
858 let finalized_block = loop {
859 let last_finalized = state.lock().await.last_finalized;
860 if let Some(block) = last_finalized {
861 break block;
862 }
863 tracing::debug!("Finalized block not yet available. Retrying in {l1_retry:?}",);
864 sleep(l1_retry).await;
865 };
866
867 tracing::debug!("Attempting to fetch stake table at L1 block {finalized_block:?}",);
868
869 loop {
870 match self_clone
871 .fetch_and_store_stake_table_events(stake_contract_address, finalized_block)
872 .await
873 {
874 Ok(events) => {
875 tracing::info!(
876 "Successfully fetched and stored stake table events at \
877 block={finalized_block:?}"
878 );
879 tracing::debug!("events={events:?}");
880 break;
881 },
882 Err(e) => {
883 tracing::error!(
884 "Error fetching stake table at block {finalized_block:?}. err= \
885 {e:#}",
886 );
887 sleep(l1_retry).await;
888 },
889 }
890 }
891
892 tracing::debug!("Waiting {update_delay:?} before next stake table update...",);
893 sleep(update_delay).await;
894 }
895 }
896 .instrument(span)
897 }
898
899 pub async fn fetch_and_store_stake_table_events(
906 &self,
907 contract: Address,
908 to_block: u64,
909 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
910 let (read_l1_offset, persistence_events) = {
911 let persistence_lock = self.persistence.lock().await;
912 persistence_lock.load_events(0, to_block).await?
913 };
914
915 tracing::info!("loaded events from storage to_block={to_block:?}");
916
917 if let Some(EventsPersistenceRead::Complete) = read_l1_offset {
920 return Ok(persistence_events);
921 }
922
923 let from_block = read_l1_offset
924 .map(|read| match read {
925 EventsPersistenceRead::UntilL1Block(block) => Ok(block + 1),
926 EventsPersistenceRead::Complete => Err(anyhow::anyhow!(
927 "Unexpected state. offset is complete after returning early"
928 )),
929 })
930 .transpose()?;
931
932 ensure!(
933 Some(to_block) >= from_block,
934 "to_block {to_block:?} is less than from_block {from_block:?}"
935 );
936
937 tracing::info!(%to_block, from_block = ?from_block, "Fetching events from contract");
938
939 let contract_events = Self::fetch_events_from_contract(
940 self.l1_client.clone(),
941 contract,
942 from_block,
943 to_block,
944 )
945 .await?;
946
947 tracing::info!(
949 "storing {} new events in storage to_block={to_block:?}",
950 contract_events.len()
951 );
952 {
953 let persistence_lock = self.persistence.lock().await;
954 persistence_lock
955 .store_events(to_block, contract_events.clone())
956 .await
957 .inspect_err(|e| tracing::error!("failed to store events. err={e}"))?;
958 }
959
960 let mut events = match from_block {
961 Some(_) => persistence_events
962 .into_iter()
963 .chain(contract_events)
964 .collect(),
965 None => contract_events,
966 };
967
968 let len_before_dedup = events.len();
973 events.dedup();
974 let len_after_dedup = events.len();
975 if len_before_dedup != len_after_dedup {
976 tracing::warn!("Duplicate events found and removed. This should not normally happen.")
977 }
978
979 Ok(events)
980 }
981
982 fn validate_event(event: &StakeTableV2Events, log: &Log) -> Result<bool, StakeTableError> {
989 match event {
990 StakeTableV2Events::ConsensusKeysUpdatedV2(evt) => {
991 if let Err(err) = evt.authenticate() {
992 tracing::warn!(
993 %err,
994 "Failed to authenticate ConsensusKeysUpdatedV2 event: {}",
995 log.display()
996 );
997 return Ok(false);
998 }
999 },
1000 StakeTableV2Events::CommissionUpdated(CommissionUpdated {
1001 validator,
1002 newCommission,
1003 ..
1004 }) => {
1005 if *newCommission > COMMISSION_BASIS_POINTS {
1006 return Err(StakeTableError::InvalidCommission(
1007 *validator,
1008 *newCommission,
1009 ));
1010 }
1011 },
1012 _ => {},
1013 }
1014
1015 Ok(true)
1016 }
1017
1018 fn block_range_chunks(
1020 from_block: u64,
1021 to_block: u64,
1022 chunk_size: u64,
1023 ) -> impl Iterator<Item = (u64, u64)> {
1024 let mut start = from_block;
1025 let end = to_block;
1026 std::iter::from_fn(move || {
1027 let chunk_end = min(start + chunk_size - 1, end);
1028 if chunk_end < start {
1029 return None;
1030 }
1031 let chunk = (start, chunk_end);
1032 start = chunk_end + 1;
1033 Some(chunk)
1034 })
1035 }
1036
1037 pub async fn fetch_events_from_contract(
1039 l1_client: L1Client,
1040 contract: Address,
1041 from_block: Option<u64>,
1042 to_block: u64,
1043 ) -> Result<Vec<(EventKey, StakeTableEvent)>, StakeTableError> {
1044 let stake_table_contract = StakeTableV2::new(contract, l1_client.provider.clone());
1045 let max_retry_duration = l1_client.options().l1_events_max_retry_duration;
1046 let retry_delay = l1_client.options().l1_retry_delay;
1047 let from_block = match from_block {
1050 Some(block) => block,
1051 None => {
1052 let start = Instant::now();
1053 loop {
1054 match stake_table_contract.initializedAtBlock().call().await {
1055 Ok(init_block) => break init_block.to::<u64>(),
1056 Err(err) => {
1057 if start.elapsed() >= max_retry_duration {
1058 panic!(
1059 "Failed to retrieve initial block after `{}`: {err}",
1060 format_duration(max_retry_duration)
1061 );
1062 }
1063 tracing::warn!(%err, "Failed to retrieve initial block, retrying...");
1064 sleep(retry_delay).await;
1065 },
1066 }
1067 }
1068 },
1069 };
1070
1071 let chunk_size = l1_client.options().l1_events_max_block_range;
1075 let chunks = Self::block_range_chunks(from_block, to_block, chunk_size);
1076
1077 let mut events = vec![];
1078
1079 for (from, to) in chunks {
1080 let provider = l1_client.provider.clone();
1081
1082 tracing::debug!(from, to, "fetch all stake table events in range");
1083 let logs: Vec<Log> = retry(
1086 retry_delay,
1087 max_retry_duration,
1088 "stake table events fetch",
1089 move || {
1090 let provider = provider.clone();
1091
1092 Box::pin(async move {
1093 let filter = Filter::new()
1094 .events([
1095 ValidatorRegistered::SIGNATURE,
1096 ValidatorRegisteredV2::SIGNATURE,
1097 ValidatorExit::SIGNATURE,
1098 ValidatorExitV2::SIGNATURE,
1099 Delegated::SIGNATURE,
1100 Undelegated::SIGNATURE,
1101 UndelegatedV2::SIGNATURE,
1102 ConsensusKeysUpdated::SIGNATURE,
1103 ConsensusKeysUpdatedV2::SIGNATURE,
1104 CommissionUpdated::SIGNATURE,
1105 ])
1106 .address(contract)
1107 .from_block(from)
1108 .to_block(to);
1109 provider.get_logs(&filter).await
1110 })
1111 },
1112 )
1113 .await;
1114
1115 let chunk_events = logs
1116 .into_iter()
1117 .filter_map(|log| {
1118 let event =
1119 StakeTableV2Events::decode_raw_log(log.topics(), &log.data().data).ok()?;
1120 match Self::validate_event(&event, &log) {
1121 Ok(true) => Some(Ok((event, log))),
1122 Ok(false) => None,
1123 Err(e) => Some(Err(e)),
1124 }
1125 })
1126 .collect::<Result<Vec<_>, _>>()?;
1127
1128 events.extend(chunk_events);
1129 }
1130
1131 sort_stake_table_events(events).map_err(Into::into)
1132 }
1133
1134 pub async fn fetch_all_validators_from_contract(
1136 l1_client: L1Client,
1137 contract: Address,
1138 to_block: u64,
1139 ) -> anyhow::Result<(RegisteredValidatorMap, StakeTableHash)> {
1140 let events = Self::fetch_events_from_contract(l1_client, contract, None, to_block).await?;
1141
1142 validators_from_l1_events(events.into_iter().map(|(_, e)| e))
1144 .context("failed to construct validators set from l1 events")
1145 }
1146
1147 pub async fn fetch_fixed_block_reward(&self) -> Result<RewardAmount, FetchRewardError> {
1151 let initial_supply = *self.initial_supply.read().await;
1153 let initial_supply = match initial_supply {
1154 Some(supply) => supply,
1155 None => self.fetch_and_update_initial_supply().await?,
1156 };
1157
1158 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
1159 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
1160 .ok_or(FetchRewardError::DivisionByZero(
1161 "COMMISSION_BASIS_POINTS is zero",
1162 ))?;
1163
1164 Ok(RewardAmount(reward))
1165 }
1166
1167 pub async fn fetch_and_update_initial_supply(&self) -> Result<U256, FetchRewardError> {
1182 tracing::info!("Fetching token initial supply");
1183 let chain_config = *self.chain_config.lock().await;
1184
1185 let stake_table_contract = chain_config
1186 .stake_table_contract
1187 .ok_or(FetchRewardError::MissingStakeTableContract)?;
1188
1189 let provider = self.l1_client.provider.clone();
1190 let stake_table = StakeTableV2::new(stake_table_contract, provider.clone());
1191
1192 let stake_table_init_block = stake_table
1196 .initializedAtBlock()
1197 .block(BlockId::finalized())
1198 .call()
1199 .await
1200 .map_err(FetchRewardError::ContractCall)?
1201 .to::<u64>();
1202
1203 tracing::info!("stake table init block ={stake_table_init_block}");
1204
1205 let token_address = stake_table
1206 .token()
1207 .block(BlockId::finalized())
1208 .call()
1209 .await
1210 .map_err(FetchRewardError::TokenAddressFetch)?;
1211
1212 let token = EspToken::new(token_address, provider.clone());
1213
1214 let init_logs = token
1217 .Initialized_filter()
1218 .from_block(0u64)
1219 .to_block(BlockNumberOrTag::Finalized)
1220 .query()
1221 .await;
1222
1223 let init_log = match init_logs {
1224 Ok(init_logs) => {
1225 if init_logs.is_empty() {
1226 tracing::error!(
1227 "Token Initialized event logs are empty. This should never happen"
1228 );
1229 return Err(FetchRewardError::MissingInitializedEvent);
1230 }
1231
1232 let (_, init_log) = init_logs[0].clone();
1233
1234 tracing::debug!(tx_hash = ?init_log.transaction_hash, "Found token `Initialized` event");
1235 init_log
1236 },
1237 Err(err) => {
1238 tracing::warn!(
1239 "RPC returned error {err:?}. will fallback to scanning over fixed block range"
1240 );
1241 self.scan_token_contract_initialized_event_log(
1242 stake_table_init_block,
1243 token.clone(),
1244 )
1245 .await?
1246 },
1247 };
1248
1249 let init_block = init_log
1250 .block_number
1251 .ok_or(FetchRewardError::MissingBlockNumber)?;
1252
1253 let init_tx_hash =
1254 init_log
1255 .transaction_hash
1256 .ok_or_else(|| FetchRewardError::MissingTransactionHash {
1257 init_log: init_log.clone().into(),
1258 })?;
1259
1260 let transfer_logs = token
1264 .Transfer_filter()
1265 .from_block(init_block)
1266 .to_block(init_block)
1267 .query()
1268 .await
1269 .map_err(FetchRewardError::TransferEventQuery)?;
1270
1271 let (mint_transfer, _) = transfer_logs
1272 .iter()
1273 .find(|(transfer, log)| {
1274 log.transaction_hash == Some(init_tx_hash) && transfer.from == Address::ZERO
1275 })
1276 .ok_or(FetchRewardError::MissingTransferEvent)?;
1277
1278 tracing::debug!("mint transfer event ={mint_transfer:?}");
1279
1280 let initial_supply = mint_transfer.value;
1281
1282 tracing::info!("Initial token amount: {} ESP", format_ether(initial_supply));
1283
1284 let mut writer = self.initial_supply.write().await;
1285 *writer = Some(initial_supply);
1286
1287 Ok(initial_supply)
1288 }
1289
1290 pub async fn scan_token_contract_initialized_event_log(
1298 &self,
1299 stake_table_init_block: u64,
1300 token: EspTokenInstance<L1Provider>,
1301 ) -> Result<Log, FetchRewardError> {
1302 let max_events_range = self.l1_client.options().l1_events_max_block_range;
1303 const MAX_BLOCKS_SCANNED: u64 = 200_000;
1304 let mut total_scanned = 0;
1305
1306 let mut from_block = stake_table_init_block.saturating_sub(max_events_range);
1307 let mut to_block = stake_table_init_block;
1308
1309 loop {
1310 if total_scanned >= MAX_BLOCKS_SCANNED {
1311 tracing::error!(
1312 total_scanned,
1313 "Exceeded maximum scan range while searching for token Initialized event"
1314 );
1315 return Err(FetchRewardError::ExceededMaxScanRange(MAX_BLOCKS_SCANNED));
1316 }
1317
1318 let init_logs = token
1319 .Initialized_filter()
1320 .from_block(from_block)
1321 .to_block(to_block)
1322 .query()
1323 .await
1324 .map_err(FetchRewardError::ScanQueryFailed)?;
1325
1326 if !init_logs.is_empty() {
1327 let (_, init_log) = init_logs[0].clone();
1328 tracing::info!(
1329 from_block,
1330 tx_hash = ?init_log.transaction_hash,
1331 "Found token Initialized event during scan"
1332 );
1333 return Ok(init_log);
1334 }
1335
1336 total_scanned += max_events_range;
1337 from_block = from_block.saturating_sub(max_events_range);
1338 to_block = to_block.saturating_sub(max_events_range);
1339 }
1340 }
1341
1342 pub async fn update_chain_config(&self, header: &Header) -> anyhow::Result<()> {
1343 let chain_config = self.get_chain_config(header).await?;
1344 *self.chain_config.lock().await = chain_config;
1346
1347 Ok(())
1348 }
1349
1350 pub async fn fetch(&self, epoch: Epoch, header: &Header) -> anyhow::Result<ValidatorSet> {
1351 let chain_config = *self.chain_config.lock().await;
1352 let Some(address) = chain_config.stake_table_contract else {
1353 bail!("No stake table contract address found in Chain config");
1354 };
1355
1356 let Some(l1_finalized_block_info) = header.l1_finalized() else {
1357 bail!(
1358 "The epoch root for epoch {epoch} is missing the L1 finalized block info. This is \
1359 a fatal error. Consensus is blocked and will not recover."
1360 );
1361 };
1362
1363 let events = match self
1364 .fetch_and_store_stake_table_events(address, l1_finalized_block_info.number())
1365 .await
1366 .map_err(GetStakeTablesError::L1ClientFetchError)
1367 {
1368 Ok(events) => events,
1369 Err(e) => {
1370 bail!("failed to fetch stake table events {e:?}");
1371 },
1372 };
1373
1374 match validator_set_from_l1_events(events.into_iter().map(|(_, e)| e)) {
1375 Ok(res) => Ok(res),
1376 Err(e) => {
1377 bail!("failed to construct stake table {e:?}");
1378 },
1379 }
1380 }
1381
1382 pub(crate) async fn get_chain_config(&self, header: &Header) -> anyhow::Result<ChainConfig> {
1385 let chain_config = self.chain_config.lock().await;
1386 let peers = self.peers.clone();
1387 let header_cf = header.chain_config();
1388 if chain_config.commit() == header_cf.commit() {
1389 return Ok(*chain_config);
1390 }
1391
1392 let cf = match header_cf.resolve() {
1393 Some(cf) => cf,
1394 None => peers
1395 .fetch_chain_config(header_cf.commit())
1396 .await
1397 .inspect_err(|err| {
1398 tracing::error!("failed to get chain_config from peers. err: {err:?}");
1399 })?,
1400 };
1401
1402 Ok(cf)
1403 }
1404
1405 #[cfg(any(test, feature = "testing"))]
1406 pub fn mock() -> Self {
1407 use crate::{mock, v0_1::NoStorage};
1408 let chain_config = ChainConfig::default();
1409 let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
1410 .expect("Failed to create L1 client");
1411
1412 let peers = Arc::new(mock::MockStateCatchup::default());
1413 let persistence = NoStorage;
1414
1415 Self::new(peers, Arc::new(Mutex::new(persistence)), l1, chain_config)
1416 }
1417}
1418
1419async fn retry<F, T, E>(
1420 retry_delay: Duration,
1421 max_duration: Duration,
1422 operation_name: &str,
1423 mut operation: F,
1424) -> T
1425where
1426 F: FnMut() -> BoxFuture<'static, Result<T, E>>,
1427 E: std::fmt::Display,
1428{
1429 let start = Instant::now();
1430 loop {
1431 match operation().await {
1432 Ok(result) => return result,
1433 Err(err) => {
1434 if start.elapsed() >= max_duration {
1435 panic!(
1436 r#"
1437 Failed to complete operation `{operation_name}` after `{}`.
1438 error: {err}
1439
1440
1441 This might be caused by:
1442 - The current block range being too large for your RPC provider.
1443 - The event query returning more data than your RPC allows as
1444 some RPC providers limit the number of events returned.
1445 - RPC provider outage
1446
1447 Suggested solution:
1448 - Reduce the value of the environment variable
1449 `ESPRESSO_SEQUENCER_L1_EVENTS_MAX_BLOCK_RANGE` to query smaller ranges.
1450 - Add multiple RPC providers
1451 - Use a different RPC provider with higher rate limits."#,
1452 format_duration(max_duration)
1453 );
1454 }
1455 tracing::warn!(%err, "Retrying `{operation_name}` after error");
1456 sleep(retry_delay).await;
1457 },
1458 }
1459 }
1460}
1461
1462#[derive(Clone, Debug)]
1464struct NonEpochCommittee {
1465 eligible_leaders: Vec<PeerConfig<SeqTypes>>,
1469
1470 stake_table: Vec<PeerConfig<SeqTypes>>,
1472
1473 da_committee: DaCommittee,
1474
1475 indexed_stake_table: HashMap<PubKey, PeerConfig<SeqTypes>>,
1477}
1478
1479#[derive(Clone, Debug)]
1481pub struct EpochCommittee {
1482 eligible_leaders: Vec<PeerConfig<SeqTypes>>,
1486 stake_table: IndexMap<PubKey, PeerConfig<SeqTypes>>,
1488 validators: AuthenticatedValidatorMap,
1489 address_mapping: HashMap<BLSPubKey, Address>,
1490 block_reward: Option<RewardAmount>,
1491 stake_table_hash: Option<StakeTableHash>,
1492 header: Option<Header>,
1493}
1494
1495impl EpochCommittees {
1496 pub fn first_epoch(&self) -> Option<Epoch> {
1497 self.first_epoch
1498 }
1499
1500 pub fn fetcher(&self) -> &Fetcher {
1501 &self.fetcher
1502 }
1503
1504 pub fn fixed_block_reward(&self) -> Option<RewardAmount> {
1505 self.fixed_block_reward
1506 }
1507
1508 async fn fetch_and_update_fixed_block_reward(
1513 membership: Arc<RwLock<Self>>,
1514 epoch: EpochNumber,
1515 ) -> anyhow::Result<RewardAmount> {
1516 let membership_reader = membership.upgradable_read().await;
1517 let fetcher = membership_reader.fetcher.clone();
1518 match membership_reader.fixed_block_reward {
1519 Some(reward) => Ok(reward),
1520 None => {
1521 tracing::warn!(%epoch,
1522 "Block reward is None. attempting to fetch it from L1",
1523
1524 );
1525 let block_reward = fetcher
1526 .fetch_fixed_block_reward()
1527 .await
1528 .inspect_err(|err| {
1529 tracing::error!(?epoch, ?err, "failed to fetch block_reward");
1530 })?;
1531 let mut writer = RwLockUpgradableReadGuard::upgrade(membership_reader).await;
1532 writer.fixed_block_reward = Some(block_reward);
1533 Ok(block_reward)
1534 },
1535 }
1536 }
1537
1538 pub fn compute_block_reward(
1539 epoch: &EpochNumber,
1540 total_supply: U256,
1541 total_stake: U256,
1542 avg_block_time_ms: u64,
1543 ) -> anyhow::Result<RewardAmount> {
1544 let total_stake_bd = BigDecimal::from_str(&total_stake.to_string())?;
1546 let total_supply_bd = BigDecimal::from_str(&(total_supply.to_string()))?;
1547
1548 tracing::debug!(?epoch, "total_stake={total_stake}");
1549 tracing::debug!(?epoch, "total_supply_bd={total_supply_bd}");
1550
1551 let (proportion, reward_rate) =
1552 calculate_proportion_staked_and_reward_rate(&total_stake_bd, &total_supply_bd)?;
1553 let inflation_rate = proportion * reward_rate;
1554
1555 tracing::debug!(?epoch, "inflation_rate={inflation_rate:?}");
1556
1557 let blocks_per_year = MILLISECONDS_PER_YEAR
1558 .checked_div(avg_block_time_ms.into())
1559 .context("avg_block_time_ms is zero")?;
1560
1561 tracing::debug!(?epoch, "blocks_per_year={blocks_per_year:?}");
1562
1563 ensure!(!blocks_per_year.is_zero(), "blocks per year is zero");
1564 let block_reward = (total_supply_bd * inflation_rate) / blocks_per_year;
1565
1566 let block_reward_u256 = U256::from_str(&block_reward.round(0).to_string())?;
1567
1568 Ok(block_reward_u256.into())
1569 }
1570
1571 pub async fn fetch_and_calculate_block_reward(
1581 current_epoch: Epoch,
1582 coordinator: EpochMembershipCoordinator<SeqTypes>,
1583 ) -> anyhow::Result<RewardAmount> {
1584 let membership_read = coordinator.membership().read().await;
1585 let fixed_block_reward = membership_read.fixed_block_reward;
1586
1587 let committee = membership_read
1588 .state
1589 .get(¤t_epoch)
1590 .context(format!("committee not found for epoch={current_epoch:?}"))?
1591 .clone();
1592
1593 if let Some(reward) = committee.block_reward {
1595 return Ok(reward);
1596 }
1597
1598 let first_epoch = *membership_read.first_epoch().context(format!(
1599 "First epoch not initialized (current_epoch={current_epoch})"
1600 ))?;
1601
1602 drop(membership_read);
1603
1604 if *current_epoch <= first_epoch + 1 {
1605 bail!(
1606 "epoch is in first two epochs: current_epoch={current_epoch}, \
1607 first_epoch={first_epoch}"
1608 );
1609 }
1610
1611 let header = match committee.header.clone() {
1612 Some(header) => header,
1613 None => {
1614 let root_epoch = current_epoch.checked_sub(2).context(format!(
1615 "Epoch calculation underflow (current_epoch={current_epoch})"
1616 ))?;
1617
1618 tracing::info!(?root_epoch, "catchup epoch root header");
1619
1620 let membership = coordinator.membership();
1621 let leaf = Self::get_epoch_root(membership.clone(), EpochNumber::new(root_epoch))
1622 .await
1623 .with_context(|| {
1624 format!("Failed to get epoch root for root_epoch={root_epoch}")
1625 })?;
1626 leaf.block_header().clone()
1627 },
1628 };
1629
1630 if header.version() <= EPOCH_VERSION {
1631 return fixed_block_reward.context(format!(
1632 "Fixed block reward not found for current_epoch={current_epoch}"
1633 ));
1634 }
1635
1636 let prev_epoch_u64 = current_epoch.checked_sub(1).context(format!(
1637 "Underflow: cannot compute previous epoch when current_epoch={current_epoch}"
1638 ))?;
1639
1640 let prev_epoch = EpochNumber::new(prev_epoch_u64);
1641
1642 if *prev_epoch > first_epoch + 1
1645 && let Err(err) = coordinator.stake_table_for_epoch(Some(prev_epoch)).await
1646 {
1647 tracing::info!("failed to get membership for epoch={prev_epoch:?}: {err:#}");
1648
1649 coordinator
1650 .wait_for_catchup(prev_epoch)
1651 .await
1652 .context(format!("failed to catch up for epoch={prev_epoch}"))?;
1653 }
1654
1655 let membership_read = coordinator.membership().read().await;
1656
1657 membership_read
1658 .calculate_dynamic_block_reward(¤t_epoch, &header, &committee.validators)
1659 .await
1660 .with_context(|| {
1661 format!("dynamic block reward calculation failed for epoch={current_epoch}")
1662 })?
1663 .with_context(|| format!("dynamic block reward returned None. epoch={current_epoch}"))
1664 }
1665
1666 async fn calculate_dynamic_block_reward(
1673 &self,
1674 epoch: &Epoch,
1675 header: &Header,
1676 validators: &AuthenticatedValidatorMap,
1677 ) -> anyhow::Result<Option<RewardAmount>> {
1678 let epoch_height = self.epoch_height;
1679 let current_epoch = epoch_from_block_number(header.height(), epoch_height);
1680 let previous_epoch = current_epoch
1681 .checked_sub(1)
1682 .context("underflow: cannot get previous epoch when current_epoch is 0")?;
1683 tracing::debug!(?epoch, "previous_epoch={previous_epoch:?}");
1684
1685 let first_epoch = *self.first_epoch().context("first epoch is None")?;
1686
1687 if previous_epoch > first_epoch + 1
1690 && !self.has_stake_table(EpochNumber::new(previous_epoch))
1691 {
1692 tracing::warn!(?previous_epoch, "missing stake table for previous epoch");
1693 return Ok(None);
1694 }
1695
1696 let fetcher = self.fetcher.clone();
1697
1698 let previous_reward_distributed = header
1699 .total_reward_distributed()
1700 .context("Invalid block header: missing total_reward_distributed field")?;
1701
1702 let total_stake: U256 = validators.values().map(|v| v.stake).sum();
1704 let initial_supply = *fetcher.initial_supply.read().await;
1705 let initial_supply = match initial_supply {
1706 Some(supply) => supply,
1707 None => fetcher.fetch_and_update_initial_supply().await?,
1708 };
1709 let total_supply = initial_supply
1710 .checked_add(previous_reward_distributed.0)
1711 .context("initial_supply + previous_reward_distributed overflow")?;
1712
1713 let curr_ts = header.timestamp_millis_internal();
1715 tracing::debug!(?epoch, "curr_ts={curr_ts:?}");
1716
1717 let average_block_time_ms = if previous_epoch <= first_epoch + 1 {
1721 ASSUMED_BLOCK_TIME_SECONDS as u64 * 1000 } else {
1723 let next_epoch = epoch
1727 .checked_sub(1)
1728 .context("underflow: cannot get next epoch when epoch is 0")?;
1729 let prev_ts = match self.get_header(EpochNumber::new(next_epoch)) {
1730 Some(header) => header.timestamp_millis_internal(),
1731 None => {
1732 tracing::info!(
1733 "Calculating rewards for epoch {}, we have no root leaf header for epoch \
1734 - 1. Fetching from peers",
1735 epoch
1736 );
1737
1738 let root_height = header.height().checked_sub(epoch_height).context(
1739 "Epoch height is greater than block height. cannot compute previous epoch \
1740 root height",
1741 )?;
1742
1743 let prev_stake_table = self
1744 .get_stake_table(&Some(EpochNumber::new(previous_epoch)))
1745 .context("Stake table not found")?
1746 .into();
1747
1748 let success_threshold =
1749 self.success_threshold(Some(EpochNumber::new(previous_epoch)));
1750
1751 fetcher
1752 .peers
1753 .fetch_leaf(root_height, prev_stake_table, success_threshold)
1754 .await
1755 .context("Epoch root leaf not found")?
1756 .block_header()
1757 .timestamp_millis_internal()
1758 },
1759 };
1760
1761 let time_diff = curr_ts.checked_sub(prev_ts).context(
1762 "Current timestamp is earlier than previous. underflow in block time calculation",
1763 )?;
1764
1765 time_diff
1766 .checked_div(epoch_height)
1767 .context("Epoch height is zero. cannot compute average block time")?
1768 };
1769 tracing::info!(?epoch, %total_supply, %total_stake, %average_block_time_ms,
1770 "dynamic block reward parameters");
1771
1772 let block_reward =
1773 Self::compute_block_reward(epoch, total_supply, total_stake, average_block_time_ms)?;
1774
1775 Ok(Some(block_reward))
1776 }
1777
1778 pub fn epoch_block_reward(&self, epoch: EpochNumber) -> Option<RewardAmount> {
1780 self.state
1781 .get(&epoch)
1782 .and_then(|committee| committee.block_reward)
1783 }
1784
1785 pub fn get_validator_index(&self, epoch: &EpochNumber, bls_key: &PubKey) -> Option<usize> {
1790 self.state
1791 .get(epoch)
1792 .and_then(|committee| committee.stake_table.get_index_of(bls_key))
1793 }
1794
1795 fn insert_committee(
1801 &mut self,
1802 epoch: EpochNumber,
1803 validators: AuthenticatedValidatorMap,
1804 block_reward: Option<RewardAmount>,
1805 hash: Option<StakeTableHash>,
1806 header: Option<Header>,
1807 ) {
1808 let mut address_mapping = HashMap::new();
1809 let stake_table: IndexMap<PubKey, PeerConfig<SeqTypes>> = validators
1810 .values()
1811 .map(|v| {
1812 address_mapping.insert(v.stake_table_key, v.account);
1813 (
1814 v.stake_table_key,
1815 PeerConfig {
1816 stake_table_entry: BLSPubKey::stake_table_entry(
1817 &v.stake_table_key,
1818 v.stake,
1819 ),
1820 state_ver_key: v.state_ver_key.clone(),
1821 connect_info: v.x25519_key.and_then(|p| {
1822 let a = v.p2p_addr.clone()?;
1823 Some(PeerConnectInfo {
1824 x25519_key: p,
1825 p2p_addr: a,
1826 })
1827 }),
1828 },
1829 )
1830 })
1831 .collect();
1832
1833 let eligible_leaders: Vec<PeerConfig<SeqTypes>> =
1834 stake_table.iter().map(|(_, l)| l.clone()).collect();
1835
1836 self.state.insert(
1837 epoch,
1838 EpochCommittee {
1839 eligible_leaders,
1840 stake_table,
1841 validators,
1842 address_mapping,
1843 block_reward,
1844 stake_table_hash: hash,
1845 header,
1846 },
1847 );
1848 }
1849
1850 pub fn active_validators(&self, epoch: &Epoch) -> anyhow::Result<AuthenticatedValidatorMap> {
1851 Ok(self
1852 .state
1853 .get(epoch)
1854 .context("state for found")?
1855 .validators
1856 .clone())
1857 }
1858
1859 pub fn address(&self, epoch: &Epoch, bls_key: BLSPubKey) -> anyhow::Result<Address> {
1860 let mapping = self
1861 .state
1862 .get(epoch)
1863 .context("state for found")?
1864 .address_mapping
1865 .clone();
1866
1867 Ok(*mapping.get(&bls_key).context(format!(
1868 "failed to get ethereum address for bls key {bls_key}. epoch={epoch}"
1869 ))?)
1870 }
1871
1872 pub fn get_validator_config(
1873 &self,
1874 epoch: &Epoch,
1875 key: BLSPubKey,
1876 ) -> anyhow::Result<AuthenticatedValidator<BLSPubKey>> {
1877 let address = self.address(epoch, key)?;
1878 let validators = self.active_validators(epoch)?;
1879 validators
1880 .get(&address)
1881 .context("validator not found")
1882 .cloned()
1883 }
1884
1885 pub fn new_stake(
1887 committee_members: Vec<PeerConfig<SeqTypes>>,
1890 da_members: Vec<PeerConfig<SeqTypes>>,
1891 fixed_block_reward: Option<RewardAmount>,
1892 fetcher: Fetcher,
1893 epoch_height: u64,
1894 ) -> Self {
1895 let stake_table: Vec<_> = committee_members
1897 .iter()
1898 .filter(|&peer_config| peer_config.stake_table_entry.stake() > U256::ZERO)
1899 .cloned()
1900 .collect();
1901
1902 let eligible_leaders = stake_table.clone();
1903 let da_members: Vec<_> = da_members
1905 .iter()
1906 .filter(|&peer_config| peer_config.stake_table_entry.stake() > U256::ZERO)
1907 .cloned()
1908 .collect();
1909
1910 let indexed_stake_table: HashMap<PubKey, _> = stake_table
1912 .iter()
1913 .map(|peer_config| {
1914 (
1915 PubKey::public_key(&peer_config.stake_table_entry),
1916 peer_config.clone(),
1917 )
1918 })
1919 .collect();
1920
1921 let indexed_da_members: HashMap<PubKey, _> = da_members
1923 .iter()
1924 .map(|peer_config| {
1925 (
1926 PubKey::public_key(&peer_config.stake_table_entry),
1927 peer_config.clone(),
1928 )
1929 })
1930 .collect();
1931
1932 let da_committee = DaCommittee {
1933 committee: da_members,
1934 indexed_committee: indexed_da_members,
1935 };
1936
1937 let members = NonEpochCommittee {
1938 eligible_leaders,
1939 stake_table,
1940 indexed_stake_table,
1941 da_committee,
1942 };
1943
1944 let mut map = HashMap::new();
1945 let epoch_committee = EpochCommittee {
1946 eligible_leaders: members.eligible_leaders.clone(),
1947 stake_table: members
1948 .stake_table
1949 .iter()
1950 .map(|x| (PubKey::public_key(&x.stake_table_entry), x.clone()))
1951 .collect(),
1952 validators: Default::default(),
1953 address_mapping: HashMap::new(),
1954 block_reward: Default::default(),
1955 stake_table_hash: None,
1956 header: None,
1957 };
1958 map.insert(Epoch::genesis(), epoch_committee.clone());
1959 map.insert(Epoch::genesis() + 1u64, epoch_committee.clone());
1961
1962 Self {
1963 non_epoch_committee: members,
1964 da_committees: BTreeMap::new(),
1965 state: map,
1966 all_validators: BTreeMap::new(),
1967 randomized_committees: BTreeMap::new(),
1968 first_epoch: None,
1969 fixed_block_reward,
1970 fetcher: Arc::new(fetcher),
1971 epoch_height,
1972 }
1973 }
1974
1975 pub async fn reload_stake(&mut self, limit: u64) {
1976 match self.fetcher.fetch_fixed_block_reward().await {
1977 Ok(block_reward) => {
1978 tracing::info!("Fetched block reward: {block_reward}");
1979 self.fixed_block_reward = Some(block_reward);
1980 },
1981 Err(err) => {
1982 tracing::warn!(
1983 "Failed to fetch the block reward when reloading the stake tables: {err}"
1984 );
1985 },
1986 }
1987
1988 let loaded_stake = match self
1990 .fetcher
1991 .persistence
1992 .lock()
1993 .await
1994 .load_latest_stake(limit)
1995 .await
1996 {
1997 Ok(Some(loaded)) => loaded,
1998 Ok(None) => {
1999 tracing::warn!("No stake table history found in persistence!");
2000 return;
2001 },
2002 Err(e) => {
2003 tracing::error!("Failed to load stake table history from persistence: {e}");
2004 return;
2005 },
2006 };
2007
2008 for (epoch, (validators, block_reward), stake_table_hash) in loaded_stake {
2009 self.insert_committee(epoch, validators, block_reward, stake_table_hash, None);
2010 }
2011 }
2012
2013 fn get_stake_table(&self, epoch: &Option<Epoch>) -> Option<Vec<PeerConfig<SeqTypes>>> {
2014 if let Some(epoch) = epoch {
2015 self.state
2016 .get(epoch)
2017 .map(|committee| committee.stake_table.clone().into_values().collect())
2018 } else {
2019 Some(self.non_epoch_committee.stake_table.clone())
2020 }
2021 }
2022
2023 fn get_da_committee(&self, epoch: Option<Epoch>) -> DaCommittee {
2024 if let Some(e) = epoch {
2025 self.da_committees
2027 .range((Bound::Included(&0), Bound::Included(&*e)))
2028 .last()
2029 .map(|(_, committee)| committee.clone())
2030 .unwrap_or(self.non_epoch_committee.da_committee.clone())
2031 } else {
2032 self.non_epoch_committee.da_committee.clone()
2033 }
2034 }
2035
2036 fn get_header(&self, epoch: Epoch) -> Option<&Header> {
2038 self.state
2039 .get(&epoch)
2040 .and_then(|committee| committee.header.as_ref())
2041 }
2042}
2043
2044pub fn calculate_proportion_staked_and_reward_rate(
2054 total_stake: &BigDecimal,
2055 total_supply: &BigDecimal,
2056) -> anyhow::Result<(BigDecimal, BigDecimal)> {
2057 if total_supply.is_zero() {
2058 return Err(anyhow::anyhow!("Total supply cannot be zero"));
2059 }
2060
2061 let proportion_staked = total_stake / total_supply;
2062
2063 if proportion_staked < BigDecimal::zero() || proportion_staked > BigDecimal::one() {
2064 return Err(anyhow::anyhow!("Stake ratio p must be in the range [0, 1]"));
2065 }
2066
2067 let two = BigDecimal::from_u32(2).unwrap();
2068 let min_stake_ratio = BigDecimal::from_str("0.01")?;
2069 let numerator = BigDecimal::from_str("0.03")?;
2070
2071 let denominator = (&two * (&proportion_staked).max(&min_stake_ratio))
2072 .sqrt()
2073 .context("Failed to compute sqrt in R(p)")?;
2074
2075 let reward_rate = numerator / denominator;
2076
2077 tracing::debug!("rp={reward_rate}");
2078
2079 Ok((proportion_staked, reward_rate))
2080}
2081
2082#[derive(Error, Debug)]
2083enum GetStakeTablesError {
2085 #[error("Error fetching from L1: {0}")]
2086 L1ClientFetchError(anyhow::Error),
2087}
2088
2089#[derive(Error, Debug)]
2090#[error("Could not lookup leader")] pub struct LeaderLookupError;
2092
2093impl Membership<SeqTypes> for EpochCommittees {
2095 type Error = LeaderLookupError;
2096 type Storage = ();
2097 type StakeTableHash = StakeTableState;
2098
2099 fn new<I: NodeImplementation<SeqTypes>>(
2101 _committee_members: Vec<PeerConfig<SeqTypes>>,
2104 _da_members: Vec<PeerConfig<SeqTypes>>,
2105 _storage: Self::Storage,
2106 _network: Arc<<I as NodeImplementation<SeqTypes>>::Network>,
2107 _public_key: <SeqTypes as NodeType>::SignatureKey,
2108 _epoch_height: u64,
2109 ) -> Self {
2110 panic!("This function has been replaced with new_stake()");
2111 }
2112
2113 fn stake_table(&self, epoch: Option<Epoch>) -> HSStakeTable<SeqTypes> {
2115 self.get_stake_table(&epoch).unwrap_or_default().into()
2116 }
2117 fn da_stake_table(&self, epoch: Option<Epoch>) -> HSStakeTable<SeqTypes> {
2119 self.get_da_committee(epoch).committee.clone().into()
2120 }
2121
2122 fn committee_members(
2124 &self,
2125 _view_number: ViewNumber,
2126 epoch: Option<Epoch>,
2127 ) -> BTreeSet<PubKey> {
2128 let stake_table = self.stake_table(epoch);
2129 stake_table
2130 .iter()
2131 .map(|x| PubKey::public_key(&x.stake_table_entry))
2132 .collect()
2133 }
2134
2135 fn da_committee_members(
2137 &self,
2138 _view_number: ViewNumber,
2139 epoch: Option<Epoch>,
2140 ) -> BTreeSet<PubKey> {
2141 self.da_stake_table(epoch)
2142 .iter()
2143 .map(|peer_config| PubKey::public_key(&peer_config.stake_table_entry))
2144 .collect()
2145 }
2146
2147 fn stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> Option<PeerConfig<SeqTypes>> {
2149 if let Some(epoch) = epoch {
2151 self.state
2152 .get(&epoch)
2153 .and_then(|h| h.stake_table.get(pub_key))
2154 .cloned()
2155 } else {
2156 self.non_epoch_committee
2157 .indexed_stake_table
2158 .get(pub_key)
2159 .cloned()
2160 }
2161 }
2162
2163 fn da_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> Option<PeerConfig<SeqTypes>> {
2165 self.get_da_committee(epoch)
2166 .indexed_committee
2167 .get(pub_key)
2168 .cloned()
2169 }
2170
2171 fn has_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> bool {
2173 self.stake(pub_key, epoch)
2174 .map(|x| x.stake_table_entry.stake() > U256::ZERO)
2175 .unwrap_or_default()
2176 }
2177
2178 fn has_da_stake(&self, pub_key: &PubKey, epoch: Option<Epoch>) -> bool {
2180 self.da_stake(pub_key, epoch)
2181 .map(|x| x.stake_table_entry.stake() > U256::ZERO)
2182 .unwrap_or_default()
2183 }
2184
2185 fn lookup_leader(
2198 &self,
2199 view_number: ViewNumber,
2200 epoch: Option<Epoch>,
2201 ) -> Result<PubKey, Self::Error> {
2202 match (self.first_epoch(), epoch) {
2203 (Some(first_epoch), Some(epoch)) => {
2204 if epoch < first_epoch {
2205 tracing::error!(
2206 "lookup_leader called with epoch {} before first epoch {}",
2207 epoch,
2208 first_epoch,
2209 );
2210 return Err(LeaderLookupError);
2211 }
2212 let Some(randomized_committee) = self.randomized_committees.get(&epoch) else {
2213 tracing::error!(
2214 "We are missing the randomized committee for epoch {}",
2215 epoch
2216 );
2217 return Err(LeaderLookupError);
2218 };
2219
2220 Ok(PubKey::public_key(&select_randomized_leader(
2221 randomized_committee,
2222 *view_number,
2223 )))
2224 },
2225 (_, None) => {
2226 let leaders = &self.non_epoch_committee.eligible_leaders;
2227
2228 let index = *view_number as usize % leaders.len();
2229 let res = leaders[index].clone();
2230 Ok(PubKey::public_key(&res.stake_table_entry))
2231 },
2232 (None, Some(epoch)) => {
2233 tracing::error!(
2234 "lookup_leader called with epoch {} but we don't have a first epoch",
2235 epoch,
2236 );
2237 Err(LeaderLookupError)
2238 },
2239 }
2240 }
2241
2242 fn total_nodes(&self, epoch: Option<Epoch>) -> usize {
2244 self.stake_table(epoch).len()
2245 }
2246
2247 fn da_total_nodes(&self, epoch: Option<Epoch>) -> usize {
2249 self.da_stake_table(epoch).len()
2250 }
2251
2252 async fn add_epoch_root(
2256 membership: Arc<RwLock<Self>>,
2257 block_header: Header,
2258 ) -> anyhow::Result<()> {
2259 let block_number = block_header.block_number();
2260
2261 let membership_reader = membership.read().await;
2262 let epoch_height = membership_reader.epoch_height;
2263
2264 let epoch =
2265 Epoch::new(epoch_from_block_number(block_number, membership_reader.epoch_height) + 2);
2266
2267 tracing::info!(?epoch, "adding epoch root. height={:?}", block_number);
2268
2269 if !is_epoch_root(block_number, epoch_height) {
2270 tracing::error!(
2271 "`add_epoch_root` was called with a block header that was not the root block for \
2272 an epoch. This should never happen. Header:\n\n{block_header:?}"
2273 );
2274 bail!(
2275 "Failed to add epoch root: block {block_number:?} is not a root block for an epoch"
2276 );
2277 }
2278
2279 let fetcher = membership_reader.fetcher.clone();
2280
2281 drop(membership_reader);
2282
2283 let version = block_header.version();
2284 fetcher.update_chain_config(&block_header).await?;
2286
2287 let mut block_reward = None;
2288 if version == EPOCH_VERSION {
2291 let reward =
2292 Self::fetch_and_update_fixed_block_reward(membership.clone(), epoch).await?;
2293 block_reward = Some(reward);
2294 }
2295
2296 let epoch_committee = {
2297 let membership_reader = membership.read().await;
2298 membership_reader.state.get(&epoch).cloned()
2299 };
2300
2301 let (active_validators, all_validators, stake_table_hash) = match epoch_committee {
2307 Some(committee)
2308 if committee.block_reward.is_some()
2309 && committee.header.is_some()
2310 && committee.stake_table_hash.is_some() =>
2311 {
2312 tracing::info!(
2313 ?epoch,
2314 "committee already has block reward, header, and stake table hash; skipping \
2315 add_epoch_root"
2316 );
2317 return Ok(());
2318 },
2319
2320 Some(committee) => {
2321 if let Some(reward) = committee.block_reward {
2322 block_reward = Some(reward);
2323 }
2324
2325 if let Some(hash) = committee.stake_table_hash {
2326 (committee.validators.clone(), Default::default(), Some(hash))
2327 } else {
2328 tracing::info!(
2330 "Stake table hash missing for epoch {epoch}. recalculating by fetching \
2331 from l1."
2332 );
2333 let set = fetcher.fetch(epoch, &block_header).await?;
2334 (
2335 set.active_validators,
2336 set.all_validators,
2337 set.stake_table_hash,
2338 )
2339 }
2340 },
2341
2342 None => {
2343 tracing::info!("Stake table missing for epoch {epoch}. Fetching from L1.");
2344 let set = fetcher.fetch(epoch, &block_header).await?;
2345 (
2346 set.active_validators,
2347 set.all_validators,
2348 set.stake_table_hash,
2349 )
2350 },
2351 };
2352
2353 if block_reward.is_none() && version >= DRB_AND_HEADER_UPGRADE_VERSION {
2357 tracing::info!(?epoch, "calculating dynamic block reward");
2358 let reader = membership.read().await;
2359 let reward = reader
2360 .calculate_dynamic_block_reward(&epoch, &block_header, &active_validators)
2361 .await?;
2362
2363 tracing::info!(?epoch, "calculated dynamic block reward = {reward:?}");
2364 block_reward = reward;
2365 }
2366
2367 let mut membership_writer = membership.write().await;
2368 membership_writer.insert_committee(
2369 epoch,
2370 active_validators.clone(),
2371 block_reward,
2372 stake_table_hash,
2373 Some(block_header.clone()),
2374 );
2375
2376 let previous_epoch = EpochNumber::new(epoch.saturating_sub(1));
2379 let previous_committee = membership_writer.state.get(&previous_epoch).cloned();
2380 membership_writer.all_validators =
2382 membership_writer.all_validators.split_off(&previous_epoch);
2383 let previous_validators = membership_writer.all_validators.remove(&previous_epoch);
2385 membership_writer
2386 .all_validators
2387 .insert(epoch, all_validators.clone());
2388 drop(membership_writer);
2389
2390 let persistence_lock = fetcher.persistence.lock().await;
2391
2392 let decided_hash = block_header.next_stake_table_hash();
2393
2394 if let Some(previous_committee) = previous_committee {
2401 if decided_hash.is_none() || decided_hash == previous_committee.stake_table_hash {
2402 if let Err(e) = persistence_lock
2403 .store_stake(
2404 previous_epoch,
2405 previous_committee.validators.clone(),
2406 previous_committee.block_reward,
2407 previous_committee.stake_table_hash,
2408 )
2409 .await
2410 {
2411 tracing::error!(
2412 ?e,
2413 ?previous_epoch,
2414 "`add_epoch_root`, error storing stake table"
2415 );
2416 }
2417
2418 if let Some(previous_validators) = previous_validators
2419 && let Err(e) = persistence_lock
2420 .store_all_validators(previous_epoch, previous_validators)
2421 .await
2422 {
2423 tracing::error!(?e, ?epoch, "`add_epoch_root`, error storing all validators");
2424 }
2425 } else {
2426 panic!(
2427 "The decided block header's `next_stake_table_hash` does not match the hash \
2428 of the stake table we have. This is an unrecoverable error likely due to \
2429 issues with the your L1 RPC provider. Decided:\n\n{:?}Actual:\n\n{:?}",
2430 decided_hash, previous_committee.stake_table_hash
2431 );
2432 }
2433 }
2434
2435 Ok(())
2436 }
2437
2438 fn has_stake_table(&self, epoch: Epoch) -> bool {
2439 self.state.contains_key(&epoch)
2440 }
2441
2442 fn has_randomized_stake_table(&self, epoch: Epoch) -> anyhow::Result<bool> {
2454 let Some(first_epoch) = self.first_epoch else {
2455 bail!(
2456 "Called has_randomized_stake_table with epoch {} but first_epoch is None",
2457 epoch
2458 );
2459 };
2460 ensure!(
2461 epoch >= first_epoch,
2462 "Called has_randomized_stake_table with epoch {} but first_epoch is {}",
2463 epoch,
2464 first_epoch
2465 );
2466 Ok(self.randomized_committees.contains_key(&epoch))
2467 }
2468
2469 async fn get_epoch_root(membership: Arc<RwLock<Self>>, epoch: Epoch) -> anyhow::Result<Leaf2> {
2470 let membership_reader = membership.read().await;
2471 let block_height = root_block_in_epoch(*epoch, membership_reader.epoch_height);
2472 let peers = membership_reader.fetcher.peers.clone();
2473 let stake_table = membership_reader.stake_table(Some(epoch)).clone();
2474 let success_threshold = membership_reader.success_threshold(Some(epoch));
2475 drop(membership_reader);
2476
2477 let leaf: Leaf2 = peers
2479 .fetch_leaf(block_height, stake_table.clone(), success_threshold)
2480 .await?;
2481
2482 Ok(leaf)
2483 }
2484
2485 async fn get_epoch_drb(
2486 membership: Arc<RwLock<Self>>,
2487 epoch: Epoch,
2488 ) -> anyhow::Result<DrbResult> {
2489 let membership_reader = membership.read().await;
2490 let peers = membership_reader.fetcher.peers.clone();
2491
2492 if let Some(randomized_committee) = membership_reader.randomized_committees.get(&epoch) {
2494 return Ok(randomized_committee.drb_result());
2495 }
2496
2497 let previous_epoch = match epoch.checked_sub(1) {
2499 Some(epoch) => EpochNumber::new(epoch),
2500 None => {
2501 return membership_reader
2502 .randomized_committees
2503 .get(&epoch)
2504 .map(|committee| committee.drb_result())
2505 .context(format!("Missing randomized committee for epoch {epoch}"));
2506 },
2507 };
2508
2509 let stake_table = membership_reader.stake_table(Some(previous_epoch)).clone();
2510 let success_threshold = membership_reader.success_threshold(Some(previous_epoch));
2511
2512 let block_height =
2513 transition_block_for_epoch(*previous_epoch, membership_reader.epoch_height);
2514
2515 drop(membership_reader);
2516
2517 tracing::debug!(
2518 "Getting DRB for epoch {}, block height {}",
2519 epoch,
2520 block_height
2521 );
2522 let drb_leaf = peers
2523 .try_fetch_leaf(1, block_height, stake_table, success_threshold)
2524 .await?;
2525
2526 let Some(drb) = drb_leaf.next_drb_result else {
2527 tracing::error!(
2528 "We received a leaf that should contain a DRB result, but the DRB result is \
2529 missing: {:?}",
2530 drb_leaf
2531 );
2532
2533 bail!("DRB leaf is missing the DRB result.");
2534 };
2535
2536 Ok(drb)
2537 }
2538
2539 fn add_drb_result(&mut self, epoch: Epoch, drb: DrbResult) {
2540 tracing::info!("Adding DRB result {drb:?} to epoch {epoch}");
2541
2542 let Some(raw_stake_table) = self.state.get(&epoch) else {
2543 tracing::error!(
2544 "add_drb_result({epoch}, {drb:?}) was called, but we do not yet have the stake \
2545 table for epoch {epoch}"
2546 );
2547 return;
2548 };
2549
2550 let leaders = raw_stake_table
2551 .eligible_leaders
2552 .clone()
2553 .into_iter()
2554 .map(|peer_config| peer_config.stake_table_entry)
2555 .collect::<Vec<_>>();
2556 let randomized_committee = generate_stake_cdf(leaders, drb);
2557
2558 self.randomized_committees
2559 .insert(epoch, randomized_committee);
2560 }
2561
2562 fn set_first_epoch(&mut self, epoch: Epoch, initial_drb_result: DrbResult) {
2563 self.first_epoch = Some(epoch);
2564
2565 let epoch_committee = self.state.get(&Epoch::genesis()).unwrap().clone();
2566 self.state.insert(epoch, epoch_committee.clone());
2567 self.state.insert(epoch + 1, epoch_committee);
2568 self.add_drb_result(epoch, initial_drb_result);
2569 self.add_drb_result(epoch + 1, initial_drb_result);
2570 }
2571
2572 fn first_epoch(&self) -> Option<EpochNumber> {
2573 self.first_epoch
2574 }
2575
2576 fn stake_table_hash(&self, epoch: Epoch) -> Option<StakeTableHash> {
2577 let committee = self.state.get(&epoch)?;
2578 committee.stake_table_hash
2579 }
2580
2581 fn add_da_committee(&mut self, first_epoch: u64, committee: Vec<PeerConfig<SeqTypes>>) {
2582 let indexed_committee: HashMap<PubKey, _> = committee
2583 .iter()
2584 .map(|peer_config| {
2585 (
2586 PubKey::public_key(&peer_config.stake_table_entry),
2587 peer_config.clone(),
2588 )
2589 })
2590 .collect();
2591
2592 let da_committee = DaCommittee {
2593 committee,
2594 indexed_committee,
2595 };
2596
2597 self.da_committees.insert(first_epoch, da_committee);
2598 }
2599}
2600
2601#[cfg(any(test, feature = "testing"))]
2602impl super::v0_3::StakeTable {
2603 pub fn mock(n: u64) -> Self {
2605 [..n]
2606 .iter()
2607 .map(|_| PeerConfig::test_default())
2608 .collect::<Vec<PeerConfig<SeqTypes>>>()
2609 .into()
2610 }
2611}
2612
2613#[cfg(any(test, feature = "testing"))]
2614impl DAMembers {
2615 pub fn mock(n: u64) -> Self {
2617 [..n]
2618 .iter()
2619 .map(|_| PeerConfig::test_default())
2620 .collect::<Vec<PeerConfig<SeqTypes>>>()
2621 .into()
2622 }
2623}
2624
2625#[cfg(any(test, feature = "testing"))]
2626pub mod testing {
2627 use alloy::primitives::Bytes;
2628 use hotshot_contract_adapter::{
2629 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
2630 stake_table::{StateSignatureSol, sign_address_bls, sign_address_schnorr},
2631 };
2632 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
2633 use rand::{Rng as _, RngCore as _};
2634
2635 use super::*;
2636
2637 #[derive(Debug, Clone)]
2640 pub struct TestValidator {
2641 pub account: Address,
2642 pub bls_vk: G2PointSol,
2643 pub schnorr_vk: EdOnBN254PointSol,
2644 pub commission: u16,
2645 pub bls_sig: G1PointSol,
2646 pub schnorr_sig: Bytes,
2647 }
2648
2649 impl TestValidator {
2650 pub fn random() -> Self {
2651 let account = Address::random();
2652 let commission = rand::thread_rng().gen_range(0..10000);
2653 Self::random_update_keys(account, commission)
2654 }
2655
2656 pub fn randomize_keys(&self) -> Self {
2657 Self::random_update_keys(self.account, self.commission)
2658 }
2659
2660 pub fn random_update_keys(account: Address, commission: u16) -> Self {
2661 let mut rng = &mut rand::thread_rng();
2662 let mut seed = [0u8; 32];
2663 rng.fill_bytes(&mut seed);
2664 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2665 let bls_sig = sign_address_bls(&bls_key_pair, account);
2666 let schnorr_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
2667 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, account);
2668 Self {
2669 account,
2670 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2671 schnorr_vk: schnorr_key_pair.ver_key().to_affine().into(),
2672 commission,
2673 bls_sig: bls_sig.into(),
2674 schnorr_sig: StateSignatureSol::from(schnorr_sig).into(),
2675 }
2676 }
2677 }
2678
2679 impl From<&TestValidator> for ValidatorRegistered {
2680 fn from(value: &TestValidator) -> Self {
2681 Self {
2682 account: value.account,
2683 blsVk: value.bls_vk,
2684 schnorrVk: value.schnorr_vk,
2685 commission: value.commission,
2686 }
2687 }
2688 }
2689
2690 impl From<&TestValidator> for ValidatorRegisteredV2 {
2691 fn from(value: &TestValidator) -> Self {
2692 Self {
2693 account: value.account,
2694 blsVK: value.bls_vk,
2695 schnorrVK: value.schnorr_vk,
2696 commission: value.commission,
2697 blsSig: value.bls_sig.into(),
2698 schnorrSig: value.schnorr_sig.clone(),
2699 metadataUri: "dummy-meta".to_string(),
2700 }
2701 }
2702 }
2703
2704 impl From<&TestValidator> for ConsensusKeysUpdated {
2705 fn from(value: &TestValidator) -> Self {
2706 Self {
2707 account: value.account,
2708 blsVK: value.bls_vk,
2709 schnorrVK: value.schnorr_vk,
2710 }
2711 }
2712 }
2713
2714 impl From<&TestValidator> for ConsensusKeysUpdatedV2 {
2715 fn from(value: &TestValidator) -> Self {
2716 Self {
2717 account: value.account,
2718 blsVK: value.bls_vk,
2719 schnorrVK: value.schnorr_vk,
2720 blsSig: value.bls_sig.into(),
2721 schnorrSig: value.schnorr_sig.clone(),
2722 }
2723 }
2724 }
2725
2726 impl From<&TestValidator> for ValidatorExit {
2727 fn from(value: &TestValidator) -> Self {
2728 Self {
2729 validator: value.account,
2730 }
2731 }
2732 }
2733
2734 impl RegisteredValidator<BLSPubKey> {
2735 pub fn mock() -> RegisteredValidator<BLSPubKey> {
2736 let val = TestValidator::random();
2737 let rng = &mut rand::thread_rng();
2738 let mut seed = [1u8; 32];
2739 rng.fill_bytes(&mut seed);
2740 let mut validator_stake = alloy::primitives::U256::from(0);
2741 let mut delegators = HashMap::new();
2742 for _i in 0..=5000 {
2743 let stake: u64 = rng.gen_range(0..10000);
2744 delegators.insert(Address::random(), alloy::primitives::U256::from(stake));
2745 validator_stake += alloy::primitives::U256::from(stake);
2746 }
2747
2748 let stake_table_key = val.bls_vk.into();
2749 let state_ver_key = val.schnorr_vk.into();
2750
2751 RegisteredValidator {
2752 account: val.account,
2753 stake_table_key,
2754 state_ver_key,
2755 stake: validator_stake,
2756 commission: val.commission,
2757 delegators,
2758 authenticated: true,
2759 x25519_key: None,
2760 p2p_addr: None,
2761 }
2762 }
2763 }
2764
2765 impl AuthenticatedValidator<BLSPubKey> {
2766 pub fn mock() -> AuthenticatedValidator<BLSPubKey> {
2767 RegisteredValidator::mock()
2768 .try_into()
2769 .expect("mock validator is always authenticated")
2770 }
2771
2772 pub fn mock_with_commission(commission: u16) -> AuthenticatedValidator<BLSPubKey> {
2773 let mut inner = RegisteredValidator::mock();
2774 inner.commission = commission;
2775 inner
2776 .try_into()
2777 .expect("mock validator is always authenticated")
2778 }
2779 }
2780}
2781
2782#[cfg(test)]
2783mod tests {
2784
2785 use alloy::{primitives::Address, rpc::types::Log};
2786 use hotshot_contract_adapter::stake_table::{StakeTableContractVersion, sign_address_bls};
2787 use hotshot_types::signature_key::BLSKeyPair;
2788 use pretty_assertions::assert_matches;
2789 use rstest::rstest;
2790
2791 use super::*;
2792 use crate::{L1ClientOptions, v0::impls::testing::*};
2793
2794 #[test_log::test]
2795 fn test_from_l1_events() -> anyhow::Result<()> {
2796 let val_1 = TestValidator::random();
2798 let val_1_new_keys = val_1.randomize_keys();
2799 let val_2 = TestValidator::random();
2800 let val_2_new_keys = val_2.randomize_keys();
2801 let delegator = Address::random();
2802 let mut events: Vec<StakeTableEvent> = [
2803 ValidatorRegistered::from(&val_1).into(),
2804 ValidatorRegisteredV2::from(&val_2).into(),
2805 Delegated {
2806 delegator,
2807 validator: val_1.account,
2808 amount: U256::from(10),
2809 }
2810 .into(),
2811 ConsensusKeysUpdated::from(&val_1_new_keys).into(),
2812 ConsensusKeysUpdatedV2::from(&val_2_new_keys).into(),
2813 Undelegated {
2814 delegator,
2815 validator: val_1.account,
2816 amount: U256::from(7),
2817 }
2818 .into(),
2819 Delegated {
2821 delegator,
2822 validator: val_1.account,
2823 amount: U256::from(5),
2824 }
2825 .into(),
2826 Delegated {
2828 delegator: Address::random(),
2829 validator: val_2.account,
2830 amount: U256::from(3),
2831 }
2832 .into(),
2833 ]
2834 .to_vec();
2835
2836 let validators_set = validator_set_from_l1_events(events.iter().cloned())?;
2837 let st = validators_set.active_validators;
2838 let st_val_1 = st.get(&val_1.account).unwrap();
2839 assert_eq!(st_val_1.stake, U256::from(8));
2841 assert_eq!(st_val_1.commission, val_1.commission);
2842 assert_eq!(st_val_1.delegators.len(), 1);
2843 assert_eq!(*st_val_1.delegators.get(&delegator).unwrap(), U256::from(8));
2845
2846 let st_val_2 = st.get(&val_2.account).unwrap();
2847 assert_eq!(st_val_2.stake, U256::from(3));
2848 assert_eq!(st_val_2.commission, val_2.commission);
2849 assert_eq!(st_val_2.delegators.len(), 1);
2850
2851 events.push(ValidatorExit::from(&val_1).into());
2852
2853 let validator_set = validator_set_from_l1_events(events.iter().cloned())?;
2854 let st = validator_set.active_validators;
2855 assert_eq!(st.get(&val_1.account), None);
2857
2858 let st_val_2 = st.get(&val_2.account).unwrap();
2860 assert_eq!(st_val_2.stake, U256::from(3));
2861 assert_eq!(st_val_2.commission, val_2.commission);
2862 assert_eq!(st_val_2.delegators.len(), 1);
2863
2864 events.push(ValidatorExit::from(&val_2).into());
2866
2867 assert!(validator_set_from_l1_events(events.iter().cloned()).is_err());
2869
2870 Ok(())
2871 }
2872
2873 #[test]
2874 fn test_from_l1_events_failures() -> anyhow::Result<()> {
2875 let val = TestValidator::random();
2876 let delegator = Address::random();
2877
2878 let register: StakeTableEvent = ValidatorRegistered::from(&val).into();
2879 let register_v2: StakeTableEvent = ValidatorRegisteredV2::from(&val).into();
2880 let delegate: StakeTableEvent = Delegated {
2881 delegator,
2882 validator: val.account,
2883 amount: U256::from(10),
2884 }
2885 .into();
2886 let key_update: StakeTableEvent = ConsensusKeysUpdated::from(&val).into();
2887 let key_update_v2: StakeTableEvent = ConsensusKeysUpdatedV2::from(&val).into();
2888 let undelegate: StakeTableEvent = Undelegated {
2889 delegator,
2890 validator: val.account,
2891 amount: U256::from(7),
2892 }
2893 .into();
2894
2895 let exit: StakeTableEvent = ValidatorExit::from(&val).into();
2896
2897 let cases = [
2898 vec![exit],
2899 vec![undelegate.clone()],
2900 vec![delegate.clone()],
2901 vec![key_update],
2902 vec![key_update_v2],
2903 vec![register.clone(), register.clone()],
2904 vec![register_v2.clone(), register_v2.clone()],
2905 vec![register.clone(), register_v2.clone()],
2906 vec![register_v2.clone(), register.clone()],
2907 vec![
2908 register,
2909 delegate.clone(),
2910 undelegate.clone(),
2911 undelegate.clone(),
2912 ],
2913 vec![register_v2, delegate, undelegate.clone(), undelegate],
2914 ];
2915
2916 for events in cases.iter() {
2917 let res = validators_from_l1_events(events.iter().cloned());
2921 assert!(
2922 res.is_err(),
2923 "events {res:?}, not a valid sequence of events"
2924 );
2925 }
2926 Ok(())
2927 }
2928
2929 #[test]
2930 fn test_validators_selection() {
2931 let mut candidates = IndexMap::new();
2932 let mut highest_stake = alloy::primitives::U256::ZERO;
2933
2934 for _i in 0..3000 {
2935 let candidate = RegisteredValidator::mock();
2936 candidates.insert(candidate.account, candidate.clone());
2937
2938 if candidate.stake > highest_stake {
2939 highest_stake = candidate.stake;
2940 }
2941 }
2942
2943 let minimum_stake = highest_stake / U256::from(VID_TARGET_TOTAL_STAKE);
2944
2945 let selected_validators =
2946 select_active_validator_set(&candidates).expect("Failed to select validators");
2947 assert!(
2948 selected_validators.len() <= MAX_VALIDATORS,
2949 "validators len is {}, expected at most {MAX_VALIDATORS}",
2950 selected_validators.len()
2951 );
2952
2953 let mut selected_validators_highest_stake = alloy::primitives::U256::ZERO;
2954 for (address, validator) in &selected_validators {
2956 assert!(
2957 validator.stake >= minimum_stake,
2958 "Validator {:?} has stake below minimum: {}",
2959 address,
2960 validator.stake
2961 );
2962
2963 if validator.stake > selected_validators_highest_stake {
2964 selected_validators_highest_stake = validator.stake;
2965 }
2966 }
2967 }
2968
2969 #[rstest::rstest]
2972 fn test_regression_non_unique_bls_keys_not_discarded(
2973 #[values(StakeTableContractVersion::V1, StakeTableContractVersion::V2)]
2974 version: StakeTableContractVersion,
2975 ) {
2976 let val = TestValidator::random();
2977 let register: StakeTableEvent = match version {
2978 StakeTableContractVersion::V1 => ValidatorRegistered::from(&val).into(),
2979 StakeTableContractVersion::V2 => ValidatorRegisteredV2::from(&val).into(),
2980 };
2981 let delegate: StakeTableEvent = Delegated {
2982 delegator: Address::random(),
2983 validator: val.account,
2984 amount: U256::from(10),
2985 }
2986 .into();
2987
2988 assert!(
2990 validator_set_from_l1_events(vec![register.clone(), delegate.clone()].into_iter())
2991 .is_ok()
2992 );
2993
2994 let key_update = ConsensusKeysUpdated::from(&val).into();
2996 let err = validator_set_from_l1_events(vec![register, delegate, key_update].into_iter())
2997 .unwrap_err();
2998
2999 let bls: BLSPubKey = val.bls_vk.into();
3000 assert!(matches!(err, StakeTableError::BlsKeyAlreadyUsed(addr) if addr == bls.to_string()));
3001 }
3002
3003 #[test]
3006 fn test_regression_reregister_eth_account() {
3007 let val1 = TestValidator::random();
3008 let val2 = val1.randomize_keys();
3009 let account = val1.account;
3010
3011 let register1 = ValidatorRegisteredV2::from(&val1).into();
3012 let deregister1 = ValidatorExit::from(&val1).into();
3013 let register2 = ValidatorRegisteredV2::from(&val2).into();
3014 let events = [register1, deregister1, register2];
3015 let error = validators_from_l1_events(events.iter().cloned()).unwrap_err();
3016 assert_matches!(error, StakeTableError::ValidatorAlreadyExited(addr) if addr == account);
3017 }
3018
3019 #[test]
3020 fn test_display_log() {
3021 let serialized = r#"{"address":"0x0000000000000000000000000000000000000069",
3022 "topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],
3023 "data":"0x69",
3024 "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
3025 "blockNumber":"0x69","blockTimestamp":"0x69",
3026 "transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
3027 "transactionIndex":"0x69","logIndex":"0x70","removed":false}"#;
3028 let log: Log = serde_json::from_str(serialized).unwrap();
3029 assert_eq!(
3030 log.display(),
3031 "Log(block=105,index=112,\
3032 transaction_hash=0x0000000000000000000000000000000000000000000000000000000000000069)"
3033 )
3034 }
3035
3036 #[rstest]
3037 #[case::v1(StakeTableContractVersion::V1)]
3038 #[case::v2(StakeTableContractVersion::V2)]
3039 fn test_register_validator(#[case] version: StakeTableContractVersion) {
3040 let mut state = StakeTableState::default();
3041 let validator = TestValidator::random();
3042
3043 let event = match version {
3044 StakeTableContractVersion::V1 => StakeTableEvent::Register((&validator).into()),
3045 StakeTableContractVersion::V2 => StakeTableEvent::RegisterV2((&validator).into()),
3046 };
3047
3048 state.apply_event(event).unwrap().unwrap();
3049
3050 let stored = state.validators.get(&validator.account).unwrap();
3051 assert_eq!(stored.account, validator.account);
3052 }
3053
3054 #[rstest]
3055 #[case::v1(StakeTableContractVersion::V1)]
3056 #[case::v2(StakeTableContractVersion::V2)]
3057 fn test_validator_already_registered(#[case] version: StakeTableContractVersion) {
3058 let mut stake_table_state = StakeTableState::default();
3059
3060 let test_validator = TestValidator::random();
3061
3062 match version {
3064 StakeTableContractVersion::V1 => {
3065 stake_table_state.apply_event(StakeTableEvent::Register((&test_validator).into()))
3066 },
3067 StakeTableContractVersion::V2 => {
3068 stake_table_state.apply_event(StakeTableEvent::RegisterV2((&test_validator).into()))
3069 },
3070 }
3071 .unwrap()
3072 .unwrap(); let v1_already_registered_result = stake_table_state
3076 .clone()
3077 .apply_event(StakeTableEvent::Register((&test_validator).into()));
3078
3079 pretty_assertions::assert_matches!(
3080 v1_already_registered_result, Err(StakeTableError::AlreadyRegistered(account))
3081 if account == test_validator.account,
3082 "Expected AlreadyRegistered error. version ={version:?} result={v1_already_registered_result:?}",
3083 );
3084
3085 let v2_already_registered_result = stake_table_state
3087 .clone()
3088 .apply_event(StakeTableEvent::RegisterV2((&test_validator).into()));
3089
3090 pretty_assertions::assert_matches!(
3091 v2_already_registered_result,
3092 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
3093 "Expected AlreadyRegistered error. version ={version:?} result={v2_already_registered_result:?}",
3094
3095 );
3096 }
3097
3098 #[test]
3099 fn test_register_validator_v2_auth_fails_marks_as_unauthenticated() {
3100 let mut state = StakeTableState::default();
3101 let mut val = TestValidator::random();
3102 val.bls_sig = Default::default();
3103 let event = StakeTableEvent::RegisterV2((&val).into());
3104
3105 let result = state.apply_event(event);
3106 assert!(
3107 result.is_ok(),
3108 "Validator with invalid auth should still be accepted"
3109 );
3110
3111 let validator = state
3112 .validators()
3113 .get(&val.account)
3114 .expect("validator should exist");
3115 assert!(
3116 !validator.authenticated,
3117 "Validator should be marked as not authenticated"
3118 );
3119
3120 let event = StakeTableEvent::Delegate(Delegated {
3121 delegator: Address::random(),
3122 validator: val.account,
3123 amount: U256::from(100),
3124 });
3125 state.apply_event(event).unwrap().unwrap();
3126
3127 let active = select_active_validator_set(state.validators());
3128 match active {
3129 Err(_) => {}, Ok(map) => {
3131 assert!(
3132 map.get(&val.account).is_none(),
3133 "Unauthenticated validator should not be in active set"
3134 );
3135 },
3136 }
3137 }
3138
3139 #[test]
3140 fn test_authenticated_validator_deserialize_rejects_unauthenticated() {
3141 let mut validator = RegisteredValidator::<BLSPubKey>::mock();
3142 validator.authenticated = false;
3143
3144 let json = serde_json::to_string(&validator).unwrap();
3145 let result: Result<AuthenticatedValidator<BLSPubKey>, _> = serde_json::from_str(&json);
3146
3147 assert!(result.is_err());
3148 let err = result.unwrap_err().to_string();
3149 assert!(
3150 err.contains("cannot deserialize unauthenticated validator"),
3151 "unexpected error: {err}"
3152 );
3153 }
3154
3155 #[rstest]
3156 #[case::v1(StakeTableContractVersion::V1)]
3157 #[case::v2(StakeTableContractVersion::V2)]
3158 fn test_deregister_validator(#[case] version: StakeTableContractVersion) {
3159 let mut state = StakeTableState::default();
3160 let val = TestValidator::random();
3161
3162 let reg = StakeTableEvent::Register((&val).into());
3163 state.apply_event(reg).unwrap().unwrap();
3164
3165 let dereg = match version {
3166 StakeTableContractVersion::V1 => StakeTableEvent::Deregister((&val).into()),
3167 StakeTableContractVersion::V2 => StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3168 validator: val.account,
3169 unlocksAt: U256::from(1000u64),
3170 }),
3171 };
3172 state.apply_event(dereg).unwrap().unwrap();
3173 assert!(!state.validators.contains_key(&val.account));
3174 }
3175
3176 #[rstest]
3177 #[case::v1(StakeTableContractVersion::V1)]
3178 #[case::v2(StakeTableContractVersion::V2)]
3179 fn test_delegate_and_undelegate(#[case] version: StakeTableContractVersion) {
3180 let mut state = StakeTableState::default();
3181 let val = TestValidator::random();
3182 state
3183 .apply_event(StakeTableEvent::Register((&val).into()))
3184 .unwrap()
3185 .unwrap();
3186
3187 let delegator = Address::random();
3188 let amount = U256::from(1000);
3189 let delegate_event = StakeTableEvent::Delegate(Delegated {
3190 delegator,
3191 validator: val.account,
3192 amount,
3193 });
3194 state.apply_event(delegate_event).unwrap().unwrap();
3195
3196 let validator = state.validators.get(&val.account).unwrap();
3197 assert_eq!(validator.delegators.get(&delegator).cloned(), Some(amount));
3198
3199 let undelegate_event = match version {
3200 StakeTableContractVersion::V1 => StakeTableEvent::Undelegate(Undelegated {
3201 delegator,
3202 validator: val.account,
3203 amount,
3204 }),
3205 StakeTableContractVersion::V2 => StakeTableEvent::UndelegateV2(UndelegatedV2 {
3206 delegator,
3207 validator: val.account,
3208 amount,
3209 unlocksAt: U256::from(2000u64),
3210 undelegationId: 1,
3211 }),
3212 };
3213 state.apply_event(undelegate_event).unwrap().unwrap();
3214 let validator = state.validators.get(&val.account).unwrap();
3215 assert!(!validator.delegators.contains_key(&delegator));
3216 }
3217
3218 #[rstest]
3219 #[case::v1(StakeTableContractVersion::V1)]
3220 #[case::v2(StakeTableContractVersion::V2)]
3221 fn test_key_update_event(#[case] version: StakeTableContractVersion) {
3222 let mut state = StakeTableState::default();
3223 let val = TestValidator::random();
3224
3225 state
3227 .apply_event(StakeTableEvent::Register((&val).into()))
3228 .unwrap()
3229 .unwrap();
3230
3231 let new_keys = val.randomize_keys();
3232
3233 let event = match version {
3234 StakeTableContractVersion::V1 => StakeTableEvent::KeyUpdate((&new_keys).into()),
3235 StakeTableContractVersion::V2 => StakeTableEvent::KeyUpdateV2((&new_keys).into()),
3236 };
3237
3238 state.apply_event(event).unwrap().unwrap();
3239
3240 let updated = state.validators.get(&val.account).unwrap();
3241 assert_eq!(updated.stake_table_key, new_keys.bls_vk.into());
3242 assert_eq!(updated.state_ver_key, new_keys.schnorr_vk.into());
3243 }
3244
3245 #[test]
3246 fn test_duplicate_bls_key() {
3247 let mut state = StakeTableState::default();
3248 let val = TestValidator::random();
3249 let event1 = StakeTableEvent::Register((&val).into());
3250 let mut val2 = TestValidator::random();
3251 val2.bls_vk = val.bls_vk;
3252 val2.account = Address::random();
3253
3254 let event2 = StakeTableEvent::Register((&val2).into());
3255 state.apply_event(event1).unwrap().unwrap();
3256 let result = state.apply_event(event2);
3257
3258 let expected_bls_key = BLSPubKey::from(val.bls_vk).to_string();
3259
3260 assert_matches!(
3261 result,
3262 Err(StakeTableError::BlsKeyAlreadyUsed(key))
3263 if key == expected_bls_key,
3264 "Expected BlsKeyAlreadyUsed({expected_bls_key}), but got: {result:?}",
3265 );
3266 }
3267
3268 #[test]
3269 fn test_duplicate_schnorr_key() {
3270 let mut state = StakeTableState::default();
3271 let val = TestValidator::random();
3272 let event1 = StakeTableEvent::Register((&val).into());
3273 let mut val2 = TestValidator::random();
3274 val2.schnorr_vk = val.schnorr_vk;
3275 val2.account = Address::random();
3276 val2.bls_vk = val2.randomize_keys().bls_vk;
3277
3278 let event2 = StakeTableEvent::Register((&val2).into());
3279 state.apply_event(event1).unwrap().unwrap();
3280 let result = state.apply_event(event2);
3281
3282 let schnorr: SchnorrPubKey = val.schnorr_vk.into();
3283 assert_matches!(
3284 result,
3285 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
3286 if key == schnorr.to_string(),
3287 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
3288
3289 );
3290 }
3291
3292 #[test]
3293 fn test_duplicate_schnorr_key_v2_during_update() {
3294 let mut state = StakeTableState::default();
3295
3296 let val1 = TestValidator::random();
3297
3298 let mut rng = &mut rand::thread_rng();
3299 let bls_key_pair = BLSKeyPair::generate(&mut rng);
3300
3301 let val2 = TestValidator {
3302 account: val1.account,
3303 bls_vk: bls_key_pair.ver_key().to_affine().into(),
3304 schnorr_vk: val1.schnorr_vk,
3305 commission: val1.commission,
3306 bls_sig: sign_address_bls(&bls_key_pair, val1.account).into(),
3307 schnorr_sig: val1.clone().schnorr_sig,
3308 };
3309 let event1 = StakeTableEvent::RegisterV2((&val1).into());
3310 let event2 = StakeTableEvent::KeyUpdateV2((&val2).into());
3311
3312 state.apply_event(event1).unwrap().unwrap();
3313 let result = state.apply_event(event2);
3314
3315 let schnorr: SchnorrPubKey = val1.schnorr_vk.into();
3316 assert_matches!(
3317 result,
3318 Err(StakeTableError::SchnorrKeyAlreadyUsed(key))
3319 if key == schnorr.to_string(),
3320 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
3321 );
3322 }
3323
3324 #[test]
3325 fn test_register_and_deregister_validator() {
3326 let mut state = StakeTableState::default();
3327 let validator = TestValidator::random();
3328 let event = StakeTableEvent::Register((&validator).into());
3329 state.apply_event(event).unwrap().unwrap();
3330
3331 let deregister_event = StakeTableEvent::Deregister((&validator).into());
3332 assert!(state.apply_event(deregister_event).unwrap().is_ok());
3333 }
3334
3335 #[test]
3336 fn test_commission_validation_exceeds_basis_points() {
3337 let validator = TestValidator::random();
3339 let mut stake_table = StakeTableState::default();
3340
3341 let registration_event = ValidatorRegistered::from(&validator).into();
3343 stake_table
3344 .apply_event(registration_event)
3345 .unwrap()
3346 .unwrap();
3347
3348 let valid_commission_event = CommissionUpdated {
3350 validator: validator.account,
3351 timestamp: Default::default(),
3352 oldCommission: 0,
3353 newCommission: COMMISSION_BASIS_POINTS, }
3355 .into();
3356 stake_table
3357 .apply_event(valid_commission_event)
3358 .unwrap()
3359 .unwrap();
3360
3361 let invalid_commission = COMMISSION_BASIS_POINTS + 1;
3362 let invalid_commission_event = CommissionUpdated {
3363 validator: validator.account,
3364 timestamp: Default::default(),
3365 oldCommission: 0,
3366 newCommission: invalid_commission,
3367 }
3368 .into();
3369
3370 let err = stake_table
3371 .apply_event(invalid_commission_event)
3372 .unwrap_err();
3373
3374 assert_matches!(
3375 err,
3376 StakeTableError::InvalidCommission(addr, invalid_commission)
3377 if addr == addr && invalid_commission == invalid_commission);
3378 }
3379
3380 #[test]
3381 fn test_delegate_zero_amount_is_rejected() {
3382 let mut state = StakeTableState::default();
3383 let validator = TestValidator::random();
3384 let account = validator.account;
3385 state
3386 .apply_event(StakeTableEvent::Register((&validator).into()))
3387 .unwrap()
3388 .unwrap();
3389
3390 let delegator = Address::random();
3391 let amount = U256::ZERO;
3392 let event = StakeTableEvent::Delegate(Delegated {
3393 delegator,
3394 validator: account,
3395 amount,
3396 });
3397 let result = state.apply_event(event);
3398
3399 assert_matches!(
3400 result,
3401 Err(StakeTableError::ZeroDelegatorStake(addr))
3402 if addr == delegator,
3403 "delegator stake is zero"
3404
3405 );
3406 }
3407
3408 #[test]
3409 fn test_undelegate_more_than_stake_fails() {
3410 let mut state = StakeTableState::default();
3411 let validator = TestValidator::random();
3412 let account = validator.account;
3413 state
3414 .apply_event(StakeTableEvent::Register((&validator).into()))
3415 .unwrap()
3416 .unwrap();
3417
3418 let delegator = Address::random();
3419 let event = StakeTableEvent::Delegate(Delegated {
3420 delegator,
3421 validator: account,
3422 amount: U256::from(10u64),
3423 });
3424 state.apply_event(event).unwrap().unwrap();
3425
3426 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3427 delegator,
3428 validator: account,
3429 amount: U256::from(20u64),
3430 }));
3431 assert_matches!(
3432 result,
3433 Err(StakeTableError::InsufficientStake),
3434 "Expected InsufficientStake error, got: {result:?}",
3435 );
3436 }
3437
3438 #[test]
3439 fn test_apply_event_does_not_modify_state_on_error() {
3440 let mut state = StakeTableState::default();
3441 let validator = TestValidator::random();
3442 let delegator = Address::random();
3443
3444 state
3445 .apply_event(StakeTableEvent::Register((&validator).into()))
3446 .unwrap()
3447 .unwrap();
3448
3449 let state_before = state.clone();
3451 let result = state.apply_event(StakeTableEvent::Register((&validator).into()));
3452 assert_matches!(result, Err(StakeTableError::AlreadyRegistered(_)));
3453 assert_eq!(
3454 state, state_before,
3455 "State should not change on AlreadyRegistered error"
3456 );
3457
3458 let state_before = state.clone();
3460 let mut validator2 = TestValidator::random();
3461 validator2.bls_vk = validator.bls_vk; let result = state.apply_event(StakeTableEvent::Register((&validator2).into()));
3463 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
3464 assert_eq!(
3465 state, state_before,
3466 "State should not change on BlsKeyAlreadyUsed error"
3467 );
3468
3469 let state_before = state.clone();
3471 let nonexistent_validator = TestValidator::random();
3472 let result =
3473 state.apply_event(StakeTableEvent::Deregister((&nonexistent_validator).into()));
3474 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
3475 assert_eq!(
3476 state, state_before,
3477 "State should not change on ValidatorNotFound error"
3478 );
3479
3480 let state_before = state.clone();
3482 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3483 delegator: Address::random(),
3484 validator: Address::random(),
3485 amount: U256::from(100u64),
3486 }));
3487 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
3488 assert_eq!(
3489 state, state_before,
3490 "State should not change on ValidatorNotFound error for Undelegate"
3491 );
3492
3493 state
3494 .apply_event(StakeTableEvent::Delegate(Delegated {
3495 delegator,
3496 validator: validator.account,
3497 amount: U256::from(100u64),
3498 }))
3499 .unwrap()
3500 .unwrap();
3501
3502 let state_before = state.clone();
3504 let non_existent_delegator = Address::random();
3505 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3506 delegator: non_existent_delegator,
3507 validator: validator.account,
3508 amount: U256::from(50u64),
3509 }));
3510 assert_matches!(result, Err(StakeTableError::DelegatorNotFound(_)));
3511 assert_eq!(
3512 state, state_before,
3513 "State should not change on DelegatorNotFound error"
3514 );
3515
3516 let state_before = state.clone();
3518 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3519 delegator,
3520 validator: validator.account,
3521 amount: U256::from(200u64),
3522 }));
3523 assert_matches!(result, Err(StakeTableError::InsufficientStake));
3524 assert_eq!(
3525 state, state_before,
3526 "State should not change on InsufficientStake error"
3527 );
3528
3529 let validator2 = TestValidator::random();
3531 let delegator2 = Address::random();
3532
3533 state
3534 .apply_event(StakeTableEvent::Register((&validator2).into()))
3535 .unwrap()
3536 .unwrap();
3537
3538 state
3539 .apply_event(StakeTableEvent::Delegate(Delegated {
3540 delegator: delegator2,
3541 validator: validator2.account,
3542 amount: U256::from(50u64),
3543 }))
3544 .unwrap()
3545 .unwrap();
3546 let state_before = state.clone();
3547 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3548 delegator: delegator2,
3549 validator: validator2.account,
3550 amount: U256::from(100u64),
3551 }));
3552 assert_matches!(result, Err(StakeTableError::InsufficientStake));
3553 assert_eq!(state, state_before,);
3554
3555 let state_before = state.clone();
3557 let result = state.apply_event(StakeTableEvent::Delegate(Delegated {
3558 delegator: Address::random(),
3559 validator: validator.account,
3560 amount: U256::ZERO,
3561 }));
3562 assert_matches!(result, Err(StakeTableError::ZeroDelegatorStake(_)));
3563 assert_eq!(
3564 state, state_before,
3565 "State should not change on ZeroDelegatorStake error"
3566 );
3567 }
3568
3569 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3570 async fn test_decaf_stake_table() {
3571 let events_json =
3607 std::fs::read_to_string("../../../data/v3/decaf_stake_table_events.json").unwrap();
3608 let events: Vec<(EventKey, StakeTableEvent)> = serde_json::from_str(&events_json).unwrap();
3609
3610 let reconstructed_stake_table =
3612 validator_set_from_l1_events(events.into_iter().map(|(_, e)| e))
3613 .unwrap()
3614 .active_validators;
3615
3616 let stake_table_json =
3617 std::fs::read_to_string("../../../data/v3/decaf_stake_table.json").unwrap();
3618 let expected: AuthenticatedValidatorMap = serde_json::from_str(&stake_table_json).unwrap();
3619
3620 assert_eq!(
3621 reconstructed_stake_table, expected,
3622 "Stake table reconstructed from events does not match the expected stake table "
3623 );
3624 }
3625
3626 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3627 #[should_panic]
3628 async fn test_large_max_events_range_panic() {
3629 let contract_address = "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037";
3631
3632 let l1 = L1ClientOptions {
3633 l1_events_max_retry_duration: Duration::from_secs(30),
3634 l1_events_max_block_range: 10_u64.pow(9),
3636 l1_retry_delay: Duration::from_secs(1),
3637 ..Default::default()
3638 }
3639 .connect(vec![
3640 "https://ethereum-sepolia.publicnode.com".parse().unwrap(),
3641 ])
3642 .expect("unable to construct l1 client");
3643
3644 let latest_block = l1.provider.get_block_number().await.unwrap();
3645 let _events = Fetcher::fetch_events_from_contract(
3646 l1,
3647 contract_address.parse().unwrap(),
3648 None,
3649 latest_block,
3650 )
3651 .await
3652 .unwrap();
3653 }
3654
3655 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3656 async fn sanity_check_block_reward_v3() {
3657 let initial_supply = U256::from_str("10000000000000000000000000000").unwrap();
3659
3660 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
3661 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
3662 .unwrap();
3663
3664 println!("Calculated reward: {reward}");
3665 assert!(reward > U256::ZERO);
3666 }
3667
3668 #[test]
3669 fn sanity_check_p_and_rp() {
3670 let total_stake = BigDecimal::from_str("1000").unwrap();
3671 let total_supply = BigDecimal::from_str("10000").unwrap(); let (p, rp) =
3674 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3675
3676 assert!(p > BigDecimal::zero());
3677 assert!(p < BigDecimal::one());
3678 assert!(rp > BigDecimal::zero());
3679 }
3680
3681 #[test]
3682 fn test_p_out_of_range() {
3683 let total_stake = BigDecimal::from_str("1000").unwrap();
3684 let total_supply = BigDecimal::from_str("500").unwrap(); let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3687 assert!(result.is_err());
3688 }
3689
3690 #[test]
3691 fn test_zero_total_supply() {
3692 let total_stake = BigDecimal::from_str("1000").unwrap();
3693 let total_supply = BigDecimal::from(0);
3694
3695 let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3696 assert!(result.is_err());
3697 }
3698
3699 #[test]
3700 fn test_valid_p_and_rp() {
3701 let total_stake = BigDecimal::from_str("5000").unwrap();
3702 let total_supply = BigDecimal::from_str("10000").unwrap();
3703
3704 let (p, rp) =
3705 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3706
3707 assert_eq!(p, BigDecimal::from_str("0.5").unwrap());
3708 assert!(rp > BigDecimal::from_str("0.0").unwrap());
3709 }
3710
3711 #[test]
3712 fn test_very_small_p() {
3713 let total_stake = BigDecimal::from_str("1").unwrap(); let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap(); let (p, rp) =
3717 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3718
3719 assert!(p > BigDecimal::from_str("0").unwrap());
3720 assert!(p < BigDecimal::from_str("1e-18").unwrap()); assert!(rp > BigDecimal::zero());
3722 }
3723
3724 #[test]
3725 fn test_p_very_close_to_one() {
3726 let total_stake = BigDecimal::from_str("9999999999999999999999999999").unwrap();
3727 let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap();
3728
3729 let (p, rp) =
3730 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3731
3732 assert!(p < BigDecimal::one());
3733 assert!(p > BigDecimal::from_str("0.999999999999999999999999999").unwrap());
3734 assert!(rp > BigDecimal::zero());
3735 }
3736
3737 #[test]
3741 fn test_reward_rate_rp() {
3742 let test_cases = [
3743 ("0.0000", "0.2121"), ("0.0050", "0.2121"), ("0.0100", "0.2121"), ("0.0250", "0.1342"), ("0.0500", "0.0949"), ("0.1000", "0.0671"), ("0.2500", "0.0424"), ("0.5000", "0.0300"), ("0.7500", "0.0245"), ("1.0000", "0.0212"), ];
3755
3756 let tolerance = BigDecimal::from_str("0.0001").unwrap();
3757
3758 let total_supply = BigDecimal::from_u32(10_000).unwrap();
3759
3760 for (p, rp) in test_cases {
3761 let p = BigDecimal::from_str(p).unwrap();
3762 let expected_rp = BigDecimal::from_str(rp).unwrap();
3763
3764 let total_stake = &p * &total_supply;
3765
3766 let (computed_p, computed_rp) =
3767 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3768
3769 assert!(
3770 (&computed_p - &p).abs() < tolerance,
3771 "p mismatch: got {computed_p}, expected {p}"
3772 );
3773
3774 assert!(
3775 (&computed_rp - &expected_rp).abs() < tolerance,
3776 "R(p) mismatch for p={p}: got {computed_rp}, expected {expected_rp}"
3777 );
3778 }
3779 }
3780
3781 #[tokio::test(flavor = "multi_thread")]
3782 async fn test_dynamic_block_reward_with_expected_values() {
3783 let total_supply = U256::from_str("10000000000000000000000000000").unwrap();
3785 let total_supply_bd = BigDecimal::from_str(&total_supply.to_string()).unwrap();
3786
3787 let test_cases = [
3788 ("0.0000", "0.2121", 1, "0"), ("0.0050", "0.2121", 1, "3362823439878234"), ("0.0100", "0.2121", 1, "6725646879756468"), ("0.0250", "0.1342", 1, "10638635210553018"), ("0.0500", "0.0949", 1, "15046296296296296"), ("0.1000", "0.0671", 1, "21277270421106037"), ("0.2500", "0.0424", 1, "33612379502790461"), ("0.5000", "0.0300", 1, "47564687975646879"), ("0.7500", "0.0245", 1, "58266742770167427"), ("1.0000", "0.0212", 1, "67224759005580923"), ("0.0000", "0.2121", 2000, "0"), ("0.0050", "0.2121", 2000, "672564687975646880"), ("0.0100", "0.2121", 2000, "1345129375951293760"), ("0.0250", "0.1342", 2000, "2127727042110603754"), ("0.0500", "0.0949", 2000, "3009259259259259259"), ("0.1000", "0.0671", 2000, "4255454084221207509"), ("0.2500", "0.0424", 2000, "6722475900558092339"), ("0.5000", "0.0300", 2000, "9512937595129375951"), ("0.7500", "0.0245", 2000, "11653348554033485540"), ("1.0000", "0.0212", 2000, "13444951801116184678"), ("0.0000", "0.2121", 10000, "0"), ("0.0050", "0.2121", 10000, "3362823439878234400"), ("0.0100", "0.2121", 10000, "6725646879756468800"), ("0.0250", "0.1342", 10000, "10638635210553018770"), ("0.0500", "0.0949", 10000, "15046296296296296295"), ("0.1000", "0.0671", 10000, "21277270421106037545"), ("0.2500", "0.0424", 10000, "33612379502790461695"), ("0.5000", "0.0300", 10000, "47564687975646879755"), ("0.7500", "0.0245", 10000, "58266742770167427700"), ("1.0000", "0.0212", 10000, "67224759005580923390"), ];
3822
3823 let tolerance = U256::from(100_000_000_000_000_000u128); for (p, rp, avg_block_time_ms, expected_reward) in test_cases {
3826 let p = BigDecimal::from_str(p).unwrap();
3827 let total_stake_bd = (&p * &total_supply_bd).round(0);
3828 println!("total_stake_bd={total_stake_bd}");
3829
3830 let total_stake = U256::from_str(&total_stake_bd.to_plain_string()).unwrap();
3831 let expected_reward = U256::from_str(expected_reward).unwrap();
3832
3833 let epoch = EpochNumber::new(0);
3834 let actual_reward = EpochCommittees::compute_block_reward(
3835 &epoch,
3836 total_supply,
3837 total_stake,
3838 avg_block_time_ms,
3839 )
3840 .unwrap()
3841 .0;
3842
3843 let diff = if actual_reward > expected_reward {
3844 actual_reward - expected_reward
3845 } else {
3846 expected_reward - actual_reward
3847 };
3848
3849 assert!(
3850 diff <= tolerance,
3851 "Reward mismatch for p = {p}, R(p) = {rp}, block_time = {avg_block_time_ms}: \
3852 expected = {expected_reward}, actual = {actual_reward}, diff = {diff}"
3853 );
3854 }
3855 }
3856
3857 #[derive(Debug, Clone, Copy)]
3859 enum EventType {
3860 Delegate,
3861 Undelegate,
3862 KeyUpdate,
3863 CommissionUpdate,
3864 Exit,
3865 }
3866
3867 #[rstest]
3868 #[case::delegate(EventType::Delegate)]
3869 #[case::undelegate(EventType::Undelegate)]
3870 #[case::key_update(EventType::KeyUpdate)]
3871 #[case::commission_update(EventType::CommissionUpdate)]
3872 #[case::exit(EventType::Exit)]
3873 fn test_events_targeting_unauthenticated_validator(
3874 #[case] event_type: EventType,
3875 ) -> anyhow::Result<()> {
3876 let mut state = StakeTableState::default();
3877 let mut val = TestValidator::random();
3878 val.bls_sig = Default::default();
3879 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3880
3881 let validator = state.validators().get(&val.account).context("validator")?;
3882 assert!(!validator.authenticated);
3883
3884 let delegator = Address::random();
3885 let initial_amount = U256::from(1000);
3886 state.apply_event(StakeTableEvent::Delegate(Delegated {
3887 delegator,
3888 validator: val.account,
3889 amount: initial_amount,
3890 }))??;
3891
3892 match event_type {
3893 EventType::Delegate => {
3894 let new_delegator = Address::random();
3895 let amount = U256::from(500);
3896 state.apply_event(StakeTableEvent::Delegate(Delegated {
3897 delegator: new_delegator,
3898 validator: val.account,
3899 amount,
3900 }))??;
3901
3902 let validator = state.validators().get(&val.account).context("validator")?;
3903 assert_eq!(validator.stake, initial_amount + amount);
3904 assert_eq!(
3905 validator.delegators.get(&new_delegator).cloned(),
3906 Some(amount)
3907 );
3908 },
3909 EventType::Undelegate => {
3910 let undelegate_amount = U256::from(300);
3911 state.apply_event(StakeTableEvent::UndelegateV2(UndelegatedV2 {
3912 delegator,
3913 validator: val.account,
3914 undelegationId: 1,
3915 amount: undelegate_amount,
3916 unlocksAt: U256::from(1000u64),
3917 }))??;
3918
3919 let validator = state.validators().get(&val.account).context("validator")?;
3920 assert_eq!(validator.stake, initial_amount - undelegate_amount);
3921 assert_eq!(
3922 validator.delegators.get(&delegator).cloned(),
3923 Some(initial_amount - undelegate_amount)
3924 );
3925 },
3926 EventType::KeyUpdate => {
3927 let new_keys = val.randomize_keys();
3928 state.apply_event(StakeTableEvent::KeyUpdateV2((&new_keys).into()))??;
3929
3930 let validator = state.validators().get(&val.account).context("validator")?;
3931 let expected_bls: BLSPubKey = new_keys.bls_vk.into();
3932 let expected_schnorr: SchnorrPubKey = new_keys.schnorr_vk.into();
3933 assert_eq!(validator.stake_table_key, expected_bls);
3934 assert_eq!(validator.state_ver_key, expected_schnorr);
3935 assert!(!validator.authenticated);
3937 },
3938 EventType::CommissionUpdate => {
3939 let new_commission: u16 = 5000;
3940 state.apply_event(StakeTableEvent::CommissionUpdate(CommissionUpdated {
3941 validator: val.account,
3942 timestamp: Default::default(),
3943 oldCommission: val.commission,
3944 newCommission: new_commission,
3945 }))??;
3946
3947 let validator = state.validators().get(&val.account).context("validator")?;
3948 assert_eq!(validator.commission, new_commission);
3949 },
3950 EventType::Exit => {
3951 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3952 validator: val.account,
3953 unlocksAt: U256::from(1000u64),
3954 }))??;
3955
3956 assert!(!state.validators().contains_key(&val.account));
3957 return Ok(());
3958 },
3959 }
3960
3961 let active = select_active_validator_set(state.validators());
3962 match active {
3963 Err(StakeTableError::NoValidValidators) => {},
3964 Err(e) => bail!("Unexpected error: {e}"),
3965 Ok(map) => assert!(!map.contains_key(&val.account)),
3966 }
3967 Ok(())
3968 }
3969}