1use std::{
2 cmp::min,
3 collections::{HashMap, HashSet},
4 future::Future,
5 str::FromStr,
6 sync::Arc,
7 time::{Duration, Instant},
8};
9
10use alloy::{
11 eips::{BlockId, BlockNumberOrTag},
12 primitives::{Address, U256, utils::format_ether},
13 providers::Provider,
14 rpc::types::{Filter, Log},
15 sol_types::{SolEvent, SolEventInterface},
16};
17use anyhow::{Context, bail, ensure};
18use ark_ec::AffineRepr;
19use ark_serialize::CanonicalSerialize;
20use ark_std::One;
21use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
22use bigdecimal::BigDecimal;
23use committable::{Commitment, Committable, RawCommitmentBuilder};
24use futures::future::BoxFuture;
25use hotshot::types::{BLSPubKey, SchnorrPubKey, SignatureKey as _};
26use hotshot_contract_adapter::sol_types::{
27 EspToken::{self, EspTokenInstance},
28 StakeTableV3::{
29 self, CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated,
30 P2pAddrUpdated, StakeTableV3Events, Undelegated, UndelegatedV2, ValidatorExit,
31 ValidatorExitV2, ValidatorRegistered, ValidatorRegisteredV2, ValidatorRegisteredV3,
32 X25519KeyUpdated,
33 },
34};
35use hotshot_types::{
36 PeerConfig,
37 addr::NetAddr,
38 data::{EpochNumber, vid_disperse::VID_TARGET_TOTAL_STAKE},
39 x25519,
40};
41use humantime::format_duration;
42use indexmap::IndexMap;
43use itertools::Itertools;
44use num_traits::{FromPrimitive, Zero};
45use thiserror::Error;
46use tokio::{spawn, time::sleep};
47use tracing::Instrument;
48use vbs::version::Version;
49
50#[cfg(any(test, feature = "testing"))]
51use super::v0_3::DAMembers;
52use super::{
53 Header, L1Client, SeqTypes,
54 traits::{MembershipPersistence, StateCatchup},
55 v0_3::{
56 AuthenticatedValidator, ChainConfig, EventKey, Fetcher, MAX_VALIDATORS,
57 RegisteredValidator, StakeTableEvent, StakeTableUpdateTask,
58 },
59};
60use crate::{
61 traits::EventsPersistenceRead,
62 v0_1::L1Provider,
63 v0_3::{
64 BLOCKS_PER_YEAR, COMMISSION_BASIS_POINTS, EventSortingError, ExpectedStakeTableError,
65 FetchRewardError, INFLATION_RATE, MILLISECONDS_PER_YEAR, RewardAmount, StakeTableError,
66 },
67};
68
69pub type RegisteredValidatorMap = IndexMap<Address, RegisteredValidator<BLSPubKey>>;
70pub type AuthenticatedValidatorMap = IndexMap<Address, AuthenticatedValidator<BLSPubKey>>;
71
72pub fn to_registered_validator_map(
73 validators: &AuthenticatedValidatorMap,
74) -> RegisteredValidatorMap {
75 validators
76 .iter()
77 .map(|(addr, v)| (*addr, v.clone().into()))
78 .collect()
79}
80
81pub type StakeTableHash = Commitment<StakeTableState>;
82
83type ApplyEventResult<T> = Result<Result<T, ExpectedStakeTableError>, StakeTableError>;
88
89trait DisplayLog {
91 fn display(&self) -> String;
92}
93
94impl DisplayLog for Log {
95 fn display(&self) -> String {
96 let block = self.block_number.unwrap_or_default();
100 let index = self.log_index.unwrap_or_default();
101 let hash = self.transaction_hash.unwrap_or_default();
102 format!("Log(block={block},index={index},transaction_hash={hash})")
103 }
104}
105
106impl TryFrom<StakeTableV3Events> for StakeTableEvent {
107 type Error = anyhow::Error;
108
109 fn try_from(value: StakeTableV3Events) -> anyhow::Result<Self> {
110 match value {
111 StakeTableV3Events::ValidatorRegistered(v) => Ok(StakeTableEvent::Register(v)),
112 StakeTableV3Events::ValidatorRegisteredV2(v) => Ok(StakeTableEvent::RegisterV2(v)),
113 StakeTableV3Events::ValidatorRegisteredV3(v) => Ok(StakeTableEvent::RegisterV3(v)),
114 StakeTableV3Events::ValidatorExit(v) => Ok(StakeTableEvent::Deregister(v)),
115 StakeTableV3Events::ValidatorExitV2(v) => Ok(StakeTableEvent::DeregisterV2(v)),
116 StakeTableV3Events::Delegated(v) => Ok(StakeTableEvent::Delegate(v)),
117 StakeTableV3Events::Undelegated(v) => Ok(StakeTableEvent::Undelegate(v)),
118 StakeTableV3Events::UndelegatedV2(v) => Ok(StakeTableEvent::UndelegateV2(v)),
119 StakeTableV3Events::ConsensusKeysUpdated(v) => Ok(StakeTableEvent::KeyUpdate(v)),
120 StakeTableV3Events::ConsensusKeysUpdatedV2(v) => Ok(StakeTableEvent::KeyUpdateV2(v)),
121 StakeTableV3Events::CommissionUpdated(v) => Ok(StakeTableEvent::CommissionUpdate(v)),
122 StakeTableV3Events::X25519KeyUpdated(v) => Ok(StakeTableEvent::X25519KeyUpdate(v)),
123 StakeTableV3Events::P2pAddrUpdated(v) => Ok(StakeTableEvent::P2pAddrUpdate(v)),
124 StakeTableV3Events::ExitEscrowPeriodUpdated(v) => Err(anyhow::anyhow!(
125 "Unsupported StakeTableV3Events::ExitEscrowPeriodUpdated({v:?})"
126 )),
127 StakeTableV3Events::Initialized(v) => Err(anyhow::anyhow!(
128 "Unsupported StakeTableV3Events::Initialized({v:?})"
129 )),
130 StakeTableV3Events::MaxCommissionIncreaseUpdated(v) => Err(anyhow::anyhow!(
131 "Unsupported StakeTableV3Events::MaxCommissionIncreaseUpdated({v:?})"
132 )),
133 StakeTableV3Events::MinCommissionUpdateIntervalUpdated(v) => Err(anyhow::anyhow!(
134 "Unsupported StakeTableV3Events::MinCommissionUpdateIntervalUpdated({v:?})"
135 )),
136 StakeTableV3Events::OwnershipTransferred(v) => Err(anyhow::anyhow!(
137 "Unsupported StakeTableV3Events::OwnershipTransferred({v:?})"
138 )),
139 StakeTableV3Events::Paused(v) => Err(anyhow::anyhow!(
140 "Unsupported StakeTableV3Events::Paused({v:?})"
141 )),
142 StakeTableV3Events::RoleAdminChanged(v) => Err(anyhow::anyhow!(
143 "Unsupported StakeTableV3Events::RoleAdminChanged({v:?})"
144 )),
145 StakeTableV3Events::RoleGranted(v) => Err(anyhow::anyhow!(
146 "Unsupported StakeTableV3Events::RoleGranted({v:?})"
147 )),
148 StakeTableV3Events::RoleRevoked(v) => Err(anyhow::anyhow!(
149 "Unsupported StakeTableV3Events::RoleRevoked({v:?})"
150 )),
151 StakeTableV3Events::Unpaused(v) => Err(anyhow::anyhow!(
152 "Unsupported StakeTableV3Events::Unpaused({v:?})"
153 )),
154 StakeTableV3Events::Upgraded(v) => Err(anyhow::anyhow!(
155 "Unsupported StakeTableV3Events::Upgraded({v:?})"
156 )),
157 StakeTableV3Events::WithdrawalClaimed(v) => Err(anyhow::anyhow!(
158 "Unsupported StakeTableV3Events::WithdrawalClaimed({v:?})"
159 )),
160 StakeTableV3Events::ValidatorExitClaimed(v) => Err(anyhow::anyhow!(
161 "Unsupported StakeTableV3Events::ValidatorExitClaimed({v:?})"
162 )),
163 StakeTableV3Events::Withdrawal(v) => Err(anyhow::anyhow!(
164 "Unsupported StakeTableV3Events::Withdrawal({v:?})"
165 )),
166 StakeTableV3Events::MetadataUriUpdated(v) => Err(anyhow::anyhow!(
167 "Unsupported StakeTableV3Events::MetadataUriUpdated({v:?})"
168 )),
169 StakeTableV3Events::MinDelegateAmountUpdated(v) => Err(anyhow::anyhow!(
170 "Unsupported StakeTableV3Events::MinDelegateAmountUpdated({v:?})"
171 )),
172 }
173 }
174}
175
176fn sort_stake_table_events(
177 event_logs: Vec<(StakeTableV3Events, Log)>,
178) -> Result<Vec<(EventKey, StakeTableEvent)>, EventSortingError> {
179 let mut events: Vec<(EventKey, StakeTableEvent)> = Vec::new();
180
181 let key = |log: &Log| -> Result<EventKey, EventSortingError> {
182 let block_number = log
183 .block_number
184 .ok_or(EventSortingError::MissingBlockNumber)?;
185 let log_index = log.log_index.ok_or(EventSortingError::MissingLogIndex)?;
186 Ok((block_number, log_index))
187 };
188
189 for (e, log) in event_logs {
190 let k = key(&log)?;
191 let evt: StakeTableEvent = e
192 .try_into()
193 .map_err(|_| EventSortingError::InvalidStakeTableEvent)?;
194 events.push((k, evt));
195 }
196
197 events.sort_by_key(|(key, _)| *key);
198 Ok(events)
199}
200
201#[derive(Clone, Debug, Default, PartialEq)]
202pub struct StakeTableState {
203 validators: RegisteredValidatorMap,
204 validator_exits: HashSet<Address>,
205 used_bls_keys: HashSet<BLSPubKey>,
206 used_schnorr_keys: HashSet<SchnorrPubKey>,
207 used_x25519_keys: HashSet<x25519::PublicKey>,
208}
209
210impl Committable for StakeTableState {
211 fn commit(&self) -> committable::Commitment<Self> {
212 let mut builder = RawCommitmentBuilder::new(&Self::tag());
213
214 for (_, validator) in self.validators.iter().sorted_by_key(|(a, _)| *a) {
215 builder = builder.field("validator", validator.commit());
216 }
217
218 builder = builder.constant_str("used_bls_keys");
219 for key in self.used_bls_keys.iter().sorted() {
220 builder = builder.var_size_bytes(&key.to_bytes());
221 }
222
223 builder = builder.constant_str("used_schnorr_keys");
224 for key in self
225 .used_schnorr_keys
226 .iter()
227 .sorted_by(|a, b| a.to_affine().xy().cmp(&b.to_affine().xy()))
228 {
229 let mut schnorr_key_bytes = vec![];
230 key.serialize_with_mode(&mut schnorr_key_bytes, ark_serialize::Compress::Yes)
231 .unwrap();
232 builder = builder.var_size_bytes(&schnorr_key_bytes);
233 }
234
235 if !self.used_x25519_keys.is_empty() {
240 builder = builder.constant_str("used_x25519_keys");
241 for key in self.used_x25519_keys.iter().sorted() {
242 builder = builder.var_size_bytes(key.as_slice());
243 }
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 used_x25519_keys: HashSet<x25519::PublicKey>,
267 ) -> Self {
268 Self {
269 validators,
270 validator_exits,
271 used_bls_keys,
272 used_schnorr_keys,
273 used_x25519_keys,
274 }
275 }
276
277 pub fn validators(&self) -> &RegisteredValidatorMap {
278 &self.validators
279 }
280
281 pub fn into_validators(self) -> RegisteredValidatorMap {
282 self.validators
283 }
284
285 pub fn used_bls_keys(&self) -> &HashSet<BLSPubKey> {
286 &self.used_bls_keys
287 }
288
289 pub fn used_schnorr_keys(&self) -> &HashSet<SchnorrPubKey> {
290 &self.used_schnorr_keys
291 }
292
293 pub fn used_x25519_keys(&self) -> &HashSet<x25519::PublicKey> {
294 &self.used_x25519_keys
295 }
296
297 pub fn validator_exits(&self) -> &HashSet<Address> {
298 &self.validator_exits
299 }
300
301 pub fn apply_event(&mut self, event: StakeTableEvent) -> ApplyEventResult<()> {
307 match event {
308 StakeTableEvent::Register(ValidatorRegistered {
309 account,
310 blsVk,
311 schnorrVk,
312 commission,
313 }) => {
314 let stake_table_key: BLSPubKey = blsVk.into();
315 let state_ver_key: SchnorrPubKey = schnorrVk.into();
316
317 if self.validator_exits.contains(&account) {
318 return Err(StakeTableError::ValidatorAlreadyExited(account));
319 }
320
321 let entry = self.validators.entry(account);
322 if let indexmap::map::Entry::Occupied(_) = entry {
323 return Err(StakeTableError::AlreadyRegistered(account));
324 }
325
326 if self.used_bls_keys.contains(&stake_table_key) {
328 return Err(StakeTableError::BlsKeyAlreadyUsed(
329 stake_table_key.to_string(),
330 ));
331 }
332
333 if self.used_schnorr_keys.contains(&state_ver_key) {
335 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
336 state_ver_key.to_string(),
337 )));
338 }
339
340 self.used_bls_keys.insert(stake_table_key);
342 self.used_schnorr_keys.insert(state_ver_key.clone());
343
344 entry.or_insert(RegisteredValidator {
345 account,
346 stake_table_key,
347 state_ver_key,
348 stake: U256::ZERO,
349 commission,
350 delegators: HashMap::new(),
351 authenticated: true,
352 x25519_key: None,
353 p2p_addr: None,
354 });
355 },
356
357 StakeTableEvent::RegisterV2(ref reg) => {
358 let authenticated = reg.authenticate().is_ok();
359 if !authenticated {
360 tracing::warn!(
361 account = ?reg.account,
362 "Validator registered with invalid signature"
363 );
364 }
365
366 let ValidatorRegisteredV2 {
367 account,
368 blsVK,
369 schnorrVK,
370 commission,
371 ..
372 } = reg;
373
374 let stake_table_key: BLSPubKey = (*blsVK).into();
375 let state_ver_key: SchnorrPubKey = (*schnorrVK).into();
376
377 if self.validator_exits.contains(account) {
379 return Err(StakeTableError::ValidatorAlreadyExited(*account));
380 }
381
382 let entry = self.validators.entry(*account);
383 if let indexmap::map::Entry::Occupied(_) = entry {
384 return Err(StakeTableError::AlreadyRegistered(*account));
385 }
386
387 if self.used_bls_keys.contains(&stake_table_key) {
389 return Err(StakeTableError::BlsKeyAlreadyUsed(
390 stake_table_key.to_string(),
391 ));
392 }
393
394 if self.used_schnorr_keys.contains(&state_ver_key) {
396 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
397 state_ver_key.to_string(),
398 ));
399 }
400
401 self.used_bls_keys.insert(stake_table_key);
403 self.used_schnorr_keys.insert(state_ver_key.clone());
404
405 entry.or_insert(RegisteredValidator {
406 account: *account,
407 stake_table_key,
408 state_ver_key,
409 stake: U256::ZERO,
410 commission: *commission,
411 delegators: HashMap::new(),
412 authenticated,
413 x25519_key: None,
414 p2p_addr: None,
415 });
416 },
417
418 StakeTableEvent::Deregister(ValidatorExit { validator })
419 | StakeTableEvent::DeregisterV2(ValidatorExitV2 { validator, .. }) => {
420 if !self.validators.contains_key(&validator) {
421 return Err(StakeTableError::ValidatorNotFound(validator));
422 }
423
424 self.validator_exits.insert(validator);
426 self.validators.shift_remove(&validator);
427 },
428
429 StakeTableEvent::Delegate(delegated) => {
430 let Delegated {
431 delegator,
432 validator,
433 amount,
434 } = delegated;
435
436 if amount.is_zero() {
438 return Err(StakeTableError::ZeroDelegatorStake(delegator));
439 }
440
441 let val = self
442 .validators
443 .get_mut(&validator)
444 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
445
446 val.stake = val.stake.checked_add(amount).unwrap_or_else(|| {
449 panic!(
450 "validator stake overflow: validator={validator}, stake={}, \
451 amount={amount}",
452 val.stake
453 )
454 });
455 val.delegators
458 .entry(delegator)
459 .and_modify(|stake| {
460 *stake = stake.checked_add(amount).unwrap_or_else(|| {
461 panic!(
462 "delegator stake overflow: delegator={delegator}, stake={stake}, \
463 amount={amount}"
464 )
465 });
466 })
467 .or_insert(amount);
468 },
469
470 StakeTableEvent::Undelegate(Undelegated {
471 delegator,
472 validator,
473 amount,
474 })
475 | StakeTableEvent::UndelegateV2(UndelegatedV2 {
476 delegator,
477 validator,
478 amount,
479 ..
480 }) => {
481 let val = self
482 .validators
483 .get_mut(&validator)
484 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
485
486 if val.stake < amount {
487 tracing::warn!("validator_stake={}, amount={amount}", val.stake);
488 return Err(StakeTableError::InsufficientStake);
489 }
490
491 let delegator_stake = val
492 .delegators
493 .get_mut(&delegator)
494 .ok_or(StakeTableError::DelegatorNotFound(delegator))?;
495
496 if *delegator_stake < amount {
497 tracing::warn!("delegator_stake={delegator_stake}, amount={amount}");
498 return Err(StakeTableError::InsufficientStake);
499 }
500
501 let new_delegator_stake = delegator_stake.checked_sub(amount).unwrap();
503
504 val.stake = val.stake.checked_sub(amount).unwrap();
507
508 if new_delegator_stake.is_zero() {
509 val.delegators.remove(&delegator);
510 } else {
511 *delegator_stake = new_delegator_stake;
512 }
513 },
514
515 StakeTableEvent::KeyUpdate(update) => {
516 let ConsensusKeysUpdated {
517 account,
518 blsVK,
519 schnorrVK,
520 } = update;
521
522 let stake_table_key: BLSPubKey = blsVK.into();
523 let state_ver_key: SchnorrPubKey = schnorrVK.into();
524
525 if !self.validators.contains_key(&account) {
526 return Err(StakeTableError::ValidatorNotFound(account));
527 }
528
529 if self.used_bls_keys.contains(&stake_table_key) {
530 return Err(StakeTableError::BlsKeyAlreadyUsed(
531 stake_table_key.to_string(),
532 ));
533 }
534
535 if self.used_schnorr_keys.contains(&state_ver_key) {
538 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
539 state_ver_key.to_string(),
540 )));
541 }
542
543 self.used_bls_keys.insert(stake_table_key);
545 self.used_schnorr_keys.insert(state_ver_key.clone());
546 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
548 panic!("validator {account} must exist after contains_key check")
549 });
550 validator.stake_table_key = stake_table_key;
551 validator.state_ver_key = state_ver_key;
552 },
553
554 StakeTableEvent::KeyUpdateV2(update) => {
555 update
558 .authenticate()
559 .map_err(|e| StakeTableError::AuthenticationFailed(e.to_string()))?;
560
561 let ConsensusKeysUpdatedV2 {
562 account,
563 blsVK,
564 schnorrVK,
565 ..
566 } = update;
567
568 let stake_table_key: BLSPubKey = blsVK.into();
569 let state_ver_key: SchnorrPubKey = schnorrVK.into();
570
571 if !self.validators.contains_key(&account) {
572 return Err(StakeTableError::ValidatorNotFound(account));
573 }
574
575 if self.used_bls_keys.contains(&stake_table_key) {
577 return Err(StakeTableError::BlsKeyAlreadyUsed(
578 stake_table_key.to_string(),
579 ));
580 }
581
582 if self.used_schnorr_keys.contains(&state_ver_key) {
584 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
585 state_ver_key.to_string(),
586 ));
587 }
588
589 self.used_bls_keys.insert(stake_table_key);
591 self.used_schnorr_keys.insert(state_ver_key.clone());
592
593 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
595 panic!("validator {account} must exist after contains_key check")
596 });
597 validator.stake_table_key = stake_table_key;
598 validator.state_ver_key = state_ver_key;
599 },
600
601 StakeTableEvent::CommissionUpdate(CommissionUpdated {
602 validator,
603 newCommission,
604 ..
605 }) => {
606 if newCommission > COMMISSION_BASIS_POINTS {
609 return Err(StakeTableError::InvalidCommission(validator, newCommission));
610 }
611
612 let val = self
616 .validators
617 .get_mut(&validator)
618 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
619 val.commission = newCommission;
620 },
621
622 StakeTableEvent::RegisterV3(ref reg) => {
623 let authenticated = reg.authenticate().is_ok();
624 if !authenticated {
625 tracing::warn!(
626 account = ?reg.account,
627 "Validator registered with invalid signature"
628 );
629 }
630
631 let ValidatorRegisteredV3 {
632 account,
633 blsVK,
634 schnorrVK,
635 commission,
636 x25519Key,
637 p2pAddr,
638 ..
639 } = reg;
640
641 let stake_table_key: BLSPubKey = (*blsVK).into();
642 let state_ver_key: SchnorrPubKey = (*schnorrVK).into();
643
644 if x25519Key.0 == [0u8; 32] {
645 return Err(StakeTableError::InvalidX25519Key("zero key".into()));
646 }
647 let x25519_key = x25519::PublicKey::try_from(x25519Key.0.as_slice())
648 .map_err(|e| StakeTableError::InvalidX25519Key(format!("{e}")))?;
649
650 let p2p_addr = match p2pAddr.parse::<NetAddr>() {
652 Ok(addr) => Some(addr),
653 Err(e) => {
654 if !p2pAddr.is_empty() {
655 tracing::warn!(%e, account = ?account, "Failed to parse p2p addr");
656 }
657 None
658 },
659 };
660
661 if self.validator_exits.contains(account) {
662 return Err(StakeTableError::ValidatorAlreadyExited(*account));
663 }
664
665 let entry = self.validators.entry(*account);
666 if let indexmap::map::Entry::Occupied(_) = entry {
667 return Err(StakeTableError::AlreadyRegistered(*account));
668 }
669
670 if self.used_bls_keys.contains(&stake_table_key) {
671 return Err(StakeTableError::BlsKeyAlreadyUsed(
672 stake_table_key.to_string(),
673 ));
674 }
675
676 if self.used_schnorr_keys.contains(&state_ver_key) {
677 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
678 state_ver_key.to_string(),
679 ));
680 }
681
682 if self.used_x25519_keys.contains(&x25519_key) {
683 return Err(StakeTableError::X25519KeyAlreadyUsed(
684 x25519_key.to_string(),
685 ));
686 }
687
688 self.used_bls_keys.insert(stake_table_key);
690 self.used_schnorr_keys.insert(state_ver_key.clone());
691 self.used_x25519_keys.insert(x25519_key);
692
693 entry.or_insert(RegisteredValidator {
694 account: *account,
695 stake_table_key,
696 state_ver_key,
697 stake: U256::ZERO,
698 commission: *commission,
699 delegators: HashMap::new(),
700 authenticated,
701 x25519_key: Some(x25519_key),
702 p2p_addr,
703 });
704 },
705
706 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
707 validator,
708 x25519Key,
709 }) => {
710 let val = self
711 .validators
712 .get_mut(&validator)
713 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
714
715 let key = x25519::PublicKey::try_from(x25519Key.0.as_slice())
716 .map_err(|e| StakeTableError::InvalidX25519Key(format!("{e}")))?;
717 if self.used_x25519_keys.contains(&key) {
718 return Err(StakeTableError::X25519KeyAlreadyUsed(key.to_string()));
719 }
720 self.used_x25519_keys.insert(key);
721 val.x25519_key = Some(key);
722 },
723
724 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
725 validator,
726 ref p2pAddr,
727 }) => {
728 let val = self
729 .validators
730 .get_mut(&validator)
731 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
732
733 match p2pAddr.parse::<NetAddr>() {
734 Ok(addr) => val.p2p_addr = Some(addr),
735 Err(e) => {
736 tracing::warn!(%e, validator = ?validator, "Failed to parse p2p addr");
737 val.p2p_addr = None;
738 },
739 }
740 },
741 }
742
743 Ok(Ok(()))
744 }
745}
746
747pub fn validators_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
748 events: I,
749) -> Result<(RegisteredValidatorMap, StakeTableHash), StakeTableError> {
750 let mut state = StakeTableState::default();
751 for event in events {
752 match state.apply_event(event.clone()) {
753 Ok(Ok(())) => {
754 },
756 Ok(Err(expected_err)) => {
757 tracing::warn!("Expected error while applying event {event:?}: {expected_err}");
759 },
760 Err(err) => {
761 tracing::error!("Fatal error in applying event {event:?}: {err}");
762 return Err(err);
763 },
764 }
765 }
766 let commit = state.commit();
767 Ok((state.into_validators(), commit))
768}
769
770pub(crate) fn select_active_validator_set(
776 candidates: &RegisteredValidatorMap,
777 protocol_version: Version,
778) -> Result<AuthenticatedValidatorMap, StakeTableError> {
779 let total_candidates = candidates.len();
780
781 let valid_validators: AuthenticatedValidatorMap = candidates
782 .iter()
783 .filter_map(
784 |(address, validator)| match AuthenticatedValidator::try_from(validator) {
785 Err(e) => {
786 tracing::debug!("{e}");
787 None
788 },
789 Ok(cv) => {
790 if cv.delegators.is_empty() {
791 tracing::info!("Validator {address:?} does not have any delegator");
792 return None;
793 }
794 if cv.stake.is_zero() {
795 tracing::info!("Validator {address:?} does not have any stake");
796 return None;
797 }
798 if !cv.is_eligible(protocol_version) {
799 tracing::debug!(
800 ?address,
801 has_x25519 = cv.x25519_key.is_some(),
802 has_p2p = cv.p2p_addr.is_some(),
803 "Validator not eligible at protocol version {protocol_version}"
804 );
805 return None;
806 }
807 Some((*address, cv))
808 },
809 },
810 )
811 .collect();
812
813 tracing::debug!(
814 total_candidates,
815 filtered = valid_validators.len(),
816 "Filtered out invalid validators"
817 );
818
819 if valid_validators.is_empty() {
820 tracing::warn!("Validator selection failed: no validators passed minimum criteria");
821 return Err(StakeTableError::NoValidValidators);
822 }
823
824 let maximum_stake = valid_validators.values().map(|v| v.stake).max().unwrap();
825
826 let minimum_stake = maximum_stake
827 .checked_div(U256::from(VID_TARGET_TOTAL_STAKE))
828 .ok_or_else(|| {
829 tracing::error!("Overflow while calculating minimum stake threshold");
830 StakeTableError::MinimumStakeOverflow
831 })?;
832
833 let mut valid_stakers: Vec<_> = valid_validators
834 .iter()
835 .filter(|(_, v)| v.stake >= minimum_stake)
836 .map(|(addr, v)| (*addr, v.stake))
837 .collect();
838
839 tracing::info!(
840 count = valid_stakers.len(),
841 "Number of validators above minimum stake threshold"
842 );
843
844 valid_stakers.sort_by_key(|(_, stake)| std::cmp::Reverse(*stake));
846
847 if valid_stakers.len() > MAX_VALIDATORS {
848 valid_stakers.truncate(MAX_VALIDATORS);
849 }
850
851 let selected_addresses: HashSet<_> = valid_stakers.iter().map(|(addr, _)| *addr).collect();
852 let selected_validators: AuthenticatedValidatorMap = valid_validators
853 .into_iter()
854 .filter(|(address, _)| selected_addresses.contains(address))
855 .collect();
856
857 tracing::info!(
858 final_count = selected_validators.len(),
859 "Selected active validator set"
860 );
861
862 Ok(selected_validators)
863}
864
865#[derive(Clone, Debug)]
866pub struct ValidatorSet {
867 pub(crate) all_validators: RegisteredValidatorMap,
868 pub(crate) active_validators: AuthenticatedValidatorMap,
869 pub(crate) stake_table_hash: Option<StakeTableHash>,
870 pub(crate) protocol_version: Version,
872}
873
874impl ValidatorSet {
875 pub fn from_state(
877 state: &StakeTableState,
878 protocol_version: Version,
879 ) -> Result<Self, StakeTableError> {
880 let active_validators = select_active_validator_set(state.validators(), protocol_version)?;
881 Ok(Self {
882 all_validators: state.validators().clone(),
883 active_validators,
884 stake_table_hash: Some(state.commit()),
885 protocol_version,
886 })
887 }
888
889 pub fn from_l1_events<I: Iterator<Item = StakeTableEvent>>(
891 events: I,
892 protocol_version: Version,
893 ) -> Result<Self, StakeTableError> {
894 let (all_validators, stake_table_hash) = validators_from_l1_events(events)?;
895 let active_validators = select_active_validator_set(&all_validators, protocol_version)?;
896 Ok(Self {
897 all_validators,
898 active_validators,
899 stake_table_hash: Some(stake_table_hash),
900 protocol_version,
901 })
902 }
903
904 pub fn all_validators(&self) -> &RegisteredValidatorMap {
906 &self.all_validators
907 }
908
909 pub fn active_validators(&self) -> &AuthenticatedValidatorMap {
911 &self.active_validators
912 }
913
914 pub fn stake_table_hash(&self) -> Option<StakeTableHash> {
916 self.stake_table_hash
917 }
918
919 pub fn protocol_version(&self) -> Version {
921 self.protocol_version
922 }
923}
924
925impl std::fmt::Debug for StakeTableEvent {
926 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
927 match self {
928 StakeTableEvent::Register(event) => write!(f, "Register({:?})", event.account),
929 StakeTableEvent::RegisterV2(event) => write!(f, "RegisterV2({:?})", event.account),
930 StakeTableEvent::Deregister(event) => write!(f, "Deregister({:?})", event.validator),
931 StakeTableEvent::DeregisterV2(event) => {
932 write!(f, "DeregisterV2({:?})", event.validator)
933 },
934 StakeTableEvent::Delegate(event) => write!(f, "Delegate({:?})", event.delegator),
935 StakeTableEvent::Undelegate(event) => write!(f, "Undelegate({:?})", event.delegator),
936 StakeTableEvent::UndelegateV2(event) => {
937 write!(f, "UndelegateV2({:?})", event.delegator)
938 },
939 StakeTableEvent::KeyUpdate(event) => write!(f, "KeyUpdate({:?})", event.account),
940 StakeTableEvent::KeyUpdateV2(event) => write!(f, "KeyUpdateV2({:?})", event.account),
941 StakeTableEvent::CommissionUpdate(event) => {
942 write!(f, "CommissionUpdate({:?})", event.validator)
943 },
944 StakeTableEvent::RegisterV3(event) => {
945 write!(f, "RegisterV3({:?})", event.account)
946 },
947 StakeTableEvent::X25519KeyUpdate(event) => {
948 write!(f, "X25519KeyUpdate({:?})", event.validator)
949 },
950 StakeTableEvent::P2pAddrUpdate(event) => {
951 write!(f, "P2pAddrUpdate({:?})", event.validator)
952 },
953 }
954 }
955}
956
957impl Fetcher {
958 pub fn new(
959 peers: Arc<dyn StateCatchup>,
960 persistence: Arc<AsyncMutex<dyn MembershipPersistence>>,
961 l1_client: L1Client,
962 chain_config: ChainConfig,
963 ) -> Self {
964 Self {
965 peers,
966 persistence,
967 l1_client,
968 chain_config: Arc::new(AsyncMutex::new(chain_config)),
969 update_task: StakeTableUpdateTask(AsyncMutex::new(None)).into(),
970 initial_supply: Arc::new(AsyncRwLock::new(None)),
971 }
972 }
973
974 pub async fn spawn_update_loop(&self) {
975 let mut update_task = self.update_task.0.lock().await;
976 if update_task.is_none() {
977 *update_task = Some(spawn(self.update_loop()));
978 }
979 }
980
981 fn update_loop(&self) -> impl Future<Output = ()> + use<> {
986 let span = tracing::warn_span!("Stake table update loop");
987 let self_clone = self.clone();
988 let state = self.l1_client.state.clone();
989 let l1_retry = self.l1_client.options().l1_retry_delay;
990 let update_delay = self.l1_client.options().stake_table_update_interval;
991 let chain_config = self.chain_config.clone();
992
993 async move {
994 let stake_contract_address = loop {
999 let contract = chain_config.lock().await.stake_table_contract;
1000 match contract {
1001 Some(addr) => break addr,
1002 None => {
1003 tracing::debug!(
1004 "Stake table contract address not found. Retrying in {l1_retry:?}...",
1005 );
1006 },
1007 }
1008 sleep(l1_retry).await;
1009 };
1010
1011 loop {
1013 let finalized_block = loop {
1014 let last_finalized = state.lock().await.last_finalized;
1015 if let Some(block) = last_finalized {
1016 break block;
1017 }
1018 tracing::debug!("Finalized block not yet available. Retrying in {l1_retry:?}",);
1019 sleep(l1_retry).await;
1020 };
1021
1022 tracing::debug!("Attempting to fetch stake table at L1 block {finalized_block:?}",);
1023
1024 loop {
1025 match self_clone
1026 .fetch_and_store_stake_table_events(stake_contract_address, finalized_block)
1027 .await
1028 {
1029 Ok(events) => {
1030 tracing::info!(
1031 "Successfully fetched and stored stake table events at \
1032 block={finalized_block:?}"
1033 );
1034 tracing::debug!("events={events:?}");
1035 break;
1036 },
1037 Err(e) => {
1038 tracing::error!(
1039 "Error fetching stake table at block {finalized_block:?}. err= \
1040 {e:#}",
1041 );
1042 sleep(l1_retry).await;
1043 },
1044 }
1045 }
1046
1047 tracing::debug!("Waiting {update_delay:?} before next stake table update...",);
1048 sleep(update_delay).await;
1049 }
1050 }
1051 .instrument(span)
1052 }
1053
1054 pub async fn fetch_and_store_stake_table_events(
1061 &self,
1062 contract: Address,
1063 to_block: u64,
1064 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
1065 let (read_l1_offset, persistence_events) = {
1066 let persistence_lock = self.persistence.lock().await;
1067 persistence_lock.load_events(0, to_block).await?
1068 };
1069
1070 tracing::info!("loaded events from storage to_block={to_block:?}");
1071
1072 if let Some(EventsPersistenceRead::Complete) = read_l1_offset {
1075 return Ok(persistence_events);
1076 }
1077
1078 let from_block = read_l1_offset
1079 .map(|read| match read {
1080 EventsPersistenceRead::UntilL1Block(block) => Ok(block + 1),
1081 EventsPersistenceRead::Complete => Err(anyhow::anyhow!(
1082 "Unexpected state. offset is complete after returning early"
1083 )),
1084 })
1085 .transpose()?;
1086
1087 ensure!(
1088 Some(to_block) >= from_block,
1089 "to_block {to_block:?} is less than from_block {from_block:?}"
1090 );
1091
1092 tracing::info!(%to_block, from_block = ?from_block, "Fetching events from contract");
1093
1094 let contract_events = Self::fetch_events_from_contract(
1095 self.l1_client.clone(),
1096 contract,
1097 from_block,
1098 to_block,
1099 )
1100 .await?;
1101
1102 tracing::info!(
1104 "storing {} new events in storage to_block={to_block:?}",
1105 contract_events.len()
1106 );
1107 {
1108 let persistence_lock = self.persistence.lock().await;
1109 persistence_lock
1110 .store_events(to_block, contract_events.clone())
1111 .await
1112 .inspect_err(|e| tracing::error!("failed to store events. err={e}"))?;
1113 }
1114
1115 let mut events = match from_block {
1116 Some(_) => persistence_events
1117 .into_iter()
1118 .chain(contract_events)
1119 .collect(),
1120 None => contract_events,
1121 };
1122
1123 let len_before_dedup = events.len();
1128 events.dedup();
1129 let len_after_dedup = events.len();
1130 if len_before_dedup != len_after_dedup {
1131 tracing::warn!("Duplicate events found and removed. This should not normally happen.")
1132 }
1133
1134 Ok(events)
1135 }
1136
1137 fn validate_event(event: &StakeTableV3Events, log: &Log) -> Result<bool, StakeTableError> {
1144 match event {
1145 StakeTableV3Events::ConsensusKeysUpdatedV2(evt) => {
1146 if let Err(err) = evt.authenticate() {
1147 tracing::warn!(
1148 %err,
1149 "Failed to authenticate ConsensusKeysUpdatedV2 event: {}",
1150 log.display()
1151 );
1152 return Ok(false);
1153 }
1154 },
1155 StakeTableV3Events::CommissionUpdated(evt)
1156 if evt.newCommission > COMMISSION_BASIS_POINTS =>
1157 {
1158 return Err(StakeTableError::InvalidCommission(
1159 evt.validator,
1160 evt.newCommission,
1161 ));
1162 },
1163 _ => {},
1164 }
1165
1166 Ok(true)
1167 }
1168
1169 fn block_range_chunks(
1171 from_block: u64,
1172 to_block: u64,
1173 chunk_size: u64,
1174 ) -> impl Iterator<Item = (u64, u64)> {
1175 let mut start = from_block;
1176 let end = to_block;
1177 std::iter::from_fn(move || {
1178 let chunk_end = min(start + chunk_size - 1, end);
1179 if chunk_end < start {
1180 return None;
1181 }
1182 let chunk = (start, chunk_end);
1183 start = chunk_end + 1;
1184 Some(chunk)
1185 })
1186 }
1187
1188 pub async fn fetch_events_from_contract(
1190 l1_client: L1Client,
1191 contract: Address,
1192 from_block: Option<u64>,
1193 to_block: u64,
1194 ) -> Result<Vec<(EventKey, StakeTableEvent)>, StakeTableError> {
1195 let stake_table_contract = StakeTableV3::new(contract, l1_client.provider.clone());
1196 let max_retry_duration = l1_client.options().l1_events_max_retry_duration;
1197 let retry_delay = l1_client.options().l1_retry_delay;
1198 let from_block = match from_block {
1201 Some(block) => block,
1202 None => {
1203 let start = Instant::now();
1204 loop {
1205 match stake_table_contract.initializedAtBlock().call().await {
1206 Ok(init_block) => break init_block.to::<u64>(),
1207 Err(err) => {
1208 if start.elapsed() >= max_retry_duration {
1209 panic!(
1210 "Failed to retrieve initial block after `{}`: {err}",
1211 format_duration(max_retry_duration)
1212 );
1213 }
1214 tracing::warn!(%err, "Failed to retrieve initial block, retrying...");
1215 sleep(retry_delay).await;
1216 },
1217 }
1218 }
1219 },
1220 };
1221
1222 let chunk_size = l1_client.options().l1_events_max_block_range;
1226 let chunks = Self::block_range_chunks(from_block, to_block, chunk_size);
1227
1228 let mut events = vec![];
1229
1230 for (from, to) in chunks {
1231 let provider = l1_client.provider.clone();
1232
1233 tracing::debug!(from, to, "fetch all stake table events in range");
1234 let logs: Vec<Log> = retry(
1237 retry_delay,
1238 max_retry_duration,
1239 "stake table events fetch",
1240 move || {
1241 let provider = provider.clone();
1242
1243 Box::pin(async move {
1244 let filter = Filter::new()
1245 .events([
1246 ValidatorRegistered::SIGNATURE,
1247 ValidatorRegisteredV2::SIGNATURE,
1248 ValidatorRegisteredV3::SIGNATURE,
1249 ValidatorExit::SIGNATURE,
1250 ValidatorExitV2::SIGNATURE,
1251 Delegated::SIGNATURE,
1252 Undelegated::SIGNATURE,
1253 UndelegatedV2::SIGNATURE,
1254 ConsensusKeysUpdated::SIGNATURE,
1255 ConsensusKeysUpdatedV2::SIGNATURE,
1256 CommissionUpdated::SIGNATURE,
1257 X25519KeyUpdated::SIGNATURE,
1258 P2pAddrUpdated::SIGNATURE,
1259 ])
1260 .address(contract)
1261 .from_block(from)
1262 .to_block(to);
1263 provider.get_logs(&filter).await
1264 })
1265 },
1266 )
1267 .await;
1268
1269 let chunk_events = logs
1270 .into_iter()
1271 .filter_map(|log| {
1272 let event =
1273 StakeTableV3Events::decode_raw_log(log.topics(), &log.data().data).ok()?;
1274 match Self::validate_event(&event, &log) {
1275 Ok(true) => Some(Ok((event, log))),
1276 Ok(false) => None,
1277 Err(e) => Some(Err(e)),
1278 }
1279 })
1280 .collect::<Result<Vec<_>, _>>()?;
1281
1282 events.extend(chunk_events);
1283 }
1284
1285 sort_stake_table_events(events).map_err(Into::into)
1286 }
1287
1288 pub async fn fetch_all_validators_from_contract(
1290 l1_client: L1Client,
1291 contract: Address,
1292 to_block: u64,
1293 ) -> anyhow::Result<(RegisteredValidatorMap, StakeTableHash)> {
1294 let events = Self::fetch_events_from_contract(l1_client, contract, None, to_block).await?;
1295
1296 validators_from_l1_events(events.into_iter().map(|(_, e)| e))
1298 .context("failed to construct validators set from l1 events")
1299 }
1300
1301 pub async fn fetch_fixed_block_reward(&self) -> Result<RewardAmount, FetchRewardError> {
1305 let initial_supply = *self.initial_supply.read().await;
1307 let initial_supply = match initial_supply {
1308 Some(supply) => supply,
1309 None => self.fetch_and_update_initial_supply().await?,
1310 };
1311
1312 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
1313 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
1314 .ok_or(FetchRewardError::DivisionByZero(
1315 "COMMISSION_BASIS_POINTS is zero",
1316 ))?;
1317
1318 Ok(RewardAmount(reward))
1319 }
1320
1321 pub async fn fetch_and_update_initial_supply(&self) -> Result<U256, FetchRewardError> {
1336 tracing::info!("Fetching token initial supply");
1337 let chain_config = *self.chain_config.lock().await;
1338
1339 let stake_table_contract = chain_config
1340 .stake_table_contract
1341 .ok_or(FetchRewardError::MissingStakeTableContract)?;
1342
1343 let provider = self.l1_client.provider.clone();
1344 let stake_table = StakeTableV3::new(stake_table_contract, provider.clone());
1345
1346 let stake_table_init_block = stake_table
1350 .initializedAtBlock()
1351 .block(BlockId::finalized())
1352 .call()
1353 .await
1354 .map_err(FetchRewardError::ContractCall)?
1355 .to::<u64>();
1356
1357 tracing::info!("stake table init block ={stake_table_init_block}");
1358
1359 let token_address = stake_table
1360 .token()
1361 .block(BlockId::finalized())
1362 .call()
1363 .await
1364 .map_err(FetchRewardError::TokenAddressFetch)?;
1365
1366 let token = EspToken::new(token_address, provider.clone());
1367
1368 let init_logs = token
1371 .Initialized_filter()
1372 .from_block(0u64)
1373 .to_block(BlockNumberOrTag::Finalized)
1374 .query()
1375 .await;
1376
1377 let init_log = match init_logs {
1378 Ok(init_logs) => {
1379 if init_logs.is_empty() {
1380 tracing::error!(
1381 "Token Initialized event logs are empty. This should never happen"
1382 );
1383 return Err(FetchRewardError::MissingInitializedEvent);
1384 }
1385
1386 let (_, init_log) = init_logs[0].clone();
1387
1388 tracing::debug!(tx_hash = ?init_log.transaction_hash, "Found token `Initialized` event");
1389 init_log
1390 },
1391 Err(err) => {
1392 tracing::warn!(
1393 "RPC returned error {err:?}. will fallback to scanning over fixed block range"
1394 );
1395 self.scan_token_contract_initialized_event_log(
1396 stake_table_init_block,
1397 token.clone(),
1398 )
1399 .await?
1400 },
1401 };
1402
1403 let init_block = init_log
1404 .block_number
1405 .ok_or(FetchRewardError::MissingBlockNumber)?;
1406
1407 let init_tx_hash =
1408 init_log
1409 .transaction_hash
1410 .ok_or_else(|| FetchRewardError::MissingTransactionHash {
1411 init_log: init_log.clone().into(),
1412 })?;
1413
1414 let transfer_logs = token
1418 .Transfer_filter()
1419 .from_block(init_block)
1420 .to_block(init_block)
1421 .query()
1422 .await
1423 .map_err(FetchRewardError::TransferEventQuery)?;
1424
1425 let (mint_transfer, _) = transfer_logs
1426 .iter()
1427 .find(|(transfer, log)| {
1428 log.transaction_hash == Some(init_tx_hash) && transfer.from == Address::ZERO
1429 })
1430 .ok_or(FetchRewardError::MissingTransferEvent)?;
1431
1432 tracing::debug!("mint transfer event ={mint_transfer:?}");
1433
1434 let initial_supply = mint_transfer.value;
1435
1436 tracing::info!("Initial token amount: {} ESP", format_ether(initial_supply));
1437
1438 let mut writer = self.initial_supply.write().await;
1439 *writer = Some(initial_supply);
1440
1441 Ok(initial_supply)
1442 }
1443
1444 pub async fn scan_token_contract_initialized_event_log(
1452 &self,
1453 stake_table_init_block: u64,
1454 token: EspTokenInstance<L1Provider>,
1455 ) -> Result<Log, FetchRewardError> {
1456 let max_events_range = self.l1_client.options().l1_events_max_block_range;
1457 const MAX_BLOCKS_SCANNED: u64 = 200_000;
1458 let mut total_scanned = 0;
1459
1460 let mut from_block = stake_table_init_block.saturating_sub(max_events_range);
1461 let mut to_block = stake_table_init_block;
1462
1463 loop {
1464 if total_scanned >= MAX_BLOCKS_SCANNED {
1465 tracing::error!(
1466 total_scanned,
1467 "Exceeded maximum scan range while searching for token Initialized event"
1468 );
1469 return Err(FetchRewardError::ExceededMaxScanRange(MAX_BLOCKS_SCANNED));
1470 }
1471
1472 let init_logs = token
1473 .Initialized_filter()
1474 .from_block(from_block)
1475 .to_block(to_block)
1476 .query()
1477 .await
1478 .map_err(FetchRewardError::ScanQueryFailed)?;
1479
1480 if !init_logs.is_empty() {
1481 let (_, init_log) = init_logs[0].clone();
1482 tracing::info!(
1483 from_block,
1484 tx_hash = ?init_log.transaction_hash,
1485 "Found token Initialized event during scan"
1486 );
1487 return Ok(init_log);
1488 }
1489
1490 total_scanned += max_events_range;
1491 from_block = from_block.saturating_sub(max_events_range);
1492 to_block = to_block.saturating_sub(max_events_range);
1493 }
1494 }
1495
1496 pub async fn update_chain_config(&self, header: &Header) -> anyhow::Result<()> {
1497 let chain_config = self.get_chain_config(header).await?;
1498 *self.chain_config.lock().await = chain_config;
1500
1501 Ok(())
1502 }
1503
1504 pub async fn fetch(&self, epoch: EpochNumber, header: &Header) -> anyhow::Result<ValidatorSet> {
1505 let chain_config = *self.chain_config.lock().await;
1506 let Some(address) = chain_config.stake_table_contract else {
1507 bail!("No stake table contract address found in Chain config");
1508 };
1509
1510 let Some(l1_finalized_block_info) = header.l1_finalized() else {
1511 bail!(
1512 "The epoch root for epoch {epoch} is missing the L1 finalized block info. This is \
1513 a fatal error. Consensus is blocked and will not recover."
1514 );
1515 };
1516
1517 let events = match self
1518 .fetch_and_store_stake_table_events(address, l1_finalized_block_info.number())
1519 .await
1520 .map_err(GetStakeTablesError::L1ClientFetchError)
1521 {
1522 Ok(events) => events,
1523 Err(e) => {
1524 bail!("failed to fetch stake table events {e:?}");
1525 },
1526 };
1527
1528 match ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), header.version()) {
1533 Ok(res) => Ok(res),
1534 Err(e) => {
1535 bail!("failed to construct stake table {e:?}");
1536 },
1537 }
1538 }
1539
1540 pub(crate) async fn get_chain_config(&self, header: &Header) -> anyhow::Result<ChainConfig> {
1543 let chain_config = self.chain_config.lock().await;
1544 let peers = self.peers.clone();
1545 let header_cf = header.chain_config();
1546 if chain_config.commit() == header_cf.commit() {
1547 return Ok(*chain_config);
1548 }
1549
1550 let cf = match header_cf.resolve() {
1551 Some(cf) => cf,
1552 None => peers
1553 .fetch_chain_config(header_cf.commit())
1554 .await
1555 .inspect_err(|err| {
1556 tracing::error!("failed to get chain_config from peers. err: {err:?}");
1557 })?,
1558 };
1559
1560 Ok(cf)
1561 }
1562
1563 #[cfg(any(test, feature = "testing"))]
1564 pub fn mock() -> Self {
1565 use crate::{mock, v0_1::NoStorage};
1566 let chain_config = ChainConfig::default();
1567 let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
1568 .expect("Failed to create L1 client");
1569
1570 let peers = Arc::new(mock::MockStateCatchup::default());
1571 let persistence = NoStorage;
1572
1573 Self::new(
1574 peers,
1575 Arc::new(AsyncMutex::new(persistence)),
1576 l1,
1577 chain_config,
1578 )
1579 }
1580}
1581
1582async fn retry<F, T, E>(
1583 retry_delay: Duration,
1584 max_duration: Duration,
1585 operation_name: &str,
1586 mut operation: F,
1587) -> T
1588where
1589 F: FnMut() -> BoxFuture<'static, Result<T, E>>,
1590 E: std::fmt::Display,
1591{
1592 let start = Instant::now();
1593 loop {
1594 match operation().await {
1595 Ok(result) => return result,
1596 Err(err) => {
1597 if start.elapsed() >= max_duration {
1598 panic!(
1599 r#"
1600 Failed to complete operation `{operation_name}` after `{}`.
1601 error: {err}
1602
1603
1604 This might be caused by:
1605 - The current block range being too large for your RPC provider.
1606 - The event query returning more data than your RPC allows as
1607 some RPC providers limit the number of events returned.
1608 - RPC provider outage
1609
1610 Suggested solution:
1611 - Reduce the value of the environment variable
1612 `ESPRESSO_L1_EVENTS_MAX_BLOCK_RANGE` to query smaller ranges.
1613 - Add multiple RPC providers
1614 - Use a different RPC provider with higher rate limits."#,
1615 format_duration(max_duration)
1616 );
1617 }
1618 tracing::warn!(%err, "Retrying `{operation_name}` after error");
1619 sleep(retry_delay).await;
1620 },
1621 }
1622 }
1623}
1624
1625pub fn calculate_proportion_staked_and_reward_rate(
1635 total_stake: &BigDecimal,
1636 total_supply: &BigDecimal,
1637) -> anyhow::Result<(BigDecimal, BigDecimal)> {
1638 if total_supply.is_zero() {
1639 return Err(anyhow::anyhow!("Total supply cannot be zero"));
1640 }
1641
1642 let proportion_staked = total_stake / total_supply;
1643
1644 if proportion_staked < BigDecimal::zero() || proportion_staked > BigDecimal::one() {
1645 return Err(anyhow::anyhow!("Stake ratio p must be in the range [0, 1]"));
1646 }
1647
1648 let two = BigDecimal::from_u32(2).unwrap();
1649 let min_stake_ratio = BigDecimal::from_str("0.01")?;
1650 let numerator = BigDecimal::from_str("0.03")?;
1651
1652 let denominator = (&two * (&proportion_staked).max(&min_stake_ratio))
1653 .sqrt()
1654 .context("Failed to compute sqrt in R(p)")?;
1655
1656 let reward_rate = numerator / denominator;
1657
1658 tracing::debug!("rp={reward_rate}");
1659
1660 Ok((proportion_staked, reward_rate))
1661}
1662
1663pub(crate) fn compute_block_reward(
1664 epoch: EpochNumber,
1665 total_supply: U256,
1666 total_stake: U256,
1667 avg_block_time_ms: u64,
1668) -> anyhow::Result<RewardAmount> {
1669 let total_stake_bd = BigDecimal::from_str(&total_stake.to_string())?;
1671 let total_supply_bd = BigDecimal::from_str(&(total_supply.to_string()))?;
1672
1673 tracing::debug!(?epoch, "total_stake={total_stake}");
1674 tracing::debug!(?epoch, "total_supply_bd={total_supply_bd}");
1675
1676 let (proportion, reward_rate) =
1677 calculate_proportion_staked_and_reward_rate(&total_stake_bd, &total_supply_bd)?;
1678 let inflation_rate = proportion * reward_rate;
1679
1680 tracing::debug!(?epoch, "inflation_rate={inflation_rate:?}");
1681
1682 let blocks_per_year = MILLISECONDS_PER_YEAR
1683 .checked_div(avg_block_time_ms.into())
1684 .context("avg_block_time_ms is zero")?;
1685
1686 tracing::debug!(?epoch, "blocks_per_year={blocks_per_year:?}");
1687
1688 ensure!(!blocks_per_year.is_zero(), "blocks per year is zero");
1689 let block_reward = (total_supply_bd * inflation_rate) / blocks_per_year;
1690
1691 let block_reward_u256 = U256::from_str(&block_reward.round(0).to_string())?;
1692
1693 Ok(block_reward_u256.into())
1694}
1695
1696#[derive(Error, Debug)]
1697enum GetStakeTablesError {
1699 #[error("Error fetching from L1: {0}")]
1700 L1ClientFetchError(anyhow::Error),
1701}
1702
1703#[cfg(any(test, feature = "testing"))]
1704impl super::v0_3::StakeTable {
1705 pub fn mock(n: u64) -> Self {
1707 [..n]
1708 .iter()
1709 .map(|_| PeerConfig::test_default())
1710 .collect::<Vec<PeerConfig<SeqTypes>>>()
1711 .into()
1712 }
1713}
1714
1715#[cfg(any(test, feature = "testing"))]
1716impl DAMembers {
1717 pub fn mock(n: u64) -> Self {
1719 [..n]
1720 .iter()
1721 .map(|_| PeerConfig::test_default())
1722 .collect::<Vec<PeerConfig<SeqTypes>>>()
1723 .into()
1724 }
1725}
1726
1727#[cfg(any(test, feature = "testing"))]
1728pub mod testing {
1729 use alloy::primitives::Bytes;
1730 use hotshot_contract_adapter::{
1731 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
1732 stake_table::{StateSignatureSol, sign_address_bls, sign_address_schnorr},
1733 };
1734 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
1735 use rand::{Rng as _, RngCore as _};
1736
1737 use super::*;
1738
1739 #[derive(Debug, Clone)]
1742 pub struct TestValidator {
1743 pub account: Address,
1744 pub bls_vk: G2PointSol,
1745 pub schnorr_vk: EdOnBN254PointSol,
1746 pub commission: u16,
1747 pub bls_sig: G1PointSol,
1748 pub schnorr_sig: Bytes,
1749 pub x25519_key: [u8; 32],
1750 pub p2p_addr: String,
1751 }
1752
1753 impl TestValidator {
1754 pub fn random() -> Self {
1755 let account = Address::random();
1756 let commission = rand::thread_rng().gen_range(0..10000);
1757 Self::random_update_keys(account, commission)
1758 }
1759
1760 pub fn randomize_keys(&self) -> Self {
1761 Self::random_update_keys(self.account, self.commission)
1762 }
1763
1764 pub fn random_update_keys(account: Address, commission: u16) -> Self {
1765 let mut rng = &mut rand::thread_rng();
1766 let mut seed = [0u8; 32];
1767 rng.fill_bytes(&mut seed);
1768 let bls_key_pair = BLSKeyPair::generate(&mut rng);
1769 let bls_sig = sign_address_bls(&bls_key_pair, account);
1770 let schnorr_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
1771 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, account);
1772 let mut x25519_key = [0u8; 32];
1773 rng.fill_bytes(&mut x25519_key);
1774 Self {
1775 account,
1776 bls_vk: bls_key_pair.ver_key().to_affine().into(),
1777 schnorr_vk: schnorr_key_pair.ver_key().to_affine().into(),
1778 commission,
1779 bls_sig: bls_sig.into(),
1780 schnorr_sig: StateSignatureSol::from(schnorr_sig).into(),
1781 x25519_key,
1782 p2p_addr: "127.0.0.1:9000".to_string(),
1783 }
1784 }
1785
1786 pub fn with_x25519_key(mut self, x25519_key: [u8; 32]) -> Self {
1787 self.x25519_key = x25519_key;
1788 self
1789 }
1790
1791 pub fn with_p2p_addr(mut self, p2p_addr: impl Into<String>) -> Self {
1792 self.p2p_addr = p2p_addr.into();
1793 self
1794 }
1795
1796 pub fn x25519_update(&self, x25519_key: [u8; 32]) -> StakeTableEvent {
1797 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
1798 validator: self.account,
1799 x25519Key: alloy::primitives::FixedBytes(x25519_key),
1800 })
1801 }
1802
1803 pub fn p2p_update(&self, p2p_addr: impl Into<String>) -> StakeTableEvent {
1804 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
1805 validator: self.account,
1806 p2pAddr: p2p_addr.into(),
1807 })
1808 }
1809 }
1810
1811 impl From<&TestValidator> for ValidatorRegistered {
1812 fn from(value: &TestValidator) -> Self {
1813 Self {
1814 account: value.account,
1815 blsVk: value.bls_vk,
1816 schnorrVk: value.schnorr_vk,
1817 commission: value.commission,
1818 }
1819 }
1820 }
1821
1822 impl From<&TestValidator> for ValidatorRegisteredV2 {
1823 fn from(value: &TestValidator) -> Self {
1824 Self {
1825 account: value.account,
1826 blsVK: value.bls_vk,
1827 schnorrVK: value.schnorr_vk,
1828 commission: value.commission,
1829 blsSig: value.bls_sig.into(),
1830 schnorrSig: value.schnorr_sig.clone(),
1831 metadataUri: "dummy-meta".to_string(),
1832 }
1833 }
1834 }
1835
1836 impl From<&TestValidator> for ValidatorRegisteredV3 {
1837 fn from(value: &TestValidator) -> Self {
1838 Self {
1839 account: value.account,
1840 blsVK: value.bls_vk,
1841 schnorrVK: value.schnorr_vk,
1842 commission: value.commission,
1843 blsSig: value.bls_sig.into(),
1844 schnorrSig: value.schnorr_sig.clone(),
1845 metadataUri: "dummy-meta".to_string(),
1846 x25519Key: alloy::primitives::FixedBytes(value.x25519_key),
1847 p2pAddr: value.p2p_addr.clone(),
1848 }
1849 }
1850 }
1851
1852 impl From<&TestValidator> for ConsensusKeysUpdated {
1853 fn from(value: &TestValidator) -> Self {
1854 Self {
1855 account: value.account,
1856 blsVK: value.bls_vk,
1857 schnorrVK: value.schnorr_vk,
1858 }
1859 }
1860 }
1861
1862 impl From<&TestValidator> for ConsensusKeysUpdatedV2 {
1863 fn from(value: &TestValidator) -> Self {
1864 Self {
1865 account: value.account,
1866 blsVK: value.bls_vk,
1867 schnorrVK: value.schnorr_vk,
1868 blsSig: value.bls_sig.into(),
1869 schnorrSig: value.schnorr_sig.clone(),
1870 }
1871 }
1872 }
1873
1874 impl From<&TestValidator> for ValidatorExit {
1875 fn from(value: &TestValidator) -> Self {
1876 Self {
1877 validator: value.account,
1878 }
1879 }
1880 }
1881
1882 impl RegisteredValidator<BLSPubKey> {
1883 pub fn mock() -> RegisteredValidator<BLSPubKey> {
1884 let val = TestValidator::random();
1885 let rng = &mut rand::thread_rng();
1886 let mut seed = [1u8; 32];
1887 rng.fill_bytes(&mut seed);
1888 let mut validator_stake = alloy::primitives::U256::from(0);
1889 let mut delegators = HashMap::new();
1890 for _i in 0..=5000 {
1891 let stake: u64 = rng.gen_range(0..10000);
1892 delegators.insert(Address::random(), alloy::primitives::U256::from(stake));
1893 validator_stake += alloy::primitives::U256::from(stake);
1894 }
1895
1896 let stake_table_key = val.bls_vk.into();
1897 let state_ver_key = val.schnorr_vk.into();
1898
1899 RegisteredValidator {
1900 account: val.account,
1901 stake_table_key,
1902 state_ver_key,
1903 stake: validator_stake,
1904 commission: val.commission,
1905 delegators,
1906 authenticated: true,
1907 x25519_key: None,
1908 p2p_addr: None,
1909 }
1910 }
1911 }
1912
1913 impl AuthenticatedValidator<BLSPubKey> {
1914 pub fn mock() -> AuthenticatedValidator<BLSPubKey> {
1915 RegisteredValidator::mock()
1916 .try_into()
1917 .expect("mock validator is always authenticated")
1918 }
1919
1920 pub fn mock_with_commission(commission: u16) -> AuthenticatedValidator<BLSPubKey> {
1921 let mut inner = RegisteredValidator::mock();
1922 inner.commission = commission;
1923 inner
1924 .try_into()
1925 .expect("mock validator is always authenticated")
1926 }
1927 }
1928}
1929
1930#[cfg(test)]
1931mod tests {
1932 use alloy::{primitives::Address, rpc::types::Log};
1933 use hotshot_contract_adapter::stake_table::{StakeTableContractVersion, sign_address_bls};
1934 use hotshot_types::signature_key::BLSKeyPair;
1935 use pretty_assertions::assert_matches;
1936 use rstest::rstest;
1937 use versions::{
1938 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
1939 };
1940
1941 use super::*;
1942 use crate::{L1ClientOptions, v0::impls::testing::*};
1943
1944 #[test_log::test]
1945 fn test_from_l1_events() -> anyhow::Result<()> {
1946 let val_1 = TestValidator::random();
1948 let val_1_new_keys = val_1.randomize_keys();
1949 let val_2 = TestValidator::random();
1950 let val_2_new_keys = val_2.randomize_keys();
1951 let delegator = Address::random();
1952 let mut events: Vec<StakeTableEvent> = [
1953 ValidatorRegistered::from(&val_1).into(),
1954 ValidatorRegisteredV2::from(&val_2).into(),
1955 Delegated {
1956 delegator,
1957 validator: val_1.account,
1958 amount: U256::from(10),
1959 }
1960 .into(),
1961 ConsensusKeysUpdated::from(&val_1_new_keys).into(),
1962 ConsensusKeysUpdatedV2::from(&val_2_new_keys).into(),
1963 Undelegated {
1964 delegator,
1965 validator: val_1.account,
1966 amount: U256::from(7),
1967 }
1968 .into(),
1969 Delegated {
1971 delegator,
1972 validator: val_1.account,
1973 amount: U256::from(5),
1974 }
1975 .into(),
1976 Delegated {
1978 delegator: Address::random(),
1979 validator: val_2.account,
1980 amount: U256::from(3),
1981 }
1982 .into(),
1983 ]
1984 .to_vec();
1985
1986 let validators_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
1987 let st = validators_set.active_validators;
1988 let st_val_1 = st.get(&val_1.account).unwrap();
1989 assert_eq!(st_val_1.stake, U256::from(8));
1991 assert_eq!(st_val_1.commission, val_1.commission);
1992 assert_eq!(st_val_1.delegators.len(), 1);
1993 assert_eq!(*st_val_1.delegators.get(&delegator).unwrap(), U256::from(8));
1995
1996 let st_val_2 = st.get(&val_2.account).unwrap();
1997 assert_eq!(st_val_2.stake, U256::from(3));
1998 assert_eq!(st_val_2.commission, val_2.commission);
1999 assert_eq!(st_val_2.delegators.len(), 1);
2000
2001 events.push(ValidatorExit::from(&val_1).into());
2002
2003 let validator_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
2004 let st = validator_set.active_validators;
2005 assert_eq!(st.get(&val_1.account), None);
2007
2008 let st_val_2 = st.get(&val_2.account).unwrap();
2010 assert_eq!(st_val_2.stake, U256::from(3));
2011 assert_eq!(st_val_2.commission, val_2.commission);
2012 assert_eq!(st_val_2.delegators.len(), 1);
2013
2014 events.push(ValidatorExit::from(&val_2).into());
2016
2017 assert!(ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION).is_err());
2019
2020 Ok(())
2021 }
2022
2023 #[test]
2024 fn test_from_l1_events_failures() -> anyhow::Result<()> {
2025 let val = TestValidator::random();
2026 let delegator = Address::random();
2027
2028 let register: StakeTableEvent = ValidatorRegistered::from(&val).into();
2029 let register_v2: StakeTableEvent = ValidatorRegisteredV2::from(&val).into();
2030 let delegate: StakeTableEvent = Delegated {
2031 delegator,
2032 validator: val.account,
2033 amount: U256::from(10),
2034 }
2035 .into();
2036 let key_update: StakeTableEvent = ConsensusKeysUpdated::from(&val).into();
2037 let key_update_v2: StakeTableEvent = ConsensusKeysUpdatedV2::from(&val).into();
2038 let undelegate: StakeTableEvent = Undelegated {
2039 delegator,
2040 validator: val.account,
2041 amount: U256::from(7),
2042 }
2043 .into();
2044
2045 let exit: StakeTableEvent = ValidatorExit::from(&val).into();
2046
2047 let cases = [
2048 vec![exit],
2049 vec![undelegate.clone()],
2050 vec![delegate.clone()],
2051 vec![key_update],
2052 vec![key_update_v2],
2053 vec![register.clone(), register.clone()],
2054 vec![register_v2.clone(), register_v2.clone()],
2055 vec![register.clone(), register_v2.clone()],
2056 vec![register_v2.clone(), register.clone()],
2057 vec![
2058 register,
2059 delegate.clone(),
2060 undelegate.clone(),
2061 undelegate.clone(),
2062 ],
2063 vec![register_v2, delegate, undelegate.clone(), undelegate],
2064 ];
2065
2066 for events in cases.iter() {
2067 let res = validators_from_l1_events(events.iter().cloned());
2071 assert!(
2072 res.is_err(),
2073 "events {res:?}, not a valid sequence of events"
2074 );
2075 }
2076 Ok(())
2077 }
2078
2079 #[test]
2080 fn test_validators_selection() {
2081 let mut candidates = IndexMap::new();
2082 let mut highest_stake = alloy::primitives::U256::ZERO;
2083
2084 for _i in 0..3000 {
2085 let candidate = RegisteredValidator::mock();
2086 candidates.insert(candidate.account, candidate.clone());
2087
2088 if candidate.stake > highest_stake {
2089 highest_stake = candidate.stake;
2090 }
2091 }
2092
2093 let minimum_stake = highest_stake / U256::from(VID_TARGET_TOTAL_STAKE);
2094
2095 let selected_validators = select_active_validator_set(&candidates, EPOCH_VERSION)
2096 .expect("Failed to select validators");
2097 assert!(
2098 selected_validators.len() <= MAX_VALIDATORS,
2099 "validators len is {}, expected at most {MAX_VALIDATORS}",
2100 selected_validators.len()
2101 );
2102
2103 let mut selected_validators_highest_stake = alloy::primitives::U256::ZERO;
2104 for (address, validator) in &selected_validators {
2106 assert!(
2107 validator.stake >= minimum_stake,
2108 "Validator {:?} has stake below minimum: {}",
2109 address,
2110 validator.stake
2111 );
2112
2113 if validator.stake > selected_validators_highest_stake {
2114 selected_validators_highest_stake = validator.stake;
2115 }
2116 }
2117 }
2118
2119 #[rstest::rstest]
2122 fn test_regression_non_unique_bls_keys_not_discarded(
2123 #[values(
2124 StakeTableContractVersion::V1,
2125 StakeTableContractVersion::V2,
2126 StakeTableContractVersion::V3
2127 )]
2128 version: StakeTableContractVersion,
2129 ) {
2130 let val = TestValidator::random();
2131 let register: StakeTableEvent = match version {
2132 StakeTableContractVersion::V1 => ValidatorRegistered::from(&val).into(),
2133 StakeTableContractVersion::V2 => ValidatorRegisteredV2::from(&val).into(),
2134 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&val).into()),
2135 };
2136 let delegate: StakeTableEvent = Delegated {
2137 delegator: Address::random(),
2138 validator: val.account,
2139 amount: U256::from(10),
2140 }
2141 .into();
2142
2143 assert!(
2145 ValidatorSet::from_l1_events(
2146 vec![register.clone(), delegate.clone()].into_iter(),
2147 EPOCH_VERSION,
2148 )
2149 .is_ok()
2150 );
2151
2152 let key_update = ConsensusKeysUpdated::from(&val).into();
2154 let err = ValidatorSet::from_l1_events(
2155 vec![register, delegate, key_update].into_iter(),
2156 EPOCH_VERSION,
2157 )
2158 .unwrap_err();
2159
2160 let bls: BLSPubKey = val.bls_vk.into();
2161 assert!(matches!(err, StakeTableError::BlsKeyAlreadyUsed(addr) if addr == bls.to_string()));
2162 }
2163
2164 #[test]
2167 fn test_regression_reregister_eth_account() {
2168 let val1 = TestValidator::random();
2169 let val2 = val1.randomize_keys();
2170 let account = val1.account;
2171
2172 let register1 = ValidatorRegisteredV2::from(&val1).into();
2173 let deregister1 = ValidatorExit::from(&val1).into();
2174 let register2 = ValidatorRegisteredV2::from(&val2).into();
2175 let events = [register1, deregister1, register2];
2176 let error = validators_from_l1_events(events.iter().cloned()).unwrap_err();
2177 assert_matches!(error, StakeTableError::ValidatorAlreadyExited(addr) if addr == account);
2178 }
2179
2180 #[test]
2181 fn test_display_log() {
2182 let serialized = r#"{"address":"0x0000000000000000000000000000000000000069",
2183 "topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],
2184 "data":"0x69",
2185 "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2186 "blockNumber":"0x69","blockTimestamp":"0x69",
2187 "transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2188 "transactionIndex":"0x69","logIndex":"0x70","removed":false}"#;
2189 let log: Log = serde_json::from_str(serialized).unwrap();
2190 assert_eq!(
2191 log.display(),
2192 "Log(block=105,index=112,\
2193 transaction_hash=0x0000000000000000000000000000000000000000000000000000000000000069)"
2194 )
2195 }
2196
2197 #[rstest]
2198 #[case::v1(StakeTableContractVersion::V1)]
2199 #[case::v2(StakeTableContractVersion::V2)]
2200 #[case::v3(StakeTableContractVersion::V3)]
2201 fn test_register_validator(#[case] version: StakeTableContractVersion) {
2202 let mut state = StakeTableState::default();
2203 let validator = TestValidator::random();
2204
2205 let event = match version {
2206 StakeTableContractVersion::V1 => StakeTableEvent::Register((&validator).into()),
2207 StakeTableContractVersion::V2 => StakeTableEvent::RegisterV2((&validator).into()),
2208 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&validator).into()),
2209 };
2210
2211 state.apply_event(event).unwrap().unwrap();
2212
2213 let stored = state.validators.get(&validator.account).unwrap();
2214 assert_eq!(stored.account, validator.account);
2215 }
2216
2217 #[rstest]
2218 #[case::v1(StakeTableContractVersion::V1)]
2219 #[case::v2(StakeTableContractVersion::V2)]
2220 #[case::v3(StakeTableContractVersion::V3)]
2221 fn test_validator_already_registered(#[case] version: StakeTableContractVersion) {
2222 let mut stake_table_state = StakeTableState::default();
2223
2224 let test_validator = TestValidator::random();
2225
2226 match version {
2228 StakeTableContractVersion::V1 => {
2229 stake_table_state.apply_event(StakeTableEvent::Register((&test_validator).into()))
2230 },
2231 StakeTableContractVersion::V2 => {
2232 stake_table_state.apply_event(StakeTableEvent::RegisterV2((&test_validator).into()))
2233 },
2234 StakeTableContractVersion::V3 => {
2235 stake_table_state.apply_event(StakeTableEvent::RegisterV3((&test_validator).into()))
2236 },
2237 }
2238 .unwrap()
2239 .unwrap(); let v1_already_registered_result = stake_table_state
2243 .clone()
2244 .apply_event(StakeTableEvent::Register((&test_validator).into()));
2245
2246 pretty_assertions::assert_matches!(
2247 v1_already_registered_result, Err(StakeTableError::AlreadyRegistered(account))
2248 if account == test_validator.account,
2249 "Expected AlreadyRegistered error. version ={version:?} result={v1_already_registered_result:?}",
2250 );
2251
2252 let v2_already_registered_result = stake_table_state
2254 .clone()
2255 .apply_event(StakeTableEvent::RegisterV2((&test_validator).into()));
2256
2257 pretty_assertions::assert_matches!(
2258 v2_already_registered_result,
2259 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2260 "Expected AlreadyRegistered error. version ={version:?} result={v2_already_registered_result:?}",
2261
2262 );
2263
2264 let v3_already_registered_result =
2267 stake_table_state
2268 .clone()
2269 .apply_event(StakeTableEvent::RegisterV3(
2270 (&test_validator
2271 .clone()
2272 .with_x25519_key([43u8; 32])
2273 .with_p2p_addr("127.0.0.1:9001"))
2274 .into(),
2275 ));
2276
2277 pretty_assertions::assert_matches!(
2278 v3_already_registered_result,
2279 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2280 "Expected AlreadyRegistered error. version ={version:?} result={v3_already_registered_result:?}",
2281 );
2282 }
2283
2284 #[test]
2285 fn test_register_validator_v2_auth_fails_marks_as_unauthenticated() {
2286 let mut state = StakeTableState::default();
2287 let mut val = TestValidator::random();
2288 val.bls_sig = Default::default();
2289 let event = StakeTableEvent::RegisterV2((&val).into());
2290
2291 let result = state.apply_event(event);
2292 assert!(
2293 result.is_ok(),
2294 "Validator with invalid auth should still be accepted"
2295 );
2296
2297 let validator = state
2298 .validators()
2299 .get(&val.account)
2300 .expect("validator should exist");
2301 assert!(
2302 !validator.authenticated,
2303 "Validator should be marked as not authenticated"
2304 );
2305
2306 let event = StakeTableEvent::Delegate(Delegated {
2307 delegator: Address::random(),
2308 validator: val.account,
2309 amount: U256::from(100),
2310 });
2311 state.apply_event(event).unwrap().unwrap();
2312
2313 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
2314 match active {
2315 Err(_) => {}, Ok(map) => {
2317 assert!(
2318 map.get(&val.account).is_none(),
2319 "Unauthenticated validator should not be in active set"
2320 );
2321 },
2322 }
2323 }
2324
2325 #[test]
2326 fn test_authenticated_validator_deserialize_rejects_unauthenticated() {
2327 let mut validator = RegisteredValidator::<BLSPubKey>::mock();
2328 validator.authenticated = false;
2329
2330 let json = serde_json::to_string(&validator).unwrap();
2331 let result: Result<AuthenticatedValidator<BLSPubKey>, _> = serde_json::from_str(&json);
2332
2333 assert!(result.is_err());
2334 let err = result.unwrap_err().to_string();
2335 assert!(
2336 err.contains("cannot deserialize unauthenticated validator"),
2337 "unexpected error: {err}"
2338 );
2339 }
2340
2341 #[rstest]
2342 #[case::v1(StakeTableContractVersion::V1)]
2343 #[case::v2(StakeTableContractVersion::V2)]
2344 #[case::v3(StakeTableContractVersion::V3)]
2345 fn test_deregister_validator(#[case] version: StakeTableContractVersion) {
2346 let mut state = StakeTableState::default();
2347 let val = TestValidator::random();
2348
2349 let reg = StakeTableEvent::Register((&val).into());
2350 state.apply_event(reg).unwrap().unwrap();
2351
2352 let dereg = match version {
2353 StakeTableContractVersion::V1 => StakeTableEvent::Deregister((&val).into()),
2354 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2355 StakeTableEvent::DeregisterV2(ValidatorExitV2 {
2356 validator: val.account,
2357 unlocksAt: U256::from(1000u64),
2358 })
2359 },
2360 };
2361 state.apply_event(dereg).unwrap().unwrap();
2362 assert!(!state.validators.contains_key(&val.account));
2363 }
2364
2365 #[rstest]
2366 #[case::v1(StakeTableContractVersion::V1)]
2367 #[case::v2(StakeTableContractVersion::V2)]
2368 #[case::v3(StakeTableContractVersion::V3)]
2369 fn test_delegate_and_undelegate(#[case] version: StakeTableContractVersion) {
2370 let mut state = StakeTableState::default();
2371 let val = TestValidator::random();
2372 state
2373 .apply_event(StakeTableEvent::Register((&val).into()))
2374 .unwrap()
2375 .unwrap();
2376
2377 let delegator = Address::random();
2378 let amount = U256::from(1000);
2379 let delegate_event = StakeTableEvent::Delegate(Delegated {
2380 delegator,
2381 validator: val.account,
2382 amount,
2383 });
2384 state.apply_event(delegate_event).unwrap().unwrap();
2385
2386 let validator = state.validators.get(&val.account).unwrap();
2387 assert_eq!(validator.delegators.get(&delegator).cloned(), Some(amount));
2388
2389 let undelegate_event = match version {
2390 StakeTableContractVersion::V1 => StakeTableEvent::Undelegate(Undelegated {
2391 delegator,
2392 validator: val.account,
2393 amount,
2394 }),
2395 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2396 StakeTableEvent::UndelegateV2(UndelegatedV2 {
2397 delegator,
2398 validator: val.account,
2399 amount,
2400 unlocksAt: U256::from(2000u64),
2401 undelegationId: 1,
2402 })
2403 },
2404 };
2405 state.apply_event(undelegate_event).unwrap().unwrap();
2406 let validator = state.validators.get(&val.account).unwrap();
2407 assert!(!validator.delegators.contains_key(&delegator));
2408 }
2409
2410 #[rstest]
2411 #[case::v1(StakeTableContractVersion::V1)]
2412 #[case::v2(StakeTableContractVersion::V2)]
2413 #[case::v3(StakeTableContractVersion::V3)]
2414 fn test_key_update_event(#[case] version: StakeTableContractVersion) {
2415 let mut state = StakeTableState::default();
2416 let val = TestValidator::random();
2417
2418 state
2420 .apply_event(StakeTableEvent::Register((&val).into()))
2421 .unwrap()
2422 .unwrap();
2423
2424 let new_keys = val.randomize_keys();
2425
2426 let event = match version {
2427 StakeTableContractVersion::V1 => StakeTableEvent::KeyUpdate((&new_keys).into()),
2428 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2429 StakeTableEvent::KeyUpdateV2((&new_keys).into())
2430 },
2431 };
2432
2433 state.apply_event(event).unwrap().unwrap();
2434
2435 let updated = state.validators.get(&val.account).unwrap();
2436 assert_eq!(updated.stake_table_key, new_keys.bls_vk.into());
2437 assert_eq!(updated.state_ver_key, new_keys.schnorr_vk.into());
2438 }
2439
2440 #[test]
2441 fn test_duplicate_bls_key() {
2442 let mut state = StakeTableState::default();
2443 let val = TestValidator::random();
2444 let event1 = StakeTableEvent::Register((&val).into());
2445 let mut val2 = TestValidator::random();
2446 val2.bls_vk = val.bls_vk;
2447 val2.account = Address::random();
2448
2449 let event2 = StakeTableEvent::Register((&val2).into());
2450 state.apply_event(event1).unwrap().unwrap();
2451 let result = state.apply_event(event2);
2452
2453 let expected_bls_key = BLSPubKey::from(val.bls_vk).to_string();
2454
2455 assert_matches!(
2456 result,
2457 Err(StakeTableError::BlsKeyAlreadyUsed(key))
2458 if key == expected_bls_key,
2459 "Expected BlsKeyAlreadyUsed({expected_bls_key}), but got: {result:?}",
2460 );
2461 }
2462
2463 #[test]
2464 fn test_duplicate_schnorr_key() {
2465 let mut state = StakeTableState::default();
2466 let val = TestValidator::random();
2467 let event1 = StakeTableEvent::Register((&val).into());
2468 let mut val2 = TestValidator::random();
2469 val2.schnorr_vk = val.schnorr_vk;
2470 val2.account = Address::random();
2471 val2.bls_vk = val2.randomize_keys().bls_vk;
2472
2473 let event2 = StakeTableEvent::Register((&val2).into());
2474 state.apply_event(event1).unwrap().unwrap();
2475 let result = state.apply_event(event2);
2476
2477 let schnorr: SchnorrPubKey = val.schnorr_vk.into();
2478 assert_matches!(
2479 result,
2480 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
2481 if key == schnorr.to_string(),
2482 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2483
2484 );
2485 }
2486
2487 #[test]
2488 fn test_duplicate_schnorr_key_v2_during_update() {
2489 let mut state = StakeTableState::default();
2490
2491 let val1 = TestValidator::random();
2492
2493 let mut rng = &mut rand::thread_rng();
2494 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2495
2496 let val2 = TestValidator {
2497 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2498 bls_sig: sign_address_bls(&bls_key_pair, val1.account).into(),
2499 ..val1.clone()
2500 };
2501 let event1 = StakeTableEvent::RegisterV2((&val1).into());
2502 let event2 = StakeTableEvent::KeyUpdateV2((&val2).into());
2503
2504 state.apply_event(event1).unwrap().unwrap();
2505 let result = state.apply_event(event2);
2506
2507 let schnorr: SchnorrPubKey = val1.schnorr_vk.into();
2508 assert_matches!(
2509 result,
2510 Err(StakeTableError::SchnorrKeyAlreadyUsed(key))
2511 if key == schnorr.to_string(),
2512 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2513 );
2514 }
2515
2516 #[test]
2517 fn test_register_and_deregister_validator() {
2518 let mut state = StakeTableState::default();
2519 let validator = TestValidator::random();
2520 let event = StakeTableEvent::Register((&validator).into());
2521 state.apply_event(event).unwrap().unwrap();
2522
2523 let deregister_event = StakeTableEvent::Deregister((&validator).into());
2524 assert!(state.apply_event(deregister_event).unwrap().is_ok());
2525 }
2526
2527 #[test]
2528 fn test_commission_validation_exceeds_basis_points() {
2529 let validator = TestValidator::random();
2531 let mut stake_table = StakeTableState::default();
2532
2533 let registration_event = ValidatorRegistered::from(&validator).into();
2535 stake_table
2536 .apply_event(registration_event)
2537 .unwrap()
2538 .unwrap();
2539
2540 let valid_commission_event = CommissionUpdated {
2542 validator: validator.account,
2543 timestamp: Default::default(),
2544 oldCommission: 0,
2545 newCommission: COMMISSION_BASIS_POINTS, }
2547 .into();
2548 stake_table
2549 .apply_event(valid_commission_event)
2550 .unwrap()
2551 .unwrap();
2552
2553 let invalid_commission = COMMISSION_BASIS_POINTS + 1;
2554 let invalid_commission_event = CommissionUpdated {
2555 validator: validator.account,
2556 timestamp: Default::default(),
2557 oldCommission: 0,
2558 newCommission: invalid_commission,
2559 }
2560 .into();
2561
2562 let err = stake_table
2563 .apply_event(invalid_commission_event)
2564 .unwrap_err();
2565
2566 assert_matches!(
2567 err,
2568 StakeTableError::InvalidCommission(addr, invalid_commission)
2569 if addr == addr && invalid_commission == invalid_commission);
2570 }
2571
2572 #[test]
2573 fn test_delegate_zero_amount_is_rejected() {
2574 let mut state = StakeTableState::default();
2575 let validator = TestValidator::random();
2576 let account = validator.account;
2577 state
2578 .apply_event(StakeTableEvent::Register((&validator).into()))
2579 .unwrap()
2580 .unwrap();
2581
2582 let delegator = Address::random();
2583 let amount = U256::ZERO;
2584 let event = StakeTableEvent::Delegate(Delegated {
2585 delegator,
2586 validator: account,
2587 amount,
2588 });
2589 let result = state.apply_event(event);
2590
2591 assert_matches!(
2592 result,
2593 Err(StakeTableError::ZeroDelegatorStake(addr))
2594 if addr == delegator,
2595 "delegator stake is zero"
2596
2597 );
2598 }
2599
2600 #[test]
2601 fn test_undelegate_more_than_stake_fails() {
2602 let mut state = StakeTableState::default();
2603 let validator = TestValidator::random();
2604 let account = validator.account;
2605 state
2606 .apply_event(StakeTableEvent::Register((&validator).into()))
2607 .unwrap()
2608 .unwrap();
2609
2610 let delegator = Address::random();
2611 let event = StakeTableEvent::Delegate(Delegated {
2612 delegator,
2613 validator: account,
2614 amount: U256::from(10u64),
2615 });
2616 state.apply_event(event).unwrap().unwrap();
2617
2618 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2619 delegator,
2620 validator: account,
2621 amount: U256::from(20u64),
2622 }));
2623 assert_matches!(
2624 result,
2625 Err(StakeTableError::InsufficientStake),
2626 "Expected InsufficientStake error, got: {result:?}",
2627 );
2628 }
2629
2630 #[test]
2631 fn test_apply_event_does_not_modify_state_on_error() {
2632 let mut state = StakeTableState::default();
2633 let validator = TestValidator::random();
2634 let delegator = Address::random();
2635
2636 state
2637 .apply_event(StakeTableEvent::Register((&validator).into()))
2638 .unwrap()
2639 .unwrap();
2640
2641 let state_before = state.clone();
2643 let result = state.apply_event(StakeTableEvent::Register((&validator).into()));
2644 assert_matches!(result, Err(StakeTableError::AlreadyRegistered(_)));
2645 assert_eq!(
2646 state, state_before,
2647 "State should not change on AlreadyRegistered error"
2648 );
2649
2650 let state_before = state.clone();
2652 let mut validator2 = TestValidator::random();
2653 validator2.bls_vk = validator.bls_vk; let result = state.apply_event(StakeTableEvent::Register((&validator2).into()));
2655 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
2656 assert_eq!(
2657 state, state_before,
2658 "State should not change on BlsKeyAlreadyUsed error"
2659 );
2660
2661 let state_before = state.clone();
2663 let nonexistent_validator = TestValidator::random();
2664 let result =
2665 state.apply_event(StakeTableEvent::Deregister((&nonexistent_validator).into()));
2666 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
2667 assert_eq!(
2668 state, state_before,
2669 "State should not change on ValidatorNotFound error"
2670 );
2671
2672 let state_before = state.clone();
2674 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2675 delegator: Address::random(),
2676 validator: Address::random(),
2677 amount: U256::from(100u64),
2678 }));
2679 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
2680 assert_eq!(
2681 state, state_before,
2682 "State should not change on ValidatorNotFound error for Undelegate"
2683 );
2684
2685 state
2686 .apply_event(StakeTableEvent::Delegate(Delegated {
2687 delegator,
2688 validator: validator.account,
2689 amount: U256::from(100u64),
2690 }))
2691 .unwrap()
2692 .unwrap();
2693
2694 let state_before = state.clone();
2696 let non_existent_delegator = Address::random();
2697 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2698 delegator: non_existent_delegator,
2699 validator: validator.account,
2700 amount: U256::from(50u64),
2701 }));
2702 assert_matches!(result, Err(StakeTableError::DelegatorNotFound(_)));
2703 assert_eq!(
2704 state, state_before,
2705 "State should not change on DelegatorNotFound error"
2706 );
2707
2708 let state_before = state.clone();
2710 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2711 delegator,
2712 validator: validator.account,
2713 amount: U256::from(200u64),
2714 }));
2715 assert_matches!(result, Err(StakeTableError::InsufficientStake));
2716 assert_eq!(
2717 state, state_before,
2718 "State should not change on InsufficientStake error"
2719 );
2720
2721 let validator2 = TestValidator::random();
2723 let delegator2 = Address::random();
2724
2725 state
2726 .apply_event(StakeTableEvent::Register((&validator2).into()))
2727 .unwrap()
2728 .unwrap();
2729
2730 state
2731 .apply_event(StakeTableEvent::Delegate(Delegated {
2732 delegator: delegator2,
2733 validator: validator2.account,
2734 amount: U256::from(50u64),
2735 }))
2736 .unwrap()
2737 .unwrap();
2738 let state_before = state.clone();
2739 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2740 delegator: delegator2,
2741 validator: validator2.account,
2742 amount: U256::from(100u64),
2743 }));
2744 assert_matches!(result, Err(StakeTableError::InsufficientStake));
2745 assert_eq!(state, state_before,);
2746
2747 let state_before = state.clone();
2749 let result = state.apply_event(StakeTableEvent::Delegate(Delegated {
2750 delegator: Address::random(),
2751 validator: validator.account,
2752 amount: U256::ZERO,
2753 }));
2754 assert_matches!(result, Err(StakeTableError::ZeroDelegatorStake(_)));
2755 assert_eq!(
2756 state, state_before,
2757 "State should not change on ZeroDelegatorStake error"
2758 );
2759 }
2760
2761 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2762 async fn test_decaf_stake_table() {
2763 let events_json =
2799 std::fs::read_to_string("../../../data/v3/decaf_stake_table_events.json").unwrap();
2800 let events: Vec<(EventKey, StakeTableEvent)> = serde_json::from_str(&events_json).unwrap();
2801
2802 let reconstructed_stake_table =
2804 ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), EPOCH_VERSION)
2805 .unwrap()
2806 .active_validators;
2807
2808 let stake_table_json =
2809 std::fs::read_to_string("../../../data/v3/decaf_stake_table.json").unwrap();
2810 let expected: AuthenticatedValidatorMap = serde_json::from_str(&stake_table_json).unwrap();
2811
2812 assert_eq!(
2813 reconstructed_stake_table, expected,
2814 "Stake table reconstructed from events does not match the expected stake table "
2815 );
2816 }
2817
2818 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2819 #[should_panic]
2820 async fn test_large_max_events_range_panic() {
2821 let contract_address = "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037";
2823
2824 let l1 = L1ClientOptions {
2825 l1_events_max_retry_duration: Duration::from_secs(30),
2826 l1_events_max_block_range: 10_u64.pow(9),
2828 l1_retry_delay: Duration::from_secs(1),
2829 ..Default::default()
2830 }
2831 .connect(vec![
2832 "https://ethereum-sepolia.publicnode.com".parse().unwrap(),
2833 ])
2834 .expect("unable to construct l1 client");
2835
2836 let latest_block = l1.provider.get_block_number().await.unwrap();
2837 let _events = Fetcher::fetch_events_from_contract(
2838 l1,
2839 contract_address.parse().unwrap(),
2840 None,
2841 latest_block,
2842 )
2843 .await
2844 .unwrap();
2845 }
2846
2847 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2848 async fn sanity_check_block_reward_v3() {
2849 let initial_supply = U256::from_str("10000000000000000000000000000").unwrap();
2851
2852 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
2853 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
2854 .unwrap();
2855
2856 println!("Calculated reward: {reward}");
2857 assert!(reward > U256::ZERO);
2858 }
2859
2860 #[test]
2861 fn sanity_check_p_and_rp() {
2862 let total_stake = BigDecimal::from_str("1000").unwrap();
2863 let total_supply = BigDecimal::from_str("10000").unwrap(); let (p, rp) =
2866 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
2867
2868 assert!(p > BigDecimal::zero());
2869 assert!(p < BigDecimal::one());
2870 assert!(rp > BigDecimal::zero());
2871 }
2872
2873 #[test]
2874 fn test_p_out_of_range() {
2875 let total_stake = BigDecimal::from_str("1000").unwrap();
2876 let total_supply = BigDecimal::from_str("500").unwrap(); let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
2879 assert!(result.is_err());
2880 }
2881
2882 #[test]
2883 fn test_zero_total_supply() {
2884 let total_stake = BigDecimal::from_str("1000").unwrap();
2885 let total_supply = BigDecimal::from(0);
2886
2887 let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
2888 assert!(result.is_err());
2889 }
2890
2891 #[test]
2892 fn test_valid_p_and_rp() {
2893 let total_stake = BigDecimal::from_str("5000").unwrap();
2894 let total_supply = BigDecimal::from_str("10000").unwrap();
2895
2896 let (p, rp) =
2897 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
2898
2899 assert_eq!(p, BigDecimal::from_str("0.5").unwrap());
2900 assert!(rp > BigDecimal::from_str("0.0").unwrap());
2901 }
2902
2903 #[test]
2904 fn test_very_small_p() {
2905 let total_stake = BigDecimal::from_str("1").unwrap(); let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap(); let (p, rp) =
2909 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
2910
2911 assert!(p > BigDecimal::from_str("0").unwrap());
2912 assert!(p < BigDecimal::from_str("1e-18").unwrap()); assert!(rp > BigDecimal::zero());
2914 }
2915
2916 #[test]
2917 fn test_p_very_close_to_one() {
2918 let total_stake = BigDecimal::from_str("9999999999999999999999999999").unwrap();
2919 let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap();
2920
2921 let (p, rp) =
2922 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
2923
2924 assert!(p < BigDecimal::one());
2925 assert!(p > BigDecimal::from_str("0.999999999999999999999999999").unwrap());
2926 assert!(rp > BigDecimal::zero());
2927 }
2928
2929 #[test]
2933 fn test_reward_rate_rp() {
2934 let test_cases = [
2935 ("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"), ];
2947
2948 let tolerance = BigDecimal::from_str("0.0001").unwrap();
2949
2950 let total_supply = BigDecimal::from_u32(10_000).unwrap();
2951
2952 for (p, rp) in test_cases {
2953 let p = BigDecimal::from_str(p).unwrap();
2954 let expected_rp = BigDecimal::from_str(rp).unwrap();
2955
2956 let total_stake = &p * &total_supply;
2957
2958 let (computed_p, computed_rp) =
2959 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
2960
2961 assert!(
2962 (&computed_p - &p).abs() < tolerance,
2963 "p mismatch: got {computed_p}, expected {p}"
2964 );
2965
2966 assert!(
2967 (&computed_rp - &expected_rp).abs() < tolerance,
2968 "R(p) mismatch for p={p}: got {computed_rp}, expected {expected_rp}"
2969 );
2970 }
2971 }
2972
2973 #[tokio::test(flavor = "multi_thread")]
2974 async fn test_dynamic_block_reward_with_expected_values() {
2975 let total_supply = U256::from_str("10000000000000000000000000000").unwrap();
2977 let total_supply_bd = BigDecimal::from_str(&total_supply.to_string()).unwrap();
2978
2979 let test_cases = [
2980 ("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"), ];
3014
3015 let tolerance = U256::from(100_000_000_000_000_000u128); for (p, rp, avg_block_time_ms, expected_reward) in test_cases {
3018 let p = BigDecimal::from_str(p).unwrap();
3019 let total_stake_bd = (&p * &total_supply_bd).round(0);
3020 println!("total_stake_bd={total_stake_bd}");
3021
3022 let total_stake = U256::from_str(&total_stake_bd.to_plain_string()).unwrap();
3023 let expected_reward = U256::from_str(expected_reward).unwrap();
3024
3025 let epoch = EpochNumber::new(0);
3026 let actual_reward =
3027 compute_block_reward(epoch, total_supply, total_stake, avg_block_time_ms)
3028 .unwrap()
3029 .0;
3030
3031 let diff = if actual_reward > expected_reward {
3032 actual_reward - expected_reward
3033 } else {
3034 expected_reward - actual_reward
3035 };
3036
3037 assert!(
3038 diff <= tolerance,
3039 "Reward mismatch for p = {p}, R(p) = {rp}, block_time = {avg_block_time_ms}: \
3040 expected = {expected_reward}, actual = {actual_reward}, diff = {diff}"
3041 );
3042 }
3043 }
3044
3045 #[derive(Debug, Clone, Copy)]
3047 enum EventType {
3048 Delegate,
3049 Undelegate,
3050 KeyUpdate,
3051 CommissionUpdate,
3052 Exit,
3053 X25519KeyUpdate,
3054 P2pAddrUpdate,
3055 }
3056
3057 #[rstest]
3062 #[case::delegate(EventType::Delegate)]
3063 #[case::undelegate(EventType::Undelegate)]
3064 #[case::key_update(EventType::KeyUpdate)]
3065 #[case::commission_update(EventType::CommissionUpdate)]
3066 #[case::exit(EventType::Exit)]
3067 #[case::x25519_key_update(EventType::X25519KeyUpdate)]
3068 #[case::p2p_addr_update(EventType::P2pAddrUpdate)]
3069 fn test_events_targeting_unauthenticated_validator(
3070 #[case] event_type: EventType,
3071 ) -> anyhow::Result<()> {
3072 let mut state = StakeTableState::default();
3073 let mut val = TestValidator::random();
3074 val.bls_sig = Default::default();
3075 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3076
3077 let validator = state.validators().get(&val.account).context("validator")?;
3078 assert!(!validator.authenticated);
3079
3080 let delegator = Address::random();
3081 let initial_amount = U256::from(1000);
3082 state.apply_event(StakeTableEvent::Delegate(Delegated {
3083 delegator,
3084 validator: val.account,
3085 amount: initial_amount,
3086 }))??;
3087
3088 match event_type {
3089 EventType::Delegate => {
3090 let new_delegator = Address::random();
3091 let amount = U256::from(500);
3092 state.apply_event(StakeTableEvent::Delegate(Delegated {
3093 delegator: new_delegator,
3094 validator: val.account,
3095 amount,
3096 }))??;
3097
3098 let validator = state.validators().get(&val.account).context("validator")?;
3099 assert_eq!(validator.stake, initial_amount + amount);
3100 assert_eq!(
3101 validator.delegators.get(&new_delegator).cloned(),
3102 Some(amount)
3103 );
3104 },
3105 EventType::Undelegate => {
3106 let undelegate_amount = U256::from(300);
3107 state.apply_event(StakeTableEvent::UndelegateV2(UndelegatedV2 {
3108 delegator,
3109 validator: val.account,
3110 undelegationId: 1,
3111 amount: undelegate_amount,
3112 unlocksAt: U256::from(1000u64),
3113 }))??;
3114
3115 let validator = state.validators().get(&val.account).context("validator")?;
3116 assert_eq!(validator.stake, initial_amount - undelegate_amount);
3117 assert_eq!(
3118 validator.delegators.get(&delegator).cloned(),
3119 Some(initial_amount - undelegate_amount)
3120 );
3121 },
3122 EventType::KeyUpdate => {
3123 let new_keys = val.randomize_keys();
3124 state.apply_event(StakeTableEvent::KeyUpdateV2((&new_keys).into()))??;
3125
3126 let validator = state.validators().get(&val.account).context("validator")?;
3127 let expected_bls: BLSPubKey = new_keys.bls_vk.into();
3128 let expected_schnorr: SchnorrPubKey = new_keys.schnorr_vk.into();
3129 assert_eq!(validator.stake_table_key, expected_bls);
3130 assert_eq!(validator.state_ver_key, expected_schnorr);
3131 assert!(!validator.authenticated);
3133 },
3134 EventType::CommissionUpdate => {
3135 let new_commission: u16 = 5000;
3136 state.apply_event(StakeTableEvent::CommissionUpdate(CommissionUpdated {
3137 validator: val.account,
3138 timestamp: Default::default(),
3139 oldCommission: val.commission,
3140 newCommission: new_commission,
3141 }))??;
3142
3143 let validator = state.validators().get(&val.account).context("validator")?;
3144 assert_eq!(validator.commission, new_commission);
3145 },
3146 EventType::Exit => {
3147 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3148 validator: val.account,
3149 unlocksAt: U256::from(1000u64),
3150 }))??;
3151
3152 assert!(!state.validators().contains_key(&val.account));
3153 return Ok(());
3154 },
3155 EventType::X25519KeyUpdate => {
3156 let new_x25519_key = [99u8; 32];
3157 state.apply_event(val.x25519_update(new_x25519_key))??;
3158
3159 let validator = state.validators().get(&val.account).context("validator")?;
3160 let expected = x25519::PublicKey::try_from(new_x25519_key.as_slice())
3161 .expect("valid x25519 key");
3162 assert_eq!(validator.x25519_key, Some(expected));
3163 assert!(!validator.authenticated);
3164 },
3165 EventType::P2pAddrUpdate => {
3166 state.apply_event(val.p2p_update("10.0.0.1:9000"))??;
3167
3168 let validator = state.validators().get(&val.account).context("validator")?;
3169 let expected: NetAddr = "10.0.0.1:9000".parse().expect("valid p2p addr");
3170 assert_eq!(validator.p2p_addr, Some(expected));
3171 assert!(!validator.authenticated);
3172 },
3173 }
3174
3175 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
3176 match active {
3177 Err(StakeTableError::NoValidValidators) => {},
3178 Err(e) => bail!("Unexpected error: {e}"),
3179 Ok(map) => assert!(!map.contains_key(&val.account)),
3180 }
3181 Ok(())
3182 }
3183
3184 #[test]
3185 fn test_register_v3_sets_x25519_and_p2p() -> anyhow::Result<()> {
3186 let val = TestValidator::random();
3187
3188 let mut state = StakeTableState::default();
3189 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3190
3191 let registered = state.validators().get(&val.account).unwrap();
3192 assert!(registered.authenticated);
3193
3194 let expected_x25519 =
3195 x25519::PublicKey::try_from(val.x25519_key.as_slice()).expect("valid x25519 key");
3196 assert_eq!(registered.x25519_key, Some(expected_x25519));
3197
3198 let expected_p2p: NetAddr = val.p2p_addr.parse().expect("valid p2p addr");
3199 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3200
3201 Ok(())
3202 }
3203
3204 #[test]
3205 fn test_register_v3_invalid_sig() -> anyhow::Result<()> {
3206 let val = TestValidator::random();
3207 let other = TestValidator::random();
3208
3209 let bad_val = TestValidator {
3211 bls_sig: other.bls_sig,
3212 ..val.clone()
3213 };
3214 let event = StakeTableEvent::RegisterV3((&bad_val).into());
3215
3216 let mut state = StakeTableState::default();
3217 state.apply_event(event)??;
3218
3219 let registered = state.validators().get(&val.account).unwrap();
3220 assert!(!registered.authenticated);
3221
3222 Ok(())
3223 }
3224
3225 #[test]
3226 fn test_register_v3_hostname_p2p() -> anyhow::Result<()> {
3227 let val = TestValidator::random();
3228
3229 let mut state = StakeTableState::default();
3230 let val = val.with_p2p_addr("my-host:9000");
3233 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3234
3235 let registered = state.validators().get(&val.account).unwrap();
3236 let expected_p2p: NetAddr = "my-host:9000".parse().unwrap();
3237 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3238
3239 Ok(())
3240 }
3241
3242 #[test]
3243 fn test_register_v3_invalid_p2p_addr_degrades_to_none() -> anyhow::Result<()> {
3244 let val = TestValidator::random();
3245
3246 let mut state = StakeTableState::default();
3247 let val = val.with_p2p_addr("host:notaport");
3248 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3249
3250 let registered = state.validators().get(&val.account).unwrap();
3251 assert_eq!(registered.p2p_addr, None);
3252
3253 Ok(())
3254 }
3255
3256 #[test]
3257 fn test_x25519_key_update_sets_value() -> anyhow::Result<()> {
3258 let val = TestValidator::random();
3259
3260 let mut state = StakeTableState::default();
3261 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3262
3263 assert_eq!(
3264 state.validators().get(&val.account).unwrap().x25519_key,
3265 None
3266 );
3267
3268 let x25519_key = [99u8; 32];
3269 state.apply_event(val.x25519_update(x25519_key))??;
3270
3271 let registered = state.validators().get(&val.account).unwrap();
3272 let expected_x25519 =
3273 x25519::PublicKey::try_from(x25519_key.as_slice()).expect("valid x25519 key");
3274 assert_eq!(registered.x25519_key, Some(expected_x25519));
3275
3276 Ok(())
3277 }
3278
3279 #[test]
3280 fn test_p2p_addr_update_sets_value() -> anyhow::Result<()> {
3281 let val = TestValidator::random();
3282
3283 let mut state = StakeTableState::default();
3284 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3285
3286 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
3287
3288 let p2p_addr = "10.0.0.1:8080";
3289 state.apply_event(val.p2p_update(p2p_addr))??;
3290
3291 let registered = state.validators().get(&val.account).unwrap();
3292 let expected_p2p: NetAddr = p2p_addr.parse().expect("valid p2p addr");
3293 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3294
3295 Ok(())
3296 }
3297
3298 #[test]
3299 fn test_p2p_addr_unparsable_sets_none() -> anyhow::Result<()> {
3300 let val = TestValidator::random();
3301
3302 let mut state = StakeTableState::default();
3303 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3304
3305 state.apply_event(val.p2p_update("10.0.0.1:8080"))??;
3307 assert!(
3308 state
3309 .validators()
3310 .get(&val.account)
3311 .unwrap()
3312 .p2p_addr
3313 .is_some()
3314 );
3315
3316 state.apply_event(val.p2p_update("host:notaport"))??;
3318 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
3319
3320 Ok(())
3321 }
3322
3323 #[test]
3324 fn test_x25519_key_update_unknown_validator() {
3325 let mut state = StakeTableState::default();
3326 let unknown = TestValidator::random();
3327
3328 let result = state.apply_event(unknown.x25519_update([1u8; 32]));
3329 assert_matches!(
3330 result,
3331 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
3332 );
3333 }
3334
3335 #[test]
3336 fn test_p2p_addr_update_unknown_validator() {
3337 let mut state = StakeTableState::default();
3338 let unknown = TestValidator::random();
3339
3340 let result = state.apply_event(unknown.p2p_update("127.0.0.1:9000"));
3341 assert_matches!(
3342 result,
3343 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
3344 );
3345 }
3346
3347 #[test]
3348 fn test_x25519_key_update_duplicate() -> anyhow::Result<()> {
3349 let shared_key = [55u8; 32];
3350 let val1 = TestValidator::random().with_x25519_key(shared_key);
3351 let val2 = TestValidator::random().with_x25519_key([2u8; 32]);
3352
3353 let mut state = StakeTableState::default();
3354 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3356 state.apply_event(StakeTableEvent::RegisterV3((&val2).into()))??;
3357
3358 let result = state.apply_event(val2.x25519_update(shared_key));
3360 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
3361
3362 Ok(())
3363 }
3364
3365 #[test]
3366 fn test_p2p_addr_update_hostname() -> anyhow::Result<()> {
3367 let val = TestValidator::random();
3368
3369 let mut state = StakeTableState::default();
3370 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3371
3372 state.apply_event(val.p2p_update("my-node.example.com:9000"))??;
3373
3374 let registered = state.validators().get(&val.account).unwrap();
3375 let expected_p2p: NetAddr = "my-node.example.com:9000".parse().unwrap();
3376 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3377
3378 Ok(())
3379 }
3380
3381 #[test]
3382 fn test_register_v3_duplicate_bls_key() -> anyhow::Result<()> {
3383 let val1 = TestValidator::random();
3384 let mut val2 = TestValidator::random();
3385 val2.bls_vk = val1.bls_vk;
3386
3387 let mut state = StakeTableState::default();
3388 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3389 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3390 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
3391
3392 Ok(())
3393 }
3394
3395 #[test]
3396 fn test_register_v3_duplicate_schnorr_key() -> anyhow::Result<()> {
3397 let val1 = TestValidator::random();
3398 let mut val2 = TestValidator::random();
3399 val2.schnorr_vk = val1.schnorr_vk;
3400
3401 let mut state = StakeTableState::default();
3402 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3403 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3404 assert_matches!(result, Err(StakeTableError::SchnorrKeyAlreadyUsed(_)));
3405
3406 Ok(())
3407 }
3408
3409 #[test]
3410 fn test_register_v3_duplicate_x25519_key() -> anyhow::Result<()> {
3411 let shared_x25519 = [7u8; 32];
3412 let val1 = TestValidator::random().with_x25519_key(shared_x25519);
3413 let val2 = TestValidator::random().with_x25519_key(shared_x25519);
3414
3415 let mut state = StakeTableState::default();
3416 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3417 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3418 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
3419
3420 Ok(())
3421 }
3422
3423 #[test]
3424 fn test_register_v3_rejects_exited_validator() -> anyhow::Result<()> {
3425 let val = TestValidator::random();
3426
3427 let mut state = StakeTableState::default();
3428 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3429 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3430 validator: val.account,
3431 unlocksAt: U256::from(1000u64),
3432 }))??;
3433
3434 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
3435 assert_matches!(
3436 result,
3437 Err(StakeTableError::ValidatorAlreadyExited(addr)) if addr == val.account
3438 );
3439
3440 Ok(())
3441 }
3442
3443 #[test]
3444 fn test_register_v3_zero_x25519_errors() -> anyhow::Result<()> {
3445 let val = TestValidator::random().with_x25519_key([0u8; 32]);
3446
3447 let mut state = StakeTableState::default();
3448 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
3449 assert_matches!(result, Err(StakeTableError::InvalidX25519Key(_)));
3450
3451 Ok(())
3452 }
3453
3454 #[test]
3458 fn test_state_commit_includes_used_x25519_keys() -> anyhow::Result<()> {
3459 let val = TestValidator::random();
3460
3461 let mut state_without = StakeTableState::default();
3462 state_without.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3463
3464 let mut state_with = state_without.clone();
3465 state_with
3466 .used_x25519_keys
3467 .insert(x25519::PublicKey::try_from([7u8; 32].as_slice()).unwrap());
3468
3469 assert_ne!(state_without.commit(), state_with.commit());
3470 Ok(())
3471 }
3472
3473 fn complete_mock_validator() -> RegisteredValidator<BLSPubKey> {
3477 let mut v = RegisteredValidator::<BLSPubKey>::mock();
3478 v.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
3479 v.p2p_addr = Some("127.0.0.1:9000".parse().unwrap());
3480 v
3481 }
3482
3483 #[test]
3485 fn test_select_pre_upgrade_includes_validators_missing_network_info() {
3486 let mut map = RegisteredValidatorMap::new();
3487
3488 let incomplete = RegisteredValidator::<BLSPubKey>::mock();
3489 assert!(incomplete.x25519_key.is_none());
3490 assert!(incomplete.p2p_addr.is_none());
3491 map.insert(incomplete.account, incomplete.clone());
3492
3493 let complete = complete_mock_validator();
3494 map.insert(complete.account, complete.clone());
3495
3496 let active = select_active_validator_set(&map, EPOCH_VERSION).unwrap();
3497 assert!(active.contains_key(&incomplete.account));
3498 assert!(active.contains_key(&complete.account));
3499 }
3500
3501 #[test]
3503 fn test_select_post_upgrade_excludes_missing_x25519() {
3504 let mut map = RegisteredValidatorMap::new();
3505
3506 let mut missing_x25519 = complete_mock_validator();
3507 missing_x25519.x25519_key = None;
3508 map.insert(missing_x25519.account, missing_x25519.clone());
3509
3510 let complete = complete_mock_validator();
3511 map.insert(complete.account, complete.clone());
3512
3513 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3514 assert!(!active.contains_key(&missing_x25519.account));
3515 assert!(active.contains_key(&complete.account));
3516 }
3517
3518 #[test]
3520 fn test_select_post_upgrade_excludes_missing_p2p() {
3521 let mut map = RegisteredValidatorMap::new();
3522
3523 let mut missing_p2p = complete_mock_validator();
3524 missing_p2p.p2p_addr = None;
3525 map.insert(missing_p2p.account, missing_p2p.clone());
3526
3527 let complete = complete_mock_validator();
3528 map.insert(complete.account, complete.clone());
3529
3530 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3531 assert!(!active.contains_key(&missing_p2p.account));
3532 assert!(active.contains_key(&complete.account));
3533 }
3534
3535 #[test]
3537 fn test_select_post_upgrade_keeps_complete() {
3538 let mut map = RegisteredValidatorMap::new();
3539 let complete = complete_mock_validator();
3540 map.insert(complete.account, complete.clone());
3541
3542 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3543 assert!(active.contains_key(&complete.account));
3544 }
3545
3546 #[test]
3549 fn test_select_post_upgrade_all_incomplete_fails() {
3550 let mut map = RegisteredValidatorMap::new();
3551 for _ in 0..5 {
3552 let v = RegisteredValidator::<BLSPubKey>::mock();
3553 map.insert(v.account, v);
3554 }
3555
3556 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
3557 assert_matches!(result, Err(StakeTableError::NoValidValidators));
3558 }
3559
3560 #[test]
3564 fn test_select_post_upgrade_top_n_only_complete() {
3565 let mut map = RegisteredValidatorMap::new();
3566 let mut complete_accounts = HashSet::new();
3567 let mut incomplete_accounts = HashSet::new();
3568
3569 for i in 0..(MAX_VALIDATORS + 50) {
3573 if i % 2 == 0 {
3574 let mut v = complete_mock_validator();
3575 v.stake = U256::from(1_000_000_000_u64 + i as u64);
3576 complete_accounts.insert(v.account);
3577 map.insert(v.account, v);
3578 } else {
3579 let mut v = RegisteredValidator::<BLSPubKey>::mock();
3580 v.stake = U256::from(10_u64);
3581 incomplete_accounts.insert(v.account);
3582 map.insert(v.account, v);
3583 }
3584 }
3585
3586 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3587
3588 assert!(
3589 active.len() <= MAX_VALIDATORS,
3590 "active has {} validators, expected at most {}",
3591 active.len(),
3592 MAX_VALIDATORS
3593 );
3594 for addr in active.keys() {
3595 assert!(
3596 complete_accounts.contains(addr),
3597 "incomplete validator {addr:?} ended up in active set"
3598 );
3599 assert!(!incomplete_accounts.contains(addr));
3600 }
3601 }
3602
3603 #[test]
3607 fn test_select_version_boundary() {
3608 let mut v = complete_mock_validator();
3609 v.x25519_key = None;
3610 let mut map = RegisteredValidatorMap::new();
3611 map.insert(v.account, v.clone());
3612
3613 for protocol_version in [
3614 EPOCH_VERSION,
3615 DRB_AND_HEADER_UPGRADE_VERSION,
3616 EPOCH_REWARD_VERSION,
3617 ] {
3618 let active = select_active_validator_set(&map, protocol_version).unwrap();
3619 assert!(
3620 active.contains_key(&v.account),
3621 "missing x25519 validator should be included at protocol version \
3622 {protocol_version}",
3623 );
3624 }
3625
3626 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
3627 assert_matches!(result, Err(StakeTableError::NoValidValidators));
3628 }
3629
3630 #[test]
3633 fn test_p2p_parse_fail_pre_upgrade_included_post_upgrade_excluded() -> anyhow::Result<()> {
3634 let val = TestValidator::random().with_p2p_addr("host:notaport");
3635
3636 let mut state = StakeTableState::default();
3637 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3638 state.apply_event(StakeTableEvent::Delegate(Delegated {
3640 delegator: Address::random(),
3641 validator: val.account,
3642 amount: U256::from(100),
3643 }))??;
3644
3645 let registered = state.validators().get(&val.account).unwrap();
3646 assert_eq!(registered.p2p_addr, None);
3647
3648 let pre = select_active_validator_set(state.validators(), EPOCH_VERSION).unwrap();
3650 assert!(pre.contains_key(&val.account));
3651
3652 let post = select_active_validator_set(state.validators(), NEW_PROTOCOL_VERSION);
3654 assert_matches!(post, Err(StakeTableError::NoValidValidators));
3655
3656 Ok(())
3657 }
3658}