1use std::{
2 cmp::min,
3 collections::{HashMap, HashSet},
4 str::FromStr,
5 time::{Duration, Instant},
6};
7#[cfg(feature = "node")]
8use std::{future::Future, sync::Arc};
9
10#[cfg(feature = "node")]
11use alloy::{
12 eips::{BlockId, BlockNumberOrTag},
13 primitives::utils::format_ether,
14 providers::Provider,
15 rpc::types::Filter,
16 sol_types::{SolEvent, SolEventInterface},
17};
18use alloy::{
19 primitives::{Address, U256},
20 rpc::types::Log,
21};
22#[cfg(feature = "node")]
23use anyhow::bail;
24use anyhow::{Context, ensure};
25use ark_ec::AffineRepr;
26use ark_serialize::CanonicalSerialize;
27use ark_std::One;
28#[cfg(any(feature = "node", test, feature = "testing"))]
29use async_lock::Mutex as AsyncMutex;
30#[cfg(feature = "node")]
31use async_lock::RwLock as AsyncRwLock;
32use bigdecimal::BigDecimal;
33use committable::{Commitment, Committable, RawCommitmentBuilder};
34use futures::future::BoxFuture;
35#[cfg(feature = "node")]
36use hotshot_contract_adapter::sol_types::{
37 EspToken::{self, EspTokenInstance},
38 StakeTableV3,
39};
40use hotshot_contract_adapter::{
41 sol_types::StakeTableV3::{
42 CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, P2pAddrUpdated,
43 StakeTableV3Events, Undelegated, UndelegatedV2, ValidatorExit, ValidatorExitV2,
44 ValidatorRegistered, ValidatorRegisteredV2, ValidatorRegisteredV3, X25519KeyUpdated,
45 },
46 stake_table::StakeTableSolError,
47};
48use hotshot_types::{
49 addr::NetAddr,
50 data::{EpochNumber, vid_disperse::VID_TARGET_TOTAL_STAKE},
51 signature_key::{BLSPubKey, SchnorrPubKey},
52 traits::signature_key::SignatureKey as _,
53 x25519,
54};
55use humantime::format_duration;
56use indexmap::IndexMap;
57use itertools::Itertools;
58use num_traits::{FromPrimitive, Zero};
59use thiserror::Error;
60#[cfg(feature = "node")]
61use tokio::spawn;
62use tokio::time::sleep;
63#[cfg(feature = "node")]
64use tracing::Instrument;
65use vbs::version::Version;
66
67#[cfg(feature = "node")]
68use super::L1Client;
69#[cfg(any(test, feature = "testing"))]
70use super::v0_3::DAMembers;
71use super::{
72 Header,
73 traits::StateCatchup,
74 v0_3::{
75 AuthenticatedValidator, ChainConfig, EventKey, Fetcher, MAX_VALIDATORS,
76 RegisteredValidator, StakeTableEvent,
77 },
78};
79#[cfg(feature = "node")]
80use super::{traits::MembershipPersistence, v0_3::StakeTableUpdateTask};
81#[cfg(feature = "node")]
82use crate::traits::EventsPersistenceRead;
83#[cfg(feature = "node")]
84use crate::v0_1::L1Provider;
85#[cfg(feature = "node")]
86use crate::v0_3::{BLOCKS_PER_YEAR, INFLATION_RATE};
87use crate::v0_3::{
88 COMMISSION_BASIS_POINTS, EventSortingError, ExpectedStakeTableError, FetchRewardError,
89 MILLISECONDS_PER_YEAR, RewardAmount, StakeTableError,
90};
91
92pub type RegisteredValidatorMap = IndexMap<Address, RegisteredValidator<BLSPubKey>>;
93pub type AuthenticatedValidatorMap = IndexMap<Address, AuthenticatedValidator<BLSPubKey>>;
94
95pub fn to_registered_validator_map(
96 validators: &AuthenticatedValidatorMap,
97) -> RegisteredValidatorMap {
98 validators
99 .iter()
100 .map(|(addr, v)| (*addr, v.clone().into()))
101 .collect()
102}
103
104pub type StakeTableHash = Commitment<StakeTableState>;
105
106type ApplyEventResult<T> = Result<Result<T, ExpectedStakeTableError>, StakeTableError>;
111
112#[cfg_attr(not(feature = "node"), allow(dead_code))]
114trait DisplayLog {
115 fn display(&self) -> String;
116}
117
118impl DisplayLog for Log {
119 fn display(&self) -> String {
120 let block = self.block_number.unwrap_or_default();
124 let index = self.log_index.unwrap_or_default();
125 let hash = self.transaction_hash.unwrap_or_default();
126 format!("Log(block={block},index={index},transaction_hash={hash})")
127 }
128}
129
130impl TryFrom<StakeTableV3Events> for StakeTableEvent {
131 type Error = anyhow::Error;
132
133 fn try_from(value: StakeTableV3Events) -> anyhow::Result<Self> {
134 match value {
135 StakeTableV3Events::ValidatorRegistered(v) => Ok(StakeTableEvent::Register(v)),
136 StakeTableV3Events::ValidatorRegisteredV2(v) => Ok(StakeTableEvent::RegisterV2(v)),
137 StakeTableV3Events::ValidatorRegisteredV3(v) => Ok(StakeTableEvent::RegisterV3(v)),
138 StakeTableV3Events::ValidatorExit(v) => Ok(StakeTableEvent::Deregister(v)),
139 StakeTableV3Events::ValidatorExitV2(v) => Ok(StakeTableEvent::DeregisterV2(v)),
140 StakeTableV3Events::Delegated(v) => Ok(StakeTableEvent::Delegate(v)),
141 StakeTableV3Events::Undelegated(v) => Ok(StakeTableEvent::Undelegate(v)),
142 StakeTableV3Events::UndelegatedV2(v) => Ok(StakeTableEvent::UndelegateV2(v)),
143 StakeTableV3Events::ConsensusKeysUpdated(v) => Ok(StakeTableEvent::KeyUpdate(v)),
144 StakeTableV3Events::ConsensusKeysUpdatedV2(v) => Ok(StakeTableEvent::KeyUpdateV2(v)),
145 StakeTableV3Events::CommissionUpdated(v) => Ok(StakeTableEvent::CommissionUpdate(v)),
146 StakeTableV3Events::X25519KeyUpdated(v) => Ok(StakeTableEvent::X25519KeyUpdate(v)),
147 StakeTableV3Events::P2pAddrUpdated(v) => Ok(StakeTableEvent::P2pAddrUpdate(v)),
148 StakeTableV3Events::ExitEscrowPeriodUpdated(v) => Err(anyhow::anyhow!(
149 "Unsupported StakeTableV3Events::ExitEscrowPeriodUpdated({v:?})"
150 )),
151 StakeTableV3Events::Initialized(v) => Err(anyhow::anyhow!(
152 "Unsupported StakeTableV3Events::Initialized({v:?})"
153 )),
154 StakeTableV3Events::MaxCommissionIncreaseUpdated(v) => Err(anyhow::anyhow!(
155 "Unsupported StakeTableV3Events::MaxCommissionIncreaseUpdated({v:?})"
156 )),
157 StakeTableV3Events::MinCommissionUpdateIntervalUpdated(v) => Err(anyhow::anyhow!(
158 "Unsupported StakeTableV3Events::MinCommissionUpdateIntervalUpdated({v:?})"
159 )),
160 StakeTableV3Events::OwnershipTransferred(v) => Err(anyhow::anyhow!(
161 "Unsupported StakeTableV3Events::OwnershipTransferred({v:?})"
162 )),
163 StakeTableV3Events::Paused(v) => Err(anyhow::anyhow!(
164 "Unsupported StakeTableV3Events::Paused({v:?})"
165 )),
166 StakeTableV3Events::RoleAdminChanged(v) => Err(anyhow::anyhow!(
167 "Unsupported StakeTableV3Events::RoleAdminChanged({v:?})"
168 )),
169 StakeTableV3Events::RoleGranted(v) => Err(anyhow::anyhow!(
170 "Unsupported StakeTableV3Events::RoleGranted({v:?})"
171 )),
172 StakeTableV3Events::RoleRevoked(v) => Err(anyhow::anyhow!(
173 "Unsupported StakeTableV3Events::RoleRevoked({v:?})"
174 )),
175 StakeTableV3Events::Unpaused(v) => Err(anyhow::anyhow!(
176 "Unsupported StakeTableV3Events::Unpaused({v:?})"
177 )),
178 StakeTableV3Events::Upgraded(v) => Err(anyhow::anyhow!(
179 "Unsupported StakeTableV3Events::Upgraded({v:?})"
180 )),
181 StakeTableV3Events::WithdrawalClaimed(v) => Err(anyhow::anyhow!(
182 "Unsupported StakeTableV3Events::WithdrawalClaimed({v:?})"
183 )),
184 StakeTableV3Events::ValidatorExitClaimed(v) => Err(anyhow::anyhow!(
185 "Unsupported StakeTableV3Events::ValidatorExitClaimed({v:?})"
186 )),
187 StakeTableV3Events::Withdrawal(v) => Err(anyhow::anyhow!(
188 "Unsupported StakeTableV3Events::Withdrawal({v:?})"
189 )),
190 StakeTableV3Events::MetadataUriUpdated(v) => Err(anyhow::anyhow!(
191 "Unsupported StakeTableV3Events::MetadataUriUpdated({v:?})"
192 )),
193 StakeTableV3Events::MinDelegateAmountUpdated(v) => Err(anyhow::anyhow!(
194 "Unsupported StakeTableV3Events::MinDelegateAmountUpdated({v:?})"
195 )),
196 }
197 }
198}
199
200#[cfg_attr(not(feature = "node"), allow(dead_code))]
201fn sort_stake_table_events(
202 event_logs: Vec<(StakeTableV3Events, Log)>,
203) -> Result<Vec<(EventKey, StakeTableEvent)>, EventSortingError> {
204 let mut events: Vec<(EventKey, StakeTableEvent)> = Vec::new();
205
206 let key = |log: &Log| -> Result<EventKey, EventSortingError> {
207 let block_number = log
208 .block_number
209 .ok_or(EventSortingError::MissingBlockNumber)?;
210 let log_index = log.log_index.ok_or(EventSortingError::MissingLogIndex)?;
211 Ok((block_number, log_index))
212 };
213
214 for (e, log) in event_logs {
215 let k = key(&log)?;
216 let evt: StakeTableEvent = e
217 .try_into()
218 .map_err(|_| EventSortingError::InvalidStakeTableEvent)?;
219 events.push((k, evt));
220 }
221
222 events.sort_by_key(|(key, _)| *key);
223 Ok(events)
224}
225
226#[derive(Clone, Debug, Default, PartialEq)]
227pub struct StakeTableState {
228 validators: RegisteredValidatorMap,
229 validator_exits: HashSet<Address>,
230 used_bls_keys: HashSet<BLSPubKey>,
231 used_schnorr_keys: HashSet<SchnorrPubKey>,
232 used_x25519_keys: HashSet<x25519::PublicKey>,
233}
234
235impl Committable for StakeTableState {
236 fn commit(&self) -> committable::Commitment<Self> {
237 let mut builder = RawCommitmentBuilder::new(&Self::tag());
238
239 for (_, validator) in self.validators.iter().sorted_by_key(|(a, _)| *a) {
240 builder = builder.field("validator", validator.commit());
241 }
242
243 builder = builder.constant_str("used_bls_keys");
244 for key in self.used_bls_keys.iter().sorted() {
245 builder = builder.var_size_bytes(&key.to_bytes());
246 }
247
248 builder = builder.constant_str("used_schnorr_keys");
249 for key in self
250 .used_schnorr_keys
251 .iter()
252 .sorted_by(|a, b| a.to_affine().xy().cmp(&b.to_affine().xy()))
253 {
254 let mut schnorr_key_bytes = vec![];
255 key.serialize_with_mode(&mut schnorr_key_bytes, ark_serialize::Compress::Yes)
256 .unwrap();
257 builder = builder.var_size_bytes(&schnorr_key_bytes);
258 }
259
260 if !self.used_x25519_keys.is_empty() {
265 builder = builder.constant_str("used_x25519_keys");
266 for key in self.used_x25519_keys.iter().sorted() {
267 builder = builder.var_size_bytes(key.as_slice());
268 }
269 }
270
271 builder = builder.constant_str("validator_exits");
272
273 for key in self.validator_exits.iter().sorted() {
274 builder = builder.fixed_size_bytes(&key.into_array());
275 }
276
277 builder.finalize()
278 }
279
280 fn tag() -> String {
281 "STAKE_TABLE".to_string()
282 }
283}
284
285impl StakeTableState {
286 pub fn new(
287 validators: RegisteredValidatorMap,
288 validator_exits: HashSet<Address>,
289 used_bls_keys: HashSet<BLSPubKey>,
290 used_schnorr_keys: HashSet<SchnorrPubKey>,
291 used_x25519_keys: HashSet<x25519::PublicKey>,
292 ) -> Self {
293 Self {
294 validators,
295 validator_exits,
296 used_bls_keys,
297 used_schnorr_keys,
298 used_x25519_keys,
299 }
300 }
301
302 pub fn validators(&self) -> &RegisteredValidatorMap {
303 &self.validators
304 }
305
306 pub fn into_validators(self) -> RegisteredValidatorMap {
307 self.validators
308 }
309
310 pub fn used_bls_keys(&self) -> &HashSet<BLSPubKey> {
311 &self.used_bls_keys
312 }
313
314 pub fn used_schnorr_keys(&self) -> &HashSet<SchnorrPubKey> {
315 &self.used_schnorr_keys
316 }
317
318 pub fn used_x25519_keys(&self) -> &HashSet<x25519::PublicKey> {
319 &self.used_x25519_keys
320 }
321
322 pub fn validator_exits(&self) -> &HashSet<Address> {
323 &self.validator_exits
324 }
325
326 pub fn apply_event(&mut self, event: StakeTableEvent) -> ApplyEventResult<()> {
332 match event {
333 StakeTableEvent::Register(ValidatorRegistered {
334 account,
335 blsVk,
336 schnorrVk,
337 commission,
338 }) => {
339 let stake_table_key = BLSPubKey::try_from(blsVk).ok();
340 if stake_table_key.is_none() {
341 tracing::warn!(
342 ?account,
343 "registering unauthenticated validator (invalid BLS key)"
344 );
345 }
346 let state_ver_key = SchnorrPubKey::try_from(schnorrVk).ok();
347 if state_ver_key.is_none() {
348 tracing::warn!(
349 ?account,
350 "registering unauthenticated validator (invalid Schnorr key)"
351 );
352 }
353 let authenticated = stake_table_key.is_some() && state_ver_key.is_some();
354
355 if self.validator_exits.contains(&account) {
356 return Err(StakeTableError::ValidatorAlreadyExited(account));
357 }
358
359 let entry = self.validators.entry(account);
360 if let indexmap::map::Entry::Occupied(_) = entry {
361 return Err(StakeTableError::AlreadyRegistered(account));
362 }
363
364 if let Some(k) = stake_table_key.as_ref()
368 && self.used_bls_keys.contains(k)
369 {
370 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
371 }
372
373 if let Some(k) = state_ver_key.as_ref()
376 && self.used_schnorr_keys.contains(k)
377 {
378 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
379 k.to_string(),
380 )));
381 }
382
383 if let Some(k) = stake_table_key.as_ref() {
385 self.used_bls_keys.insert(*k);
386 }
387 if let Some(k) = state_ver_key.as_ref() {
388 self.used_schnorr_keys.insert(k.clone());
389 }
390
391 entry.or_insert(RegisteredValidator {
392 account,
393 stake_table_key,
394 state_ver_key,
395 stake: U256::ZERO,
396 commission,
397 delegators: HashMap::new(),
398 authenticated,
399 x25519_key: None,
400 p2p_addr: None,
401 });
402 },
403
404 StakeTableEvent::RegisterV2(ref reg) => {
405 let (stake_table_key, state_ver_key, authenticated) = match reg.authenticate() {
406 Ok((bls, schnorr)) => (Some(bls), Some(schnorr), true),
407 Err(e) => {
408 tracing::warn!(
409 account = ?reg.account,
410 %e,
411 "registering unauthenticated validator",
412 );
413 (
414 BLSPubKey::try_from(reg.blsVK).ok(),
415 SchnorrPubKey::try_from(reg.schnorrVK).ok(),
416 false,
417 )
418 },
419 };
420
421 let ValidatorRegisteredV2 {
422 account,
423 commission,
424 ..
425 } = reg;
426
427 if self.validator_exits.contains(account) {
429 return Err(StakeTableError::ValidatorAlreadyExited(*account));
430 }
431
432 let entry = self.validators.entry(*account);
433 if let indexmap::map::Entry::Occupied(_) = entry {
434 return Err(StakeTableError::AlreadyRegistered(*account));
435 }
436
437 if let Some(k) = stake_table_key.as_ref()
439 && self.used_bls_keys.contains(k)
440 {
441 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
442 }
443
444 if let Some(k) = state_ver_key.as_ref()
446 && self.used_schnorr_keys.contains(k)
447 {
448 return Err(StakeTableError::SchnorrKeyAlreadyUsed(k.to_string()));
449 }
450
451 if let Some(k) = stake_table_key.as_ref() {
453 self.used_bls_keys.insert(*k);
454 }
455 if let Some(k) = state_ver_key.as_ref() {
456 self.used_schnorr_keys.insert(k.clone());
457 }
458
459 entry.or_insert(RegisteredValidator {
460 account: *account,
461 stake_table_key,
462 state_ver_key,
463 stake: U256::ZERO,
464 commission: *commission,
465 delegators: HashMap::new(),
466 authenticated,
467 x25519_key: None,
468 p2p_addr: None,
469 });
470 },
471
472 StakeTableEvent::Deregister(ValidatorExit { validator })
473 | StakeTableEvent::DeregisterV2(ValidatorExitV2 { validator, .. }) => {
474 if !self.validators.contains_key(&validator) {
475 return Err(StakeTableError::ValidatorNotFound(validator));
476 }
477
478 self.validator_exits.insert(validator);
480 self.validators.shift_remove(&validator);
481 },
482
483 StakeTableEvent::Delegate(delegated) => {
484 let Delegated {
485 delegator,
486 validator,
487 amount,
488 } = delegated;
489
490 if amount.is_zero() {
492 return Err(StakeTableError::ZeroDelegatorStake(delegator));
493 }
494
495 let val = self
496 .validators
497 .get_mut(&validator)
498 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
499
500 val.stake = val.stake.checked_add(amount).unwrap_or_else(|| {
503 panic!(
504 "validator stake overflow: validator={validator}, stake={}, \
505 amount={amount}",
506 val.stake
507 )
508 });
509 val.delegators
512 .entry(delegator)
513 .and_modify(|stake| {
514 *stake = stake.checked_add(amount).unwrap_or_else(|| {
515 panic!(
516 "delegator stake overflow: delegator={delegator}, stake={stake}, \
517 amount={amount}"
518 )
519 });
520 })
521 .or_insert(amount);
522 },
523
524 StakeTableEvent::Undelegate(Undelegated {
525 delegator,
526 validator,
527 amount,
528 })
529 | StakeTableEvent::UndelegateV2(UndelegatedV2 {
530 delegator,
531 validator,
532 amount,
533 ..
534 }) => {
535 let val = self
536 .validators
537 .get_mut(&validator)
538 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
539
540 if val.stake < amount {
541 tracing::warn!("validator_stake={}, amount={amount}", val.stake);
542 return Err(StakeTableError::InsufficientStake);
543 }
544
545 let delegator_stake = val
546 .delegators
547 .get_mut(&delegator)
548 .ok_or(StakeTableError::DelegatorNotFound(delegator))?;
549
550 if *delegator_stake < amount {
551 tracing::warn!("delegator_stake={delegator_stake}, amount={amount}");
552 return Err(StakeTableError::InsufficientStake);
553 }
554
555 let new_delegator_stake = delegator_stake.checked_sub(amount).unwrap();
557
558 val.stake = val.stake.checked_sub(amount).unwrap();
561
562 if new_delegator_stake.is_zero() {
563 val.delegators.remove(&delegator);
564 } else {
565 *delegator_stake = new_delegator_stake;
566 }
567 },
568
569 StakeTableEvent::KeyUpdate(update) => {
570 let ConsensusKeysUpdated {
571 account,
572 blsVK,
573 schnorrVK,
574 } = update;
575
576 let Ok(stake_table_key) = BLSPubKey::try_from(blsVK) else {
577 return Ok(Err(ExpectedStakeTableError::InvalidBlsKey));
578 };
579 let Ok(state_ver_key) = SchnorrPubKey::try_from(schnorrVK) else {
580 return Ok(Err(ExpectedStakeTableError::InvalidSchnorrKey));
581 };
582
583 if !self.validators.contains_key(&account) {
584 return Err(StakeTableError::ValidatorNotFound(account));
585 }
586
587 if self.used_bls_keys.contains(&stake_table_key) {
588 return Err(StakeTableError::BlsKeyAlreadyUsed(
589 stake_table_key.to_string(),
590 ));
591 }
592
593 if self.used_schnorr_keys.contains(&state_ver_key) {
596 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
597 state_ver_key.to_string(),
598 )));
599 }
600
601 self.used_bls_keys.insert(stake_table_key);
603 self.used_schnorr_keys.insert(state_ver_key.clone());
604 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
606 panic!("validator {account} must exist after contains_key check")
607 });
608 validator.stake_table_key = Some(stake_table_key);
609 validator.state_ver_key = Some(state_ver_key);
610 validator.authenticated = true;
612 },
613
614 StakeTableEvent::KeyUpdateV2(update) => {
615 let (stake_table_key, state_ver_key) = match update.authenticate() {
618 Ok(parsed) => parsed,
619 Err(StakeTableSolError::InvalidBlsKey) => {
620 return Ok(Err(ExpectedStakeTableError::InvalidBlsKey));
621 },
622 Err(StakeTableSolError::InvalidSchnorrKey) => {
623 return Ok(Err(ExpectedStakeTableError::InvalidSchnorrKey));
624 },
625 Err(e) => {
626 return Err(StakeTableError::AuthenticationFailed(e.to_string()));
627 },
628 };
629
630 let ConsensusKeysUpdatedV2 { account, .. } = update;
631
632 if !self.validators.contains_key(&account) {
633 return Err(StakeTableError::ValidatorNotFound(account));
634 }
635
636 if self.used_bls_keys.contains(&stake_table_key) {
638 return Err(StakeTableError::BlsKeyAlreadyUsed(
639 stake_table_key.to_string(),
640 ));
641 }
642
643 if self.used_schnorr_keys.contains(&state_ver_key) {
645 return Err(StakeTableError::SchnorrKeyAlreadyUsed(
646 state_ver_key.to_string(),
647 ));
648 }
649
650 self.used_bls_keys.insert(stake_table_key);
652 self.used_schnorr_keys.insert(state_ver_key.clone());
653
654 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
656 panic!("validator {account} must exist after contains_key check")
657 });
658 validator.stake_table_key = Some(stake_table_key);
659 validator.state_ver_key = Some(state_ver_key);
660 validator.authenticated = true;
661 },
662
663 StakeTableEvent::CommissionUpdate(CommissionUpdated {
664 validator,
665 newCommission,
666 ..
667 }) => {
668 if newCommission > COMMISSION_BASIS_POINTS {
671 return Err(StakeTableError::InvalidCommission(validator, newCommission));
672 }
673
674 let val = self
678 .validators
679 .get_mut(&validator)
680 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
681 val.commission = newCommission;
682 },
683
684 StakeTableEvent::RegisterV3(ref reg) => {
685 let (stake_table_key, state_ver_key, authenticated) = match reg.authenticate() {
686 Ok((bls, schnorr)) => (Some(bls), Some(schnorr), true),
687 Err(e) => {
688 tracing::warn!(
689 account = ?reg.account,
690 %e,
691 "registering unauthenticated validator",
692 );
693 (
694 BLSPubKey::try_from(reg.blsVK).ok(),
695 SchnorrPubKey::try_from(reg.schnorrVK).ok(),
696 false,
697 )
698 },
699 };
700
701 let ValidatorRegisteredV3 {
702 account,
703 commission,
704 x25519Key,
705 p2pAddr,
706 ..
707 } = reg;
708
709 let x25519_key = parse_x25519_key(x25519Key.0)
710 .inspect_err(|e| tracing::warn!(%e, ?account, "Invalid x25519 key"))
711 .ok();
712
713 let p2p_addr = match p2pAddr.parse::<NetAddr>() {
715 Ok(addr) => Some(addr),
716 Err(e) => {
717 if !p2pAddr.is_empty() {
718 tracing::warn!(%e, account = ?account, "Failed to parse p2p addr");
719 }
720 None
721 },
722 };
723
724 if self.validator_exits.contains(account) {
725 return Err(StakeTableError::ValidatorAlreadyExited(*account));
726 }
727
728 let entry = self.validators.entry(*account);
729 if let indexmap::map::Entry::Occupied(_) = entry {
730 return Err(StakeTableError::AlreadyRegistered(*account));
731 }
732
733 if let Some(k) = stake_table_key.as_ref()
735 && self.used_bls_keys.contains(k)
736 {
737 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
738 }
739
740 if let Some(k) = state_ver_key.as_ref()
741 && self.used_schnorr_keys.contains(k)
742 {
743 return Err(StakeTableError::SchnorrKeyAlreadyUsed(k.to_string()));
744 }
745
746 if let Some(k) = x25519_key.as_ref()
747 && self.used_x25519_keys.contains(k)
748 {
749 return Err(StakeTableError::X25519KeyAlreadyUsed(k.to_string()));
750 }
751
752 if let Some(k) = stake_table_key.as_ref() {
754 self.used_bls_keys.insert(*k);
755 }
756 if let Some(k) = state_ver_key.as_ref() {
757 self.used_schnorr_keys.insert(k.clone());
758 }
759 if let Some(k) = x25519_key.as_ref() {
760 self.used_x25519_keys.insert(*k);
761 }
762
763 entry.or_insert(RegisteredValidator {
764 account: *account,
765 stake_table_key,
766 state_ver_key,
767 stake: U256::ZERO,
768 commission: *commission,
769 delegators: HashMap::new(),
770 authenticated,
771 x25519_key,
772 p2p_addr,
773 });
774 },
775
776 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
777 validator,
778 x25519Key,
779 }) => {
780 let val = self
781 .validators
782 .get_mut(&validator)
783 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
784
785 let x25519_key = parse_x25519_key(x25519Key.0)
786 .inspect_err(|e| tracing::warn!(%e, ?validator, "Invalid x25519 key"))
787 .ok();
788
789 if let Some(key) = x25519_key {
790 if self.used_x25519_keys.contains(&key) {
791 return Err(StakeTableError::X25519KeyAlreadyUsed(key.to_string()));
792 }
793 self.used_x25519_keys.insert(key);
794 }
795 val.x25519_key = x25519_key;
796 },
797
798 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
799 validator,
800 ref p2pAddr,
801 }) => {
802 let val = self
803 .validators
804 .get_mut(&validator)
805 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
806
807 match p2pAddr.parse::<NetAddr>() {
808 Ok(addr) => val.p2p_addr = Some(addr),
809 Err(e) => {
810 tracing::warn!(%e, validator = ?validator, "Failed to parse p2p addr");
811 val.p2p_addr = None;
812 },
813 }
814 },
815 }
816
817 Ok(Ok(()))
818 }
819}
820
821fn parse_x25519_key(bytes: [u8; 32]) -> anyhow::Result<x25519::PublicKey> {
827 ensure!(bytes != [0u8; 32], "zero key");
828 Ok(x25519::PublicKey::try_from(bytes.as_slice())?)
829}
830
831pub fn validators_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
832 events: I,
833) -> Result<(RegisteredValidatorMap, StakeTableHash), StakeTableError> {
834 let mut state = StakeTableState::default();
835 for event in events {
836 match state.apply_event(event.clone()) {
837 Ok(Ok(())) => {
838 },
840 Ok(Err(expected_err)) => {
841 tracing::warn!("Expected error while applying event {event:?}: {expected_err}");
843 },
844 Err(err) => {
845 tracing::error!("Fatal error in applying event {event:?}: {err}");
846 return Err(err);
847 },
848 }
849 }
850 let commit = state.commit();
851 Ok((state.into_validators(), commit))
852}
853
854pub(crate) fn select_active_validator_set(
860 candidates: &RegisteredValidatorMap,
861 protocol_version: Version,
862) -> Result<AuthenticatedValidatorMap, StakeTableError> {
863 let total_candidates = candidates.len();
864
865 let valid_validators: AuthenticatedValidatorMap = candidates
866 .iter()
867 .filter_map(
868 |(address, validator)| match AuthenticatedValidator::try_from(validator) {
869 Err(e) => {
870 tracing::debug!("{e}");
871 None
872 },
873 Ok(cv) => {
874 if cv.delegators.is_empty() {
875 tracing::debug!("Validator {address:?} does not have any delegator");
876 return None;
877 }
878 if cv.stake.is_zero() {
879 tracing::debug!("Validator {address:?} does not have any stake");
880 return None;
881 }
882 if !cv.is_eligible(protocol_version) {
883 tracing::debug!(
884 ?address,
885 has_x25519 = cv.x25519_key.is_some(),
886 has_p2p = cv.p2p_addr.is_some(),
887 "Validator not eligible at protocol version {protocol_version}"
888 );
889 return None;
890 }
891 Some((*address, cv))
892 },
893 },
894 )
895 .collect();
896
897 tracing::debug!(
898 total_candidates,
899 filtered = valid_validators.len(),
900 "Filtered out invalid validators"
901 );
902
903 if valid_validators.is_empty() {
904 tracing::warn!("Validator selection failed: no validators passed minimum criteria");
905 return Err(StakeTableError::NoValidValidators);
906 }
907
908 let maximum_stake = valid_validators.values().map(|v| v.stake).max().unwrap();
909
910 let minimum_stake = maximum_stake
911 .checked_div(U256::from(VID_TARGET_TOTAL_STAKE))
912 .ok_or_else(|| {
913 tracing::error!("Overflow while calculating minimum stake threshold");
914 StakeTableError::MinimumStakeOverflow
915 })?;
916
917 let mut valid_stakers: Vec<_> = valid_validators
918 .iter()
919 .filter(|(_, v)| v.stake >= minimum_stake)
920 .map(|(addr, v)| (*addr, v.stake))
921 .collect();
922
923 tracing::info!(
924 count = valid_stakers.len(),
925 "Number of validators above minimum stake threshold"
926 );
927
928 valid_stakers.sort_by_key(|(_, stake)| std::cmp::Reverse(*stake));
930
931 if valid_stakers.len() > MAX_VALIDATORS {
932 valid_stakers.truncate(MAX_VALIDATORS);
933 }
934
935 let selected_addresses: HashSet<_> = valid_stakers.iter().map(|(addr, _)| *addr).collect();
936 let selected_validators: AuthenticatedValidatorMap = valid_validators
937 .into_iter()
938 .filter(|(address, _)| selected_addresses.contains(address))
939 .collect();
940
941 tracing::info!(
942 final_count = selected_validators.len(),
943 "Selected active validator set"
944 );
945
946 Ok(selected_validators)
947}
948
949#[derive(Clone, Debug)]
950pub struct ValidatorSet {
951 pub(crate) all_validators: RegisteredValidatorMap,
952 pub(crate) active_validators: AuthenticatedValidatorMap,
953 pub(crate) stake_table_hash: Option<StakeTableHash>,
954 pub(crate) protocol_version: Version,
956}
957
958impl ValidatorSet {
959 pub fn from_state(
961 state: &StakeTableState,
962 protocol_version: Version,
963 ) -> Result<Self, StakeTableError> {
964 let active_validators = select_active_validator_set(state.validators(), protocol_version)?;
965 Ok(Self {
966 all_validators: state.validators().clone(),
967 active_validators,
968 stake_table_hash: Some(state.commit()),
969 protocol_version,
970 })
971 }
972
973 pub fn from_l1_events<I: Iterator<Item = StakeTableEvent>>(
975 events: I,
976 protocol_version: Version,
977 ) -> Result<Self, StakeTableError> {
978 let (all_validators, stake_table_hash) = validators_from_l1_events(events)?;
979 let active_validators = select_active_validator_set(&all_validators, protocol_version)?;
980 Ok(Self {
981 all_validators,
982 active_validators,
983 stake_table_hash: Some(stake_table_hash),
984 protocol_version,
985 })
986 }
987
988 pub fn all_validators(&self) -> &RegisteredValidatorMap {
990 &self.all_validators
991 }
992
993 pub fn active_validators(&self) -> &AuthenticatedValidatorMap {
995 &self.active_validators
996 }
997
998 pub fn stake_table_hash(&self) -> Option<StakeTableHash> {
1000 self.stake_table_hash
1001 }
1002
1003 pub fn protocol_version(&self) -> Version {
1005 self.protocol_version
1006 }
1007}
1008
1009impl std::fmt::Debug for StakeTableEvent {
1010 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1011 match self {
1012 StakeTableEvent::Register(event) => write!(f, "Register({:?})", event.account),
1013 StakeTableEvent::RegisterV2(event) => write!(f, "RegisterV2({:?})", event.account),
1014 StakeTableEvent::Deregister(event) => write!(f, "Deregister({:?})", event.validator),
1015 StakeTableEvent::DeregisterV2(event) => {
1016 write!(f, "DeregisterV2({:?})", event.validator)
1017 },
1018 StakeTableEvent::Delegate(event) => write!(f, "Delegate({:?})", event.delegator),
1019 StakeTableEvent::Undelegate(event) => write!(f, "Undelegate({:?})", event.delegator),
1020 StakeTableEvent::UndelegateV2(event) => {
1021 write!(f, "UndelegateV2({:?})", event.delegator)
1022 },
1023 StakeTableEvent::KeyUpdate(event) => write!(f, "KeyUpdate({:?})", event.account),
1024 StakeTableEvent::KeyUpdateV2(event) => write!(f, "KeyUpdateV2({:?})", event.account),
1025 StakeTableEvent::CommissionUpdate(event) => {
1026 write!(f, "CommissionUpdate({:?})", event.validator)
1027 },
1028 StakeTableEvent::RegisterV3(event) => {
1029 write!(f, "RegisterV3({:?})", event.account)
1030 },
1031 StakeTableEvent::X25519KeyUpdate(event) => {
1032 write!(f, "X25519KeyUpdate({:?})", event.validator)
1033 },
1034 StakeTableEvent::P2pAddrUpdate(event) => {
1035 write!(f, "P2pAddrUpdate({:?})", event.validator)
1036 },
1037 }
1038 }
1039}
1040
1041impl Fetcher {
1042 #[cfg(feature = "node")]
1043 pub fn new(
1044 peers: Arc<dyn StateCatchup>,
1045 persistence: Arc<AsyncMutex<dyn MembershipPersistence>>,
1046 l1_client: L1Client,
1047 chain_config: ChainConfig,
1048 ) -> Self {
1049 Self {
1050 peers,
1051 persistence,
1052 l1_client,
1053 chain_config: Arc::new(AsyncMutex::new(chain_config)),
1054 update_task: StakeTableUpdateTask(AsyncMutex::new(None)).into(),
1055 initial_supply: Arc::new(AsyncRwLock::new(None)),
1056 }
1057 }
1058
1059 #[cfg(feature = "node")]
1060 pub async fn spawn_update_loop(&self) {
1061 let mut update_task = self.update_task.0.lock().await;
1062 if update_task.is_none() {
1063 *update_task = Some(spawn(self.update_loop()));
1064 }
1065 }
1066
1067 #[cfg(feature = "node")]
1072 fn update_loop(&self) -> impl Future<Output = ()> + use<> {
1073 let span = tracing::warn_span!("Stake table update loop");
1074 let self_clone = self.clone();
1075 let state = self.l1_client.state.clone();
1076 let l1_retry = self.l1_client.options().l1_retry_delay;
1077 let update_delay = self.l1_client.options().stake_table_update_interval;
1078 let chain_config = self.chain_config.clone();
1079
1080 async move {
1081 let stake_contract_address = loop {
1086 let contract = chain_config.lock().await.stake_table_contract;
1087 match contract {
1088 Some(addr) => break addr,
1089 None => {
1090 tracing::debug!(
1091 "Stake table contract address not found. Retrying in {l1_retry:?}...",
1092 );
1093 },
1094 }
1095 sleep(l1_retry).await;
1096 };
1097
1098 loop {
1100 let finalized_block = loop {
1101 let last_finalized = state.lock().await.last_finalized;
1102 if let Some(block) = last_finalized {
1103 break block;
1104 }
1105 tracing::debug!("Finalized block not yet available. Retrying in {l1_retry:?}",);
1106 sleep(l1_retry).await;
1107 };
1108
1109 tracing::debug!("Attempting to fetch stake table at L1 block {finalized_block:?}",);
1110
1111 loop {
1112 match self_clone
1113 .fetch_and_store_stake_table_events(stake_contract_address, finalized_block)
1114 .await
1115 {
1116 Ok(events) => {
1117 tracing::info!(
1118 "Successfully fetched and stored stake table events at \
1119 block={finalized_block:?}"
1120 );
1121 tracing::debug!("events={events:?}");
1122 break;
1123 },
1124 Err(e) => {
1125 tracing::error!(
1126 "Error fetching stake table at block {finalized_block:?}. err= \
1127 {e:#}",
1128 );
1129 sleep(l1_retry).await;
1130 },
1131 }
1132 }
1133
1134 tracing::debug!("Waiting {update_delay:?} before next stake table update...",);
1135 sleep(update_delay).await;
1136 }
1137 }
1138 .instrument(span)
1139 }
1140
1141 #[cfg(feature = "node")]
1148 pub async fn fetch_and_store_stake_table_events(
1149 &self,
1150 contract: Address,
1151 to_block: u64,
1152 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
1153 let (read_l1_offset, persistence_events) = {
1154 let persistence_lock = self.persistence.lock().await;
1155 persistence_lock.load_events(0, to_block).await?
1156 };
1157
1158 tracing::info!("loaded events from storage to_block={to_block:?}");
1159
1160 if let Some(EventsPersistenceRead::Complete) = read_l1_offset {
1163 return Ok(persistence_events);
1164 }
1165
1166 let from_block = read_l1_offset
1167 .map(|read| match read {
1168 EventsPersistenceRead::UntilL1Block(block) => Ok(block + 1),
1169 EventsPersistenceRead::Complete => Err(anyhow::anyhow!(
1170 "Unexpected state. offset is complete after returning early"
1171 )),
1172 })
1173 .transpose()?;
1174
1175 ensure!(
1176 Some(to_block) >= from_block,
1177 "to_block {to_block:?} is less than from_block {from_block:?}"
1178 );
1179
1180 tracing::info!(%to_block, from_block = ?from_block, "Fetching events from contract");
1181
1182 let contract_events = Self::fetch_events_from_contract(
1183 self.l1_client.clone(),
1184 contract,
1185 from_block,
1186 to_block,
1187 )
1188 .await?;
1189
1190 tracing::info!(
1192 "storing {} new events in storage to_block={to_block:?}",
1193 contract_events.len()
1194 );
1195 {
1196 let persistence_lock = self.persistence.lock().await;
1197 persistence_lock
1198 .store_events(to_block, contract_events.clone())
1199 .await
1200 .inspect_err(|e| tracing::error!("failed to store events. err={e}"))?;
1201 }
1202
1203 let mut events = match from_block {
1204 Some(_) => persistence_events
1205 .into_iter()
1206 .chain(contract_events)
1207 .collect(),
1208 None => contract_events,
1209 };
1210
1211 let len_before_dedup = events.len();
1216 events.dedup();
1217 let len_after_dedup = events.len();
1218 if len_before_dedup != len_after_dedup {
1219 tracing::warn!("Duplicate events found and removed. This should not normally happen.")
1220 }
1221
1222 Ok(events)
1223 }
1224
1225 #[cfg_attr(not(feature = "node"), allow(dead_code))]
1232 fn validate_event(event: &StakeTableV3Events, log: &Log) -> Result<bool, StakeTableError> {
1233 match event {
1234 StakeTableV3Events::ConsensusKeysUpdatedV2(evt) => {
1235 if let Err(err) = evt.authenticate() {
1236 tracing::warn!(
1237 %err,
1238 "Failed to authenticate ConsensusKeysUpdatedV2 event: {}",
1239 log.display()
1240 );
1241 return Ok(false);
1242 }
1243 },
1244 StakeTableV3Events::CommissionUpdated(evt)
1245 if evt.newCommission > COMMISSION_BASIS_POINTS =>
1246 {
1247 return Err(StakeTableError::InvalidCommission(
1248 evt.validator,
1249 evt.newCommission,
1250 ));
1251 },
1252 _ => {},
1253 }
1254
1255 Ok(true)
1256 }
1257
1258 #[cfg_attr(not(feature = "node"), allow(dead_code))]
1260 fn block_range_chunks(
1261 from_block: u64,
1262 to_block: u64,
1263 chunk_size: u64,
1264 ) -> impl Iterator<Item = (u64, u64)> {
1265 let mut start = from_block;
1266 let end = to_block;
1267 std::iter::from_fn(move || {
1268 let chunk_end = min(start + chunk_size - 1, end);
1269 if chunk_end < start {
1270 return None;
1271 }
1272 let chunk = (start, chunk_end);
1273 start = chunk_end + 1;
1274 Some(chunk)
1275 })
1276 }
1277
1278 #[cfg(feature = "node")]
1280 pub async fn fetch_events_from_contract(
1281 l1_client: L1Client,
1282 contract: Address,
1283 from_block: Option<u64>,
1284 to_block: u64,
1285 ) -> Result<Vec<(EventKey, StakeTableEvent)>, StakeTableError> {
1286 let stake_table_contract = StakeTableV3::new(contract, l1_client.provider.clone());
1287 let max_retry_duration = l1_client.options().l1_events_max_retry_duration;
1288 let retry_delay = l1_client.options().l1_retry_delay;
1289 let from_block = match from_block {
1292 Some(block) => block,
1293 None => {
1294 let start = Instant::now();
1295 loop {
1296 match stake_table_contract.initializedAtBlock().call().await {
1297 Ok(init_block) => break init_block.to::<u64>(),
1298 Err(err) => {
1299 if start.elapsed() >= max_retry_duration {
1300 panic!(
1301 "Failed to retrieve initial block after `{}`: {err}",
1302 format_duration(max_retry_duration)
1303 );
1304 }
1305 tracing::warn!(%err, "Failed to retrieve initial block, retrying...");
1306 sleep(retry_delay).await;
1307 },
1308 }
1309 }
1310 },
1311 };
1312
1313 let chunk_size = l1_client.options().l1_events_max_block_range;
1317 let chunks = Self::block_range_chunks(from_block, to_block, chunk_size);
1318
1319 let mut events = vec![];
1320
1321 for (from, to) in chunks {
1322 let provider = l1_client.provider.clone();
1323
1324 tracing::debug!(from, to, "fetch all stake table events in range");
1325 let logs: Vec<Log> = retry(
1328 retry_delay,
1329 max_retry_duration,
1330 "stake table events fetch",
1331 move || {
1332 let provider = provider.clone();
1333
1334 Box::pin(async move {
1335 let filter = Filter::new()
1336 .events([
1337 ValidatorRegistered::SIGNATURE,
1338 ValidatorRegisteredV2::SIGNATURE,
1339 ValidatorRegisteredV3::SIGNATURE,
1340 ValidatorExit::SIGNATURE,
1341 ValidatorExitV2::SIGNATURE,
1342 Delegated::SIGNATURE,
1343 Undelegated::SIGNATURE,
1344 UndelegatedV2::SIGNATURE,
1345 ConsensusKeysUpdated::SIGNATURE,
1346 ConsensusKeysUpdatedV2::SIGNATURE,
1347 CommissionUpdated::SIGNATURE,
1348 X25519KeyUpdated::SIGNATURE,
1349 P2pAddrUpdated::SIGNATURE,
1350 ])
1351 .address(contract)
1352 .from_block(from)
1353 .to_block(to);
1354 provider.get_logs(&filter).await
1355 })
1356 },
1357 )
1358 .await;
1359
1360 let chunk_events = logs
1361 .into_iter()
1362 .filter_map(|log| {
1363 let event =
1364 StakeTableV3Events::decode_raw_log(log.topics(), &log.data().data).ok()?;
1365 match Self::validate_event(&event, &log) {
1366 Ok(true) => Some(Ok((event, log))),
1367 Ok(false) => None,
1368 Err(e) => Some(Err(e)),
1369 }
1370 })
1371 .collect::<Result<Vec<_>, _>>()?;
1372
1373 events.extend(chunk_events);
1374 }
1375
1376 sort_stake_table_events(events).map_err(Into::into)
1377 }
1378
1379 #[cfg(feature = "node")]
1381 pub async fn fetch_all_validators_from_contract(
1382 l1_client: L1Client,
1383 contract: Address,
1384 to_block: u64,
1385 ) -> anyhow::Result<(RegisteredValidatorMap, StakeTableHash)> {
1386 let events = Self::fetch_events_from_contract(l1_client, contract, None, to_block).await?;
1387
1388 validators_from_l1_events(events.into_iter().map(|(_, e)| e))
1390 .context("failed to construct validators set from l1 events")
1391 }
1392
1393 #[cfg(feature = "node")]
1395 pub async fn initial_supply_or_fetch(&self) -> Result<U256, FetchRewardError> {
1396 let supply = *self.initial_supply.read().await;
1398 match supply {
1399 Some(supply) => Ok(supply),
1400 None => self.fetch_and_update_initial_supply().await,
1401 }
1402 }
1403
1404 #[cfg(not(feature = "node"))]
1406 pub async fn initial_supply_or_fetch(&self) -> Result<U256, FetchRewardError> {
1407 Ok((*self.initial_supply.read().await).expect("initial supply pre-populated"))
1408 }
1409
1410 #[cfg(feature = "node")]
1413 pub async fn fetch_fixed_block_reward(&self) -> Result<RewardAmount, FetchRewardError> {
1414 let initial_supply = self.initial_supply_or_fetch().await?;
1415
1416 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
1417 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
1418 .ok_or(FetchRewardError::DivisionByZero(
1419 "COMMISSION_BASIS_POINTS is zero",
1420 ))?;
1421
1422 Ok(RewardAmount(reward))
1423 }
1424
1425 #[cfg(feature = "node")]
1440 pub async fn fetch_and_update_initial_supply(&self) -> Result<U256, FetchRewardError> {
1441 tracing::info!("Fetching token initial supply");
1442 let chain_config = *self.chain_config.lock().await;
1443
1444 let stake_table_contract = chain_config
1445 .stake_table_contract
1446 .ok_or(FetchRewardError::MissingStakeTableContract)?;
1447
1448 let provider = self.l1_client.provider.clone();
1449 let stake_table = StakeTableV3::new(stake_table_contract, provider.clone());
1450
1451 let stake_table_init_block = stake_table
1455 .initializedAtBlock()
1456 .block(BlockId::finalized())
1457 .call()
1458 .await
1459 .map_err(FetchRewardError::ContractCall)?
1460 .to::<u64>();
1461
1462 tracing::info!("stake table init block ={stake_table_init_block}");
1463
1464 let token_address = stake_table
1465 .token()
1466 .block(BlockId::finalized())
1467 .call()
1468 .await
1469 .map_err(FetchRewardError::TokenAddressFetch)?;
1470
1471 let token = EspToken::new(token_address, provider.clone());
1472
1473 let init_logs = token
1476 .Initialized_filter()
1477 .from_block(0u64)
1478 .to_block(BlockNumberOrTag::Finalized)
1479 .query()
1480 .await;
1481
1482 let init_log = match init_logs {
1483 Ok(init_logs) => {
1484 if init_logs.is_empty() {
1485 tracing::error!(
1486 "Token Initialized event logs are empty. This should never happen"
1487 );
1488 return Err(FetchRewardError::MissingInitializedEvent);
1489 }
1490
1491 let (_, init_log) = init_logs[0].clone();
1492
1493 tracing::debug!(tx_hash = ?init_log.transaction_hash, "Found token `Initialized` event");
1494 init_log
1495 },
1496 Err(err) => {
1497 tracing::warn!(
1498 "RPC returned error {err:?}. will fallback to scanning over fixed block range"
1499 );
1500 self.scan_token_contract_initialized_event_log(
1501 stake_table_init_block,
1502 token.clone(),
1503 )
1504 .await?
1505 },
1506 };
1507
1508 let init_block = init_log
1509 .block_number
1510 .ok_or(FetchRewardError::MissingBlockNumber)?;
1511
1512 let init_tx_hash =
1513 init_log
1514 .transaction_hash
1515 .ok_or_else(|| FetchRewardError::MissingTransactionHash {
1516 init_log: init_log.clone().into(),
1517 })?;
1518
1519 let transfer_logs = token
1523 .Transfer_filter()
1524 .from_block(init_block)
1525 .to_block(init_block)
1526 .query()
1527 .await
1528 .map_err(FetchRewardError::TransferEventQuery)?;
1529
1530 let (mint_transfer, _) = transfer_logs
1531 .iter()
1532 .find(|(transfer, log)| {
1533 log.transaction_hash == Some(init_tx_hash) && transfer.from == Address::ZERO
1534 })
1535 .ok_or(FetchRewardError::MissingTransferEvent)?;
1536
1537 tracing::debug!("mint transfer event ={mint_transfer:?}");
1538
1539 let initial_supply = mint_transfer.value;
1540
1541 tracing::info!("Initial token amount: {} ESP", format_ether(initial_supply));
1542
1543 let mut writer = self.initial_supply.write().await;
1544 *writer = Some(initial_supply);
1545
1546 Ok(initial_supply)
1547 }
1548
1549 #[cfg(feature = "node")]
1557 pub async fn scan_token_contract_initialized_event_log(
1558 &self,
1559 stake_table_init_block: u64,
1560 token: EspTokenInstance<L1Provider>,
1561 ) -> Result<Log, FetchRewardError> {
1562 let max_events_range = self.l1_client.options().l1_events_max_block_range;
1563 const MAX_BLOCKS_SCANNED: u64 = 200_000;
1564 let mut total_scanned = 0;
1565
1566 let mut from_block = stake_table_init_block.saturating_sub(max_events_range);
1567 let mut to_block = stake_table_init_block;
1568
1569 loop {
1570 if total_scanned >= MAX_BLOCKS_SCANNED {
1571 tracing::error!(
1572 total_scanned,
1573 "Exceeded maximum scan range while searching for token Initialized event"
1574 );
1575 return Err(FetchRewardError::ExceededMaxScanRange(MAX_BLOCKS_SCANNED));
1576 }
1577
1578 let init_logs = token
1579 .Initialized_filter()
1580 .from_block(from_block)
1581 .to_block(to_block)
1582 .query()
1583 .await
1584 .map_err(FetchRewardError::ScanQueryFailed)?;
1585
1586 if !init_logs.is_empty() {
1587 let (_, init_log) = init_logs[0].clone();
1588 tracing::info!(
1589 from_block,
1590 tx_hash = ?init_log.transaction_hash,
1591 "Found token Initialized event during scan"
1592 );
1593 return Ok(init_log);
1594 }
1595
1596 total_scanned += max_events_range;
1597 from_block = from_block.saturating_sub(max_events_range);
1598 to_block = to_block.saturating_sub(max_events_range);
1599 }
1600 }
1601
1602 pub async fn update_chain_config(&self, header: &Header) -> anyhow::Result<()> {
1603 let chain_config = self.get_chain_config(header).await?;
1604 *self.chain_config.lock().await = chain_config;
1606
1607 Ok(())
1608 }
1609
1610 #[cfg(feature = "node")]
1611 pub async fn fetch(&self, epoch: EpochNumber, header: &Header) -> anyhow::Result<ValidatorSet> {
1612 let chain_config = *self.chain_config.lock().await;
1613 let Some(address) = chain_config.stake_table_contract else {
1614 bail!("No stake table contract address found in Chain config");
1615 };
1616
1617 let Some(l1_finalized_block_info) = header.l1_finalized() else {
1618 bail!(
1619 "The epoch root for epoch {epoch} is missing the L1 finalized block info. This is \
1620 a fatal error. Consensus is blocked and will not recover."
1621 );
1622 };
1623
1624 let events = match self
1625 .fetch_and_store_stake_table_events(address, l1_finalized_block_info.number())
1626 .await
1627 .map_err(GetStakeTablesError::L1ClientFetchError)
1628 {
1629 Ok(events) => events,
1630 Err(e) => {
1631 bail!("failed to fetch stake table events {e:?}");
1632 },
1633 };
1634
1635 match ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), header.version()) {
1640 Ok(res) => Ok(res),
1641 Err(e) => {
1642 bail!("failed to construct stake table {e:?}");
1643 },
1644 }
1645 }
1646
1647 pub(crate) async fn get_chain_config(&self, header: &Header) -> anyhow::Result<ChainConfig> {
1650 let chain_config = self.chain_config.lock().await;
1651 let peers = self.peers.clone();
1652 let header_cf = header.chain_config();
1653 if chain_config.commit() == header_cf.commit() {
1654 return Ok(*chain_config);
1655 }
1656
1657 let cf = match header_cf.resolve() {
1658 Some(cf) => cf,
1659 None => peers
1660 .fetch_chain_config(header_cf.commit())
1661 .await
1662 .inspect_err(|err| {
1663 tracing::error!("failed to get chain_config from peers. err: {err:?}");
1664 })?,
1665 };
1666
1667 Ok(cf)
1668 }
1669
1670 #[cfg(any(test, feature = "testing"))]
1671 pub fn mock() -> Self {
1672 use crate::{mock, v0_1::NoStorage};
1673 let chain_config = ChainConfig::default();
1674 let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
1675 .expect("Failed to create L1 client");
1676
1677 let peers = Arc::new(mock::MockStateCatchup::default());
1678 let persistence = NoStorage;
1679
1680 Self::new(
1681 peers,
1682 Arc::new(AsyncMutex::new(persistence)),
1683 l1,
1684 chain_config,
1685 )
1686 }
1687}
1688
1689#[cfg_attr(not(feature = "node"), allow(dead_code))]
1690async fn retry<F, T, E>(
1691 retry_delay: Duration,
1692 max_duration: Duration,
1693 operation_name: &str,
1694 mut operation: F,
1695) -> T
1696where
1697 F: FnMut() -> BoxFuture<'static, Result<T, E>>,
1698 E: std::fmt::Display,
1699{
1700 let start = Instant::now();
1701 loop {
1702 match operation().await {
1703 Ok(result) => return result,
1704 Err(err) => {
1705 if start.elapsed() >= max_duration {
1706 panic!(
1707 r#"
1708 Failed to complete operation `{operation_name}` after `{}`.
1709 error: {err}
1710
1711
1712 This might be caused by:
1713 - The current block range being too large for your RPC provider.
1714 - The event query returning more data than your RPC allows as
1715 some RPC providers limit the number of events returned.
1716 - RPC provider outage
1717
1718 Suggested solution:
1719 - Reduce the value of the environment variable
1720 `ESPRESSO_L1_EVENTS_MAX_BLOCK_RANGE` to query smaller ranges.
1721 - Add multiple RPC providers
1722 - Use a different RPC provider with higher rate limits."#,
1723 format_duration(max_duration)
1724 );
1725 }
1726 tracing::warn!(%err, "Retrying `{operation_name}` after error");
1727 sleep(retry_delay).await;
1728 },
1729 }
1730 }
1731}
1732
1733pub fn calculate_proportion_staked_and_reward_rate(
1743 total_stake: &BigDecimal,
1744 total_supply: &BigDecimal,
1745) -> anyhow::Result<(BigDecimal, BigDecimal)> {
1746 if total_supply.is_zero() {
1747 return Err(anyhow::anyhow!("Total supply cannot be zero"));
1748 }
1749
1750 let proportion_staked = total_stake / total_supply;
1751
1752 if proportion_staked < BigDecimal::zero() || proportion_staked > BigDecimal::one() {
1753 return Err(anyhow::anyhow!("Stake ratio p must be in the range [0, 1]"));
1754 }
1755
1756 let two = BigDecimal::from_u32(2).unwrap();
1757 let min_stake_ratio = BigDecimal::from_str("0.01")?;
1758 let numerator = BigDecimal::from_str("0.03")?;
1759
1760 let denominator = (&two * (&proportion_staked).max(&min_stake_ratio))
1761 .sqrt()
1762 .context("Failed to compute sqrt in R(p)")?;
1763
1764 let reward_rate = numerator / denominator;
1765
1766 tracing::debug!("rp={reward_rate}");
1767
1768 Ok((proportion_staked, reward_rate))
1769}
1770
1771pub(crate) fn compute_block_reward(
1772 epoch: EpochNumber,
1773 total_supply: U256,
1774 total_stake: U256,
1775 avg_block_time_ms: u64,
1776) -> anyhow::Result<RewardAmount> {
1777 let total_stake_bd = BigDecimal::from_str(&total_stake.to_string())?;
1779 let total_supply_bd = BigDecimal::from_str(&(total_supply.to_string()))?;
1780
1781 tracing::debug!(?epoch, "total_stake={total_stake}");
1782 tracing::debug!(?epoch, "total_supply_bd={total_supply_bd}");
1783
1784 let (proportion, reward_rate) =
1785 calculate_proportion_staked_and_reward_rate(&total_stake_bd, &total_supply_bd)?;
1786 let inflation_rate = proportion * reward_rate;
1787
1788 tracing::debug!(?epoch, "inflation_rate={inflation_rate:?}");
1789
1790 let blocks_per_year = MILLISECONDS_PER_YEAR
1791 .checked_div(avg_block_time_ms.into())
1792 .context("avg_block_time_ms is zero")?;
1793
1794 tracing::debug!(?epoch, "blocks_per_year={blocks_per_year:?}");
1795
1796 ensure!(!blocks_per_year.is_zero(), "blocks per year is zero");
1797 let block_reward = (total_supply_bd * inflation_rate) / blocks_per_year;
1798
1799 let block_reward_u256 = U256::from_str(&block_reward.round(0).to_string())?;
1800
1801 Ok(block_reward_u256.into())
1802}
1803
1804#[derive(Error, Debug)]
1805#[cfg_attr(not(feature = "node"), allow(dead_code))]
1807enum GetStakeTablesError {
1808 #[error("Error fetching from L1: {0}")]
1809 L1ClientFetchError(anyhow::Error),
1810}
1811
1812#[cfg(any(test, feature = "testing"))]
1813impl super::v0_3::StakeTable {
1814 pub fn mock(n: u64) -> Self {
1816 use hotshot_types::PeerConfig;
1817
1818 use super::SeqTypes;
1819
1820 [..n]
1821 .iter()
1822 .map(|_| PeerConfig::test_default())
1823 .collect::<Vec<PeerConfig<SeqTypes>>>()
1824 .into()
1825 }
1826}
1827
1828#[cfg(any(test, feature = "testing"))]
1829impl DAMembers {
1830 pub fn mock(n: u64) -> Self {
1832 use hotshot_types::PeerConfig;
1833
1834 use super::SeqTypes;
1835
1836 [..n]
1837 .iter()
1838 .map(|_| PeerConfig::test_default())
1839 .collect::<Vec<PeerConfig<SeqTypes>>>()
1840 .into()
1841 }
1842}
1843
1844#[cfg(any(test, feature = "testing"))]
1845pub mod testing {
1846 use alloy::primitives::Bytes;
1847 use hotshot_contract_adapter::{
1848 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
1849 stake_table::{StateSignatureSol, sign_address_bls, sign_address_schnorr},
1850 };
1851 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
1852 use rand::{Rng as _, RngCore as _};
1853
1854 use super::*;
1855
1856 #[derive(Debug, Clone)]
1859 pub struct TestValidator {
1860 pub account: Address,
1861 pub bls_vk: G2PointSol,
1862 pub schnorr_vk: EdOnBN254PointSol,
1863 pub commission: u16,
1864 pub bls_sig: G1PointSol,
1865 pub schnorr_sig: Bytes,
1866 pub x25519_key: [u8; 32],
1867 pub p2p_addr: String,
1868 }
1869
1870 impl TestValidator {
1871 pub fn random() -> Self {
1872 let account = Address::random();
1873 let commission = rand::thread_rng().gen_range(0..10000);
1874 Self::random_update_keys(account, commission)
1875 }
1876
1877 pub fn randomize_keys(&self) -> Self {
1878 Self::random_update_keys(self.account, self.commission)
1879 }
1880
1881 pub fn random_update_keys(account: Address, commission: u16) -> Self {
1882 let mut rng = &mut rand::thread_rng();
1883 let mut seed = [0u8; 32];
1884 rng.fill_bytes(&mut seed);
1885 let bls_key_pair = BLSKeyPair::generate(&mut rng);
1886 let bls_sig = sign_address_bls(&bls_key_pair, account);
1887 let schnorr_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
1888 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, account);
1889 let mut x25519_key = [0u8; 32];
1890 rng.fill_bytes(&mut x25519_key);
1891 Self {
1892 account,
1893 bls_vk: bls_key_pair.ver_key().to_affine().into(),
1894 schnorr_vk: schnorr_key_pair.ver_key().to_affine().into(),
1895 commission,
1896 bls_sig: bls_sig.into(),
1897 schnorr_sig: StateSignatureSol::from(schnorr_sig).into(),
1898 x25519_key,
1899 p2p_addr: "127.0.0.1:9000".to_string(),
1900 }
1901 }
1902
1903 pub fn with_x25519_key(mut self, x25519_key: [u8; 32]) -> Self {
1904 self.x25519_key = x25519_key;
1905 self
1906 }
1907
1908 pub fn with_p2p_addr(mut self, p2p_addr: impl Into<String>) -> Self {
1909 self.p2p_addr = p2p_addr.into();
1910 self
1911 }
1912
1913 pub fn x25519_update(&self, x25519_key: [u8; 32]) -> StakeTableEvent {
1914 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
1915 validator: self.account,
1916 x25519Key: alloy::primitives::FixedBytes(x25519_key),
1917 })
1918 }
1919
1920 pub fn p2p_update(&self, p2p_addr: impl Into<String>) -> StakeTableEvent {
1921 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
1922 validator: self.account,
1923 p2pAddr: p2p_addr.into(),
1924 })
1925 }
1926 }
1927
1928 impl From<&TestValidator> for ValidatorRegistered {
1929 fn from(value: &TestValidator) -> Self {
1930 Self {
1931 account: value.account,
1932 blsVk: value.bls_vk,
1933 schnorrVk: value.schnorr_vk,
1934 commission: value.commission,
1935 }
1936 }
1937 }
1938
1939 impl From<&TestValidator> for ValidatorRegisteredV2 {
1940 fn from(value: &TestValidator) -> Self {
1941 Self {
1942 account: value.account,
1943 blsVK: value.bls_vk,
1944 schnorrVK: value.schnorr_vk,
1945 commission: value.commission,
1946 blsSig: value.bls_sig.into(),
1947 schnorrSig: value.schnorr_sig.clone(),
1948 metadataUri: "dummy-meta".to_string(),
1949 }
1950 }
1951 }
1952
1953 impl From<&TestValidator> for ValidatorRegisteredV3 {
1954 fn from(value: &TestValidator) -> Self {
1955 Self {
1956 account: value.account,
1957 blsVK: value.bls_vk,
1958 schnorrVK: value.schnorr_vk,
1959 commission: value.commission,
1960 blsSig: value.bls_sig.into(),
1961 schnorrSig: value.schnorr_sig.clone(),
1962 metadataUri: "dummy-meta".to_string(),
1963 x25519Key: alloy::primitives::FixedBytes(value.x25519_key),
1964 p2pAddr: value.p2p_addr.clone(),
1965 }
1966 }
1967 }
1968
1969 impl From<&TestValidator> for ConsensusKeysUpdated {
1970 fn from(value: &TestValidator) -> Self {
1971 Self {
1972 account: value.account,
1973 blsVK: value.bls_vk,
1974 schnorrVK: value.schnorr_vk,
1975 }
1976 }
1977 }
1978
1979 impl From<&TestValidator> for ConsensusKeysUpdatedV2 {
1980 fn from(value: &TestValidator) -> Self {
1981 Self {
1982 account: value.account,
1983 blsVK: value.bls_vk,
1984 schnorrVK: value.schnorr_vk,
1985 blsSig: value.bls_sig.into(),
1986 schnorrSig: value.schnorr_sig.clone(),
1987 }
1988 }
1989 }
1990
1991 impl From<&TestValidator> for ValidatorExit {
1992 fn from(value: &TestValidator) -> Self {
1993 Self {
1994 validator: value.account,
1995 }
1996 }
1997 }
1998
1999 impl RegisteredValidator<BLSPubKey> {
2000 pub fn mock() -> RegisteredValidator<BLSPubKey> {
2001 let val = TestValidator::random();
2002 let rng = &mut rand::thread_rng();
2003 let mut seed = [1u8; 32];
2004 rng.fill_bytes(&mut seed);
2005 let mut validator_stake = alloy::primitives::U256::from(0);
2006 let mut delegators = HashMap::new();
2007 for _i in 0..=5000 {
2008 let stake: u64 = rng.gen_range(0..10000);
2009 delegators.insert(Address::random(), alloy::primitives::U256::from(stake));
2010 validator_stake += alloy::primitives::U256::from(stake);
2011 }
2012
2013 let stake_table_key = BLSPubKey::try_from(val.bls_vk).expect("valid test BLS key");
2014 let state_ver_key =
2015 SchnorrPubKey::try_from(val.schnorr_vk).expect("valid test Schnorr key");
2016
2017 RegisteredValidator {
2018 account: val.account,
2019 stake_table_key: Some(stake_table_key),
2020 state_ver_key: Some(state_ver_key),
2021 stake: validator_stake,
2022 commission: val.commission,
2023 delegators,
2024 authenticated: true,
2025 x25519_key: None,
2026 p2p_addr: None,
2027 }
2028 }
2029 }
2030
2031 impl AuthenticatedValidator<BLSPubKey> {
2032 pub fn mock() -> AuthenticatedValidator<BLSPubKey> {
2033 RegisteredValidator::mock()
2034 .try_into()
2035 .expect("mock validator is always authenticated")
2036 }
2037
2038 pub fn mock_with_commission(commission: u16) -> AuthenticatedValidator<BLSPubKey> {
2039 let mut inner = RegisteredValidator::mock();
2040 inner.commission = commission;
2041 inner
2042 .try_into()
2043 .expect("mock validator is always authenticated")
2044 }
2045 }
2046}
2047
2048#[cfg(test)]
2049mod tests {
2050 use alloy::{
2051 primitives::{Address, Bytes},
2052 rpc::types::Log,
2053 };
2054 use hotshot_contract_adapter::{
2055 sol_types::{G1PointSol, G2PointSol},
2056 stake_table::{StakeTableContractVersion, sign_address_bls},
2057 };
2058 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
2059 use pretty_assertions::assert_matches;
2060 use rstest::rstest;
2061 use versions::{
2062 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
2063 };
2064
2065 use super::*;
2066 use crate::{L1ClientOptions, v0::impls::testing::*};
2067
2068 fn zero_g2() -> G2PointSol {
2069 G2PointSol {
2070 x0: U256::ZERO,
2071 x1: U256::ZERO,
2072 y0: U256::ZERO,
2073 y1: U256::ZERO,
2074 }
2075 }
2076
2077 fn zero_g1() -> G1PointSol {
2078 G1PointSol {
2079 x: U256::ZERO,
2080 y: U256::ZERO,
2081 }
2082 }
2083
2084 #[test_log::test]
2085 fn test_from_l1_events() -> anyhow::Result<()> {
2086 let val_1 = TestValidator::random();
2088 let val_1_new_keys = val_1.randomize_keys();
2089 let val_2 = TestValidator::random();
2090 let val_2_new_keys = val_2.randomize_keys();
2091 let delegator = Address::random();
2092 let mut events: Vec<StakeTableEvent> = [
2093 ValidatorRegistered::from(&val_1).into(),
2094 ValidatorRegisteredV2::from(&val_2).into(),
2095 Delegated {
2096 delegator,
2097 validator: val_1.account,
2098 amount: U256::from(10),
2099 }
2100 .into(),
2101 ConsensusKeysUpdated::from(&val_1_new_keys).into(),
2102 ConsensusKeysUpdatedV2::from(&val_2_new_keys).into(),
2103 Undelegated {
2104 delegator,
2105 validator: val_1.account,
2106 amount: U256::from(7),
2107 }
2108 .into(),
2109 Delegated {
2111 delegator,
2112 validator: val_1.account,
2113 amount: U256::from(5),
2114 }
2115 .into(),
2116 Delegated {
2118 delegator: Address::random(),
2119 validator: val_2.account,
2120 amount: U256::from(3),
2121 }
2122 .into(),
2123 ]
2124 .to_vec();
2125
2126 let validators_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
2127 let st = validators_set.active_validators;
2128 let st_val_1 = st.get(&val_1.account).unwrap();
2129 assert_eq!(st_val_1.stake, U256::from(8));
2131 assert_eq!(st_val_1.commission, val_1.commission);
2132 assert_eq!(st_val_1.delegators.len(), 1);
2133 assert_eq!(*st_val_1.delegators.get(&delegator).unwrap(), U256::from(8));
2135
2136 let st_val_2 = st.get(&val_2.account).unwrap();
2137 assert_eq!(st_val_2.stake, U256::from(3));
2138 assert_eq!(st_val_2.commission, val_2.commission);
2139 assert_eq!(st_val_2.delegators.len(), 1);
2140
2141 events.push(ValidatorExit::from(&val_1).into());
2142
2143 let validator_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
2144 let st = validator_set.active_validators;
2145 assert_eq!(st.get(&val_1.account), None);
2147
2148 let st_val_2 = st.get(&val_2.account).unwrap();
2150 assert_eq!(st_val_2.stake, U256::from(3));
2151 assert_eq!(st_val_2.commission, val_2.commission);
2152 assert_eq!(st_val_2.delegators.len(), 1);
2153
2154 events.push(ValidatorExit::from(&val_2).into());
2156
2157 assert!(ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION).is_err());
2159
2160 Ok(())
2161 }
2162
2163 #[test]
2164 fn test_from_l1_events_failures() -> anyhow::Result<()> {
2165 let val = TestValidator::random();
2166 let delegator = Address::random();
2167
2168 let register: StakeTableEvent = ValidatorRegistered::from(&val).into();
2169 let register_v2: StakeTableEvent = ValidatorRegisteredV2::from(&val).into();
2170 let delegate: StakeTableEvent = Delegated {
2171 delegator,
2172 validator: val.account,
2173 amount: U256::from(10),
2174 }
2175 .into();
2176 let key_update: StakeTableEvent = ConsensusKeysUpdated::from(&val).into();
2177 let key_update_v2: StakeTableEvent = ConsensusKeysUpdatedV2::from(&val).into();
2178 let undelegate: StakeTableEvent = Undelegated {
2179 delegator,
2180 validator: val.account,
2181 amount: U256::from(7),
2182 }
2183 .into();
2184
2185 let exit: StakeTableEvent = ValidatorExit::from(&val).into();
2186
2187 let cases = [
2188 vec![exit],
2189 vec![undelegate.clone()],
2190 vec![delegate.clone()],
2191 vec![key_update],
2192 vec![key_update_v2],
2193 vec![register.clone(), register.clone()],
2194 vec![register_v2.clone(), register_v2.clone()],
2195 vec![register.clone(), register_v2.clone()],
2196 vec![register_v2.clone(), register.clone()],
2197 vec![
2198 register,
2199 delegate.clone(),
2200 undelegate.clone(),
2201 undelegate.clone(),
2202 ],
2203 vec![register_v2, delegate, undelegate.clone(), undelegate],
2204 ];
2205
2206 for events in cases.iter() {
2207 let res = validators_from_l1_events(events.iter().cloned());
2211 assert!(
2212 res.is_err(),
2213 "events {res:?}, not a valid sequence of events"
2214 );
2215 }
2216 Ok(())
2217 }
2218
2219 #[test]
2220 fn test_validators_selection() {
2221 let mut candidates = IndexMap::new();
2222 let mut highest_stake = alloy::primitives::U256::ZERO;
2223
2224 for _i in 0..3000 {
2225 let candidate = RegisteredValidator::mock();
2226 candidates.insert(candidate.account, candidate.clone());
2227
2228 if candidate.stake > highest_stake {
2229 highest_stake = candidate.stake;
2230 }
2231 }
2232
2233 let minimum_stake = highest_stake / U256::from(VID_TARGET_TOTAL_STAKE);
2234
2235 let selected_validators = select_active_validator_set(&candidates, EPOCH_VERSION)
2236 .expect("Failed to select validators");
2237 assert!(
2238 selected_validators.len() <= MAX_VALIDATORS,
2239 "validators len is {}, expected at most {MAX_VALIDATORS}",
2240 selected_validators.len()
2241 );
2242
2243 let mut selected_validators_highest_stake = alloy::primitives::U256::ZERO;
2244 for (address, validator) in &selected_validators {
2246 assert!(
2247 validator.stake >= minimum_stake,
2248 "Validator {:?} has stake below minimum: {}",
2249 address,
2250 validator.stake
2251 );
2252
2253 if validator.stake > selected_validators_highest_stake {
2254 selected_validators_highest_stake = validator.stake;
2255 }
2256 }
2257 }
2258
2259 #[rstest::rstest]
2262 fn test_regression_non_unique_bls_keys_not_discarded(
2263 #[values(
2264 StakeTableContractVersion::V1,
2265 StakeTableContractVersion::V2,
2266 StakeTableContractVersion::V3
2267 )]
2268 version: StakeTableContractVersion,
2269 ) {
2270 let val = TestValidator::random();
2271 let register: StakeTableEvent = match version {
2272 StakeTableContractVersion::V1 => ValidatorRegistered::from(&val).into(),
2273 StakeTableContractVersion::V2 => ValidatorRegisteredV2::from(&val).into(),
2274 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&val).into()),
2275 };
2276 let delegate: StakeTableEvent = Delegated {
2277 delegator: Address::random(),
2278 validator: val.account,
2279 amount: U256::from(10),
2280 }
2281 .into();
2282
2283 assert!(
2285 ValidatorSet::from_l1_events(
2286 vec![register.clone(), delegate.clone()].into_iter(),
2287 EPOCH_VERSION,
2288 )
2289 .is_ok()
2290 );
2291
2292 let key_update = ConsensusKeysUpdated::from(&val).into();
2294 let err = ValidatorSet::from_l1_events(
2295 vec![register, delegate, key_update].into_iter(),
2296 EPOCH_VERSION,
2297 )
2298 .unwrap_err();
2299
2300 let bls = BLSPubKey::try_from(val.bls_vk).expect("valid test BLS key");
2301 assert!(matches!(err, StakeTableError::BlsKeyAlreadyUsed(addr) if addr == bls.to_string()));
2302 }
2303
2304 #[test]
2307 fn test_regression_reregister_eth_account() {
2308 let val1 = TestValidator::random();
2309 let val2 = val1.randomize_keys();
2310 let account = val1.account;
2311
2312 let register1 = ValidatorRegisteredV2::from(&val1).into();
2313 let deregister1 = ValidatorExit::from(&val1).into();
2314 let register2 = ValidatorRegisteredV2::from(&val2).into();
2315 let events = [register1, deregister1, register2];
2316 let error = validators_from_l1_events(events.iter().cloned()).unwrap_err();
2317 assert_matches!(error, StakeTableError::ValidatorAlreadyExited(addr) if addr == account);
2318 }
2319
2320 #[test]
2321 fn test_display_log() {
2322 let serialized = r#"{"address":"0x0000000000000000000000000000000000000069",
2323 "topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],
2324 "data":"0x69",
2325 "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2326 "blockNumber":"0x69","blockTimestamp":"0x69",
2327 "transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2328 "transactionIndex":"0x69","logIndex":"0x70","removed":false}"#;
2329 let log: Log = serde_json::from_str(serialized).unwrap();
2330 assert_eq!(
2331 log.display(),
2332 "Log(block=105,index=112,\
2333 transaction_hash=0x0000000000000000000000000000000000000000000000000000000000000069)"
2334 )
2335 }
2336
2337 #[rstest]
2338 #[case::v1(StakeTableContractVersion::V1)]
2339 #[case::v2(StakeTableContractVersion::V2)]
2340 #[case::v3(StakeTableContractVersion::V3)]
2341 fn test_register_validator(#[case] version: StakeTableContractVersion) {
2342 let mut state = StakeTableState::default();
2343 let validator = TestValidator::random();
2344
2345 let event = match version {
2346 StakeTableContractVersion::V1 => StakeTableEvent::Register((&validator).into()),
2347 StakeTableContractVersion::V2 => StakeTableEvent::RegisterV2((&validator).into()),
2348 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&validator).into()),
2349 };
2350
2351 state.apply_event(event).unwrap().unwrap();
2352
2353 let stored = state.validators.get(&validator.account).unwrap();
2354 assert_eq!(stored.account, validator.account);
2355 }
2356
2357 #[rstest]
2358 #[case::v1(StakeTableContractVersion::V1)]
2359 #[case::v2(StakeTableContractVersion::V2)]
2360 #[case::v3(StakeTableContractVersion::V3)]
2361 fn test_validator_already_registered(#[case] version: StakeTableContractVersion) {
2362 let mut stake_table_state = StakeTableState::default();
2363
2364 let test_validator = TestValidator::random();
2365
2366 match version {
2368 StakeTableContractVersion::V1 => {
2369 stake_table_state.apply_event(StakeTableEvent::Register((&test_validator).into()))
2370 },
2371 StakeTableContractVersion::V2 => {
2372 stake_table_state.apply_event(StakeTableEvent::RegisterV2((&test_validator).into()))
2373 },
2374 StakeTableContractVersion::V3 => {
2375 stake_table_state.apply_event(StakeTableEvent::RegisterV3((&test_validator).into()))
2376 },
2377 }
2378 .unwrap()
2379 .unwrap(); let v1_already_registered_result = stake_table_state
2383 .clone()
2384 .apply_event(StakeTableEvent::Register((&test_validator).into()));
2385
2386 pretty_assertions::assert_matches!(
2387 v1_already_registered_result, Err(StakeTableError::AlreadyRegistered(account))
2388 if account == test_validator.account,
2389 "Expected AlreadyRegistered error. version ={version:?} result={v1_already_registered_result:?}",
2390 );
2391
2392 let v2_already_registered_result = stake_table_state
2394 .clone()
2395 .apply_event(StakeTableEvent::RegisterV2((&test_validator).into()));
2396
2397 pretty_assertions::assert_matches!(
2398 v2_already_registered_result,
2399 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2400 "Expected AlreadyRegistered error. version ={version:?} result={v2_already_registered_result:?}",
2401
2402 );
2403
2404 let v3_already_registered_result =
2407 stake_table_state
2408 .clone()
2409 .apply_event(StakeTableEvent::RegisterV3(
2410 (&test_validator
2411 .clone()
2412 .with_x25519_key([43u8; 32])
2413 .with_p2p_addr("127.0.0.1:9001"))
2414 .into(),
2415 ));
2416
2417 pretty_assertions::assert_matches!(
2418 v3_already_registered_result,
2419 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2420 "Expected AlreadyRegistered error. version ={version:?} result={v3_already_registered_result:?}",
2421 );
2422 }
2423
2424 #[test]
2425 fn test_register_validator_v2_auth_fails_marks_as_unauthenticated() {
2426 let mut state = StakeTableState::default();
2427 let mut val = TestValidator::random();
2428 val.bls_sig = Default::default();
2429 let event = StakeTableEvent::RegisterV2((&val).into());
2430
2431 let result = state.apply_event(event);
2432 assert!(
2433 result.is_ok(),
2434 "Validator with invalid auth should still be accepted"
2435 );
2436
2437 let validator = state
2438 .validators()
2439 .get(&val.account)
2440 .expect("validator should exist");
2441 assert!(
2442 !validator.authenticated,
2443 "Validator should be marked as not authenticated"
2444 );
2445
2446 let event = StakeTableEvent::Delegate(Delegated {
2447 delegator: Address::random(),
2448 validator: val.account,
2449 amount: U256::from(100),
2450 });
2451 state.apply_event(event).unwrap().unwrap();
2452
2453 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
2454 match active {
2455 Err(_) => {}, Ok(map) => {
2457 assert!(
2458 map.get(&val.account).is_none(),
2459 "Unauthenticated validator should not be in active set"
2460 );
2461 },
2462 }
2463 }
2464
2465 #[test]
2466 fn test_register_v2_bad_sig_preserves_parsed_bls_key() {
2467 let val = TestValidator::random();
2468 let other = TestValidator::random();
2469 let bad_val = TestValidator {
2470 bls_sig: other.bls_sig,
2471 ..val.clone()
2472 };
2473
2474 let mut state = StakeTableState::default();
2475 state
2476 .apply_event(StakeTableEvent::RegisterV2((&bad_val).into()))
2477 .expect("no fatal error")
2478 .expect("registered unauthenticated");
2479
2480 let registered = state.validators().get(&val.account).expect("present");
2481 assert!(!registered.authenticated);
2482 let expected_bls = BLSPubKey::try_from(val.bls_vk).expect("valid bls key");
2483 assert_eq!(registered.stake_table_key.as_ref(), Some(&expected_bls));
2484 }
2485
2486 #[test]
2487 fn test_register_v3_bad_sig_preserves_parsed_bls_key() {
2488 let val = TestValidator::random();
2489 let other = TestValidator::random();
2490 let bad_val = TestValidator {
2491 bls_sig: other.bls_sig,
2492 ..val.clone()
2493 };
2494
2495 let mut state = StakeTableState::default();
2496 state
2497 .apply_event(StakeTableEvent::RegisterV3((&bad_val).into()))
2498 .expect("no fatal error")
2499 .expect("registered unauthenticated");
2500
2501 let registered = state.validators().get(&val.account).expect("present");
2502 assert!(!registered.authenticated);
2503 let expected_bls = BLSPubKey::try_from(val.bls_vk).expect("valid bls key");
2504 assert_eq!(registered.stake_table_key.as_ref(), Some(&expected_bls));
2505 }
2506
2507 #[test]
2508 fn test_authenticated_validator_deserialize_rejects_unauthenticated() {
2509 let mut validator = RegisteredValidator::<BLSPubKey>::mock();
2510 validator.authenticated = false;
2511
2512 let json = serde_json::to_string(&validator).unwrap();
2513 let result: Result<AuthenticatedValidator<BLSPubKey>, _> = serde_json::from_str(&json);
2514
2515 assert!(result.is_err());
2516 let err = result.unwrap_err().to_string();
2517 assert!(
2518 err.contains("cannot deserialize unauthenticated validator"),
2519 "unexpected error: {err}"
2520 );
2521 }
2522
2523 #[rstest]
2524 #[case::v1(StakeTableContractVersion::V1)]
2525 #[case::v2(StakeTableContractVersion::V2)]
2526 #[case::v3(StakeTableContractVersion::V3)]
2527 fn test_deregister_validator(#[case] version: StakeTableContractVersion) {
2528 let mut state = StakeTableState::default();
2529 let val = TestValidator::random();
2530
2531 let reg = StakeTableEvent::Register((&val).into());
2532 state.apply_event(reg).unwrap().unwrap();
2533
2534 let dereg = match version {
2535 StakeTableContractVersion::V1 => StakeTableEvent::Deregister((&val).into()),
2536 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2537 StakeTableEvent::DeregisterV2(ValidatorExitV2 {
2538 validator: val.account,
2539 unlocksAt: U256::from(1000u64),
2540 })
2541 },
2542 };
2543 state.apply_event(dereg).unwrap().unwrap();
2544 assert!(!state.validators.contains_key(&val.account));
2545 }
2546
2547 #[rstest]
2548 #[case::v1(StakeTableContractVersion::V1)]
2549 #[case::v2(StakeTableContractVersion::V2)]
2550 #[case::v3(StakeTableContractVersion::V3)]
2551 fn test_delegate_and_undelegate(#[case] version: StakeTableContractVersion) {
2552 let mut state = StakeTableState::default();
2553 let val = TestValidator::random();
2554 state
2555 .apply_event(StakeTableEvent::Register((&val).into()))
2556 .unwrap()
2557 .unwrap();
2558
2559 let delegator = Address::random();
2560 let amount = U256::from(1000);
2561 let delegate_event = StakeTableEvent::Delegate(Delegated {
2562 delegator,
2563 validator: val.account,
2564 amount,
2565 });
2566 state.apply_event(delegate_event).unwrap().unwrap();
2567
2568 let validator = state.validators.get(&val.account).unwrap();
2569 assert_eq!(validator.delegators.get(&delegator).cloned(), Some(amount));
2570
2571 let undelegate_event = match version {
2572 StakeTableContractVersion::V1 => StakeTableEvent::Undelegate(Undelegated {
2573 delegator,
2574 validator: val.account,
2575 amount,
2576 }),
2577 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2578 StakeTableEvent::UndelegateV2(UndelegatedV2 {
2579 delegator,
2580 validator: val.account,
2581 amount,
2582 unlocksAt: U256::from(2000u64),
2583 undelegationId: 1,
2584 })
2585 },
2586 };
2587 state.apply_event(undelegate_event).unwrap().unwrap();
2588 let validator = state.validators.get(&val.account).unwrap();
2589 assert!(!validator.delegators.contains_key(&delegator));
2590 }
2591
2592 #[rstest]
2593 #[case::v1(StakeTableContractVersion::V1)]
2594 #[case::v2(StakeTableContractVersion::V2)]
2595 #[case::v3(StakeTableContractVersion::V3)]
2596 fn test_key_update_event(#[case] version: StakeTableContractVersion) {
2597 let mut state = StakeTableState::default();
2598 let val = TestValidator::random();
2599
2600 state
2602 .apply_event(StakeTableEvent::Register((&val).into()))
2603 .unwrap()
2604 .unwrap();
2605
2606 let new_keys = val.randomize_keys();
2607
2608 let event = match version {
2609 StakeTableContractVersion::V1 => StakeTableEvent::KeyUpdate((&new_keys).into()),
2610 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2611 StakeTableEvent::KeyUpdateV2((&new_keys).into())
2612 },
2613 };
2614
2615 state.apply_event(event).unwrap().unwrap();
2616
2617 let updated = state.validators.get(&val.account).unwrap();
2618 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).expect("valid test BLS key");
2619 assert_eq!(updated.stake_table_key.as_ref(), Some(&expected_bls));
2620 let expected_schnorr =
2621 SchnorrPubKey::try_from(new_keys.schnorr_vk).expect("valid test Schnorr key");
2622 assert_eq!(updated.state_ver_key.as_ref(), Some(&expected_schnorr));
2623 }
2624
2625 #[test]
2626 fn test_duplicate_bls_key() {
2627 let mut state = StakeTableState::default();
2628 let val = TestValidator::random();
2629 let event1 = StakeTableEvent::Register((&val).into());
2630 let mut val2 = TestValidator::random();
2631 val2.bls_vk = val.bls_vk;
2632 val2.account = Address::random();
2633
2634 let event2 = StakeTableEvent::Register((&val2).into());
2635 state.apply_event(event1).unwrap().unwrap();
2636 let result = state.apply_event(event2);
2637
2638 let expected_bls_key = BLSPubKey::try_from(val.bls_vk)
2639 .expect("valid test BLS key")
2640 .to_string();
2641
2642 assert_matches!(
2643 result,
2644 Err(StakeTableError::BlsKeyAlreadyUsed(key))
2645 if key == expected_bls_key,
2646 "Expected BlsKeyAlreadyUsed({expected_bls_key}), but got: {result:?}",
2647 );
2648 }
2649
2650 #[test]
2651 fn test_duplicate_schnorr_key() {
2652 let mut state = StakeTableState::default();
2653 let val = TestValidator::random();
2654 let event1 = StakeTableEvent::Register((&val).into());
2655 let mut val2 = TestValidator::random();
2656 val2.schnorr_vk = val.schnorr_vk;
2657 val2.account = Address::random();
2658 val2.bls_vk = val2.randomize_keys().bls_vk;
2659
2660 let event2 = StakeTableEvent::Register((&val2).into());
2661 state.apply_event(event1).unwrap().unwrap();
2662 let result = state.apply_event(event2);
2663
2664 let schnorr = SchnorrPubKey::try_from(val.schnorr_vk).expect("valid test Schnorr key");
2665 assert_matches!(
2666 result,
2667 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
2668 if key == schnorr.to_string(),
2669 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2670
2671 );
2672 }
2673
2674 #[test]
2675 fn test_duplicate_schnorr_key_v2_during_update() {
2676 let mut state = StakeTableState::default();
2677
2678 let val1 = TestValidator::random();
2679
2680 let mut rng = &mut rand::thread_rng();
2681 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2682
2683 let val2 = TestValidator {
2684 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2685 bls_sig: sign_address_bls(&bls_key_pair, val1.account).into(),
2686 ..val1.clone()
2687 };
2688 let event1 = StakeTableEvent::RegisterV2((&val1).into());
2689 let event2 = StakeTableEvent::KeyUpdateV2((&val2).into());
2690
2691 state.apply_event(event1).unwrap().unwrap();
2692 let result = state.apply_event(event2);
2693
2694 let schnorr = SchnorrPubKey::try_from(val1.schnorr_vk).expect("valid test Schnorr key");
2695 assert_matches!(
2696 result,
2697 Err(StakeTableError::SchnorrKeyAlreadyUsed(key))
2698 if key == schnorr.to_string(),
2699 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2700 );
2701 }
2702
2703 #[test]
2704 fn test_register_and_deregister_validator() {
2705 let mut state = StakeTableState::default();
2706 let validator = TestValidator::random();
2707 let event = StakeTableEvent::Register((&validator).into());
2708 state.apply_event(event).unwrap().unwrap();
2709
2710 let deregister_event = StakeTableEvent::Deregister((&validator).into());
2711 assert!(state.apply_event(deregister_event).unwrap().is_ok());
2712 }
2713
2714 #[test]
2715 fn test_commission_validation_exceeds_basis_points() {
2716 let validator = TestValidator::random();
2718 let mut stake_table = StakeTableState::default();
2719
2720 let registration_event = ValidatorRegistered::from(&validator).into();
2722 stake_table
2723 .apply_event(registration_event)
2724 .unwrap()
2725 .unwrap();
2726
2727 let valid_commission_event = CommissionUpdated {
2729 validator: validator.account,
2730 timestamp: Default::default(),
2731 oldCommission: 0,
2732 newCommission: COMMISSION_BASIS_POINTS, }
2734 .into();
2735 stake_table
2736 .apply_event(valid_commission_event)
2737 .unwrap()
2738 .unwrap();
2739
2740 let invalid_commission = COMMISSION_BASIS_POINTS + 1;
2741 let invalid_commission_event = CommissionUpdated {
2742 validator: validator.account,
2743 timestamp: Default::default(),
2744 oldCommission: 0,
2745 newCommission: invalid_commission,
2746 }
2747 .into();
2748
2749 let err = stake_table
2750 .apply_event(invalid_commission_event)
2751 .unwrap_err();
2752
2753 assert_matches!(
2754 err,
2755 StakeTableError::InvalidCommission(addr, invalid_commission)
2756 if addr == addr && invalid_commission == invalid_commission);
2757 }
2758
2759 #[test]
2760 fn test_delegate_zero_amount_is_rejected() {
2761 let mut state = StakeTableState::default();
2762 let validator = TestValidator::random();
2763 let account = validator.account;
2764 state
2765 .apply_event(StakeTableEvent::Register((&validator).into()))
2766 .unwrap()
2767 .unwrap();
2768
2769 let delegator = Address::random();
2770 let amount = U256::ZERO;
2771 let event = StakeTableEvent::Delegate(Delegated {
2772 delegator,
2773 validator: account,
2774 amount,
2775 });
2776 let result = state.apply_event(event);
2777
2778 assert_matches!(
2779 result,
2780 Err(StakeTableError::ZeroDelegatorStake(addr))
2781 if addr == delegator,
2782 "delegator stake is zero"
2783
2784 );
2785 }
2786
2787 #[test]
2788 fn test_undelegate_more_than_stake_fails() {
2789 let mut state = StakeTableState::default();
2790 let validator = TestValidator::random();
2791 let account = validator.account;
2792 state
2793 .apply_event(StakeTableEvent::Register((&validator).into()))
2794 .unwrap()
2795 .unwrap();
2796
2797 let delegator = Address::random();
2798 let event = StakeTableEvent::Delegate(Delegated {
2799 delegator,
2800 validator: account,
2801 amount: U256::from(10u64),
2802 });
2803 state.apply_event(event).unwrap().unwrap();
2804
2805 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2806 delegator,
2807 validator: account,
2808 amount: U256::from(20u64),
2809 }));
2810 assert_matches!(
2811 result,
2812 Err(StakeTableError::InsufficientStake),
2813 "Expected InsufficientStake error, got: {result:?}",
2814 );
2815 }
2816
2817 #[test]
2818 fn test_apply_event_does_not_modify_state_on_error() {
2819 let mut state = StakeTableState::default();
2820 let validator = TestValidator::random();
2821 let delegator = Address::random();
2822
2823 state
2824 .apply_event(StakeTableEvent::Register((&validator).into()))
2825 .unwrap()
2826 .unwrap();
2827
2828 let state_before = state.clone();
2830 let result = state.apply_event(StakeTableEvent::Register((&validator).into()));
2831 assert_matches!(result, Err(StakeTableError::AlreadyRegistered(_)));
2832 assert_eq!(
2833 state, state_before,
2834 "State should not change on AlreadyRegistered error"
2835 );
2836
2837 let state_before = state.clone();
2839 let mut validator2 = TestValidator::random();
2840 validator2.bls_vk = validator.bls_vk; let result = state.apply_event(StakeTableEvent::Register((&validator2).into()));
2842 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
2843 assert_eq!(
2844 state, state_before,
2845 "State should not change on BlsKeyAlreadyUsed error"
2846 );
2847
2848 let state_before = state.clone();
2850 let nonexistent_validator = TestValidator::random();
2851 let result =
2852 state.apply_event(StakeTableEvent::Deregister((&nonexistent_validator).into()));
2853 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
2854 assert_eq!(
2855 state, state_before,
2856 "State should not change on ValidatorNotFound error"
2857 );
2858
2859 let state_before = state.clone();
2861 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2862 delegator: Address::random(),
2863 validator: Address::random(),
2864 amount: U256::from(100u64),
2865 }));
2866 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
2867 assert_eq!(
2868 state, state_before,
2869 "State should not change on ValidatorNotFound error for Undelegate"
2870 );
2871
2872 state
2873 .apply_event(StakeTableEvent::Delegate(Delegated {
2874 delegator,
2875 validator: validator.account,
2876 amount: U256::from(100u64),
2877 }))
2878 .unwrap()
2879 .unwrap();
2880
2881 let state_before = state.clone();
2883 let non_existent_delegator = Address::random();
2884 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2885 delegator: non_existent_delegator,
2886 validator: validator.account,
2887 amount: U256::from(50u64),
2888 }));
2889 assert_matches!(result, Err(StakeTableError::DelegatorNotFound(_)));
2890 assert_eq!(
2891 state, state_before,
2892 "State should not change on DelegatorNotFound error"
2893 );
2894
2895 let state_before = state.clone();
2897 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2898 delegator,
2899 validator: validator.account,
2900 amount: U256::from(200u64),
2901 }));
2902 assert_matches!(result, Err(StakeTableError::InsufficientStake));
2903 assert_eq!(
2904 state, state_before,
2905 "State should not change on InsufficientStake error"
2906 );
2907
2908 let validator2 = TestValidator::random();
2910 let delegator2 = Address::random();
2911
2912 state
2913 .apply_event(StakeTableEvent::Register((&validator2).into()))
2914 .unwrap()
2915 .unwrap();
2916
2917 state
2918 .apply_event(StakeTableEvent::Delegate(Delegated {
2919 delegator: delegator2,
2920 validator: validator2.account,
2921 amount: U256::from(50u64),
2922 }))
2923 .unwrap()
2924 .unwrap();
2925 let state_before = state.clone();
2926 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
2927 delegator: delegator2,
2928 validator: validator2.account,
2929 amount: U256::from(100u64),
2930 }));
2931 assert_matches!(result, Err(StakeTableError::InsufficientStake));
2932 assert_eq!(state, state_before,);
2933
2934 let state_before = state.clone();
2936 let result = state.apply_event(StakeTableEvent::Delegate(Delegated {
2937 delegator: Address::random(),
2938 validator: validator.account,
2939 amount: U256::ZERO,
2940 }));
2941 assert_matches!(result, Err(StakeTableError::ZeroDelegatorStake(_)));
2942 assert_eq!(
2943 state, state_before,
2944 "State should not change on ZeroDelegatorStake error"
2945 );
2946 }
2947
2948 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2949 async fn test_decaf_stake_table() {
2950 let events_json =
2986 std::fs::read_to_string("../../../data/v3/decaf_stake_table_events.json").unwrap();
2987 let events: Vec<(EventKey, StakeTableEvent)> = serde_json::from_str(&events_json).unwrap();
2988
2989 let reconstructed_stake_table =
2991 ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), EPOCH_VERSION)
2992 .unwrap()
2993 .active_validators;
2994
2995 let stake_table_json =
2996 std::fs::read_to_string("../../../data/v3/decaf_stake_table.json").unwrap();
2997 let expected: AuthenticatedValidatorMap = serde_json::from_str(&stake_table_json).unwrap();
2998
2999 assert_eq!(
3000 reconstructed_stake_table, expected,
3001 "Stake table reconstructed from events does not match the expected stake table "
3002 );
3003 }
3004
3005 async fn snapshot_stake_table_commit(
3006 network: &str,
3007 rpc_url: &str,
3008 contract: &str,
3009 to_block: u64,
3010 ) {
3011 let l1 = L1ClientOptions {
3012 l1_events_max_retry_duration: Duration::from_secs(120),
3013 l1_events_max_block_range: 10_000,
3014 l1_retry_delay: Duration::from_secs(2),
3015 ..Default::default()
3016 }
3017 .connect(vec![rpc_url.parse().unwrap()])
3018 .expect("unable to construct l1 client");
3019
3020 let events =
3021 Fetcher::fetch_events_from_contract(l1, contract.parse().unwrap(), None, to_block)
3022 .await
3023 .unwrap();
3024
3025 let validator_set =
3026 ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), EPOCH_VERSION)
3027 .expect("failed to build validator set");
3028
3029 let active_as_registered = to_registered_validator_map(validator_set.active_validators());
3030 let active_state = StakeTableState::new(
3031 active_as_registered,
3032 Default::default(),
3033 Default::default(),
3034 Default::default(),
3035 Default::default(),
3036 );
3037 let active_commit = active_state.commit();
3038
3039 let summary = format!(
3040 "network: {network}\nto_block: {to_block}\nstake_table_contract: \
3041 {contract}\nstake_table_hash: {}\nactive_validators_commit: {}\nall_validators: \
3042 {}\nactive_validators: {}\n",
3043 validator_set
3044 .stake_table_hash()
3045 .expect("stake_table_hash should be set"),
3046 active_commit,
3047 validator_set.all_validators().len(),
3048 validator_set.active_validators().len(),
3049 );
3050
3051 let mut settings = insta::Settings::clone_current();
3052 let data_dir = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
3053 .join("../../../data/insta_snapshots");
3054 settings.set_snapshot_path(data_dir);
3055 settings.set_prepend_module_to_snapshot(false);
3056 settings.bind(|| {
3057 insta::assert_snapshot!(format!("{network}_stake_table_snapshot"), summary);
3058 });
3059 }
3060
3061 #[ignore = "talks to public Sepolia RPC"]
3062 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3063 async fn snapshot_decaf_stake_table_commit() {
3064 snapshot_stake_table_commit(
3065 "decaf",
3066 "https://ethereum-sepolia.publicnode.com",
3067 "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037",
3068 10_935_000,
3069 )
3070 .await;
3071 }
3072
3073 #[ignore = "talks to public Ethereum mainnet RPC"]
3074 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3075 async fn snapshot_mainnet_stake_table_commit() {
3076 snapshot_stake_table_commit(
3077 "mainnet",
3078 "https://ethereum-rpc.publicnode.com",
3079 "0xcef474d372b5b09defe2af187bf17338dc704451",
3080 25_188_000,
3081 )
3082 .await;
3083 }
3084
3085 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3086 #[should_panic]
3087 async fn test_large_max_events_range_panic() {
3088 let contract_address = "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037";
3090
3091 let l1 = L1ClientOptions {
3092 l1_events_max_retry_duration: Duration::from_secs(30),
3093 l1_events_max_block_range: 10_u64.pow(9),
3095 l1_retry_delay: Duration::from_secs(1),
3096 ..Default::default()
3097 }
3098 .connect(vec![
3099 "https://ethereum-sepolia.publicnode.com".parse().unwrap(),
3100 ])
3101 .expect("unable to construct l1 client");
3102
3103 let latest_block = l1.provider.get_block_number().await.unwrap();
3104 let _events = Fetcher::fetch_events_from_contract(
3105 l1,
3106 contract_address.parse().unwrap(),
3107 None,
3108 latest_block,
3109 )
3110 .await
3111 .unwrap();
3112 }
3113
3114 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3115 async fn sanity_check_block_reward_v3() {
3116 let initial_supply = U256::from_str("10000000000000000000000000000").unwrap();
3118
3119 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
3120 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
3121 .unwrap();
3122
3123 println!("Calculated reward: {reward}");
3124 assert!(reward > U256::ZERO);
3125 }
3126
3127 #[test]
3128 fn sanity_check_p_and_rp() {
3129 let total_stake = BigDecimal::from_str("1000").unwrap();
3130 let total_supply = BigDecimal::from_str("10000").unwrap(); let (p, rp) =
3133 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3134
3135 assert!(p > BigDecimal::zero());
3136 assert!(p < BigDecimal::one());
3137 assert!(rp > BigDecimal::zero());
3138 }
3139
3140 #[test]
3141 fn test_p_out_of_range() {
3142 let total_stake = BigDecimal::from_str("1000").unwrap();
3143 let total_supply = BigDecimal::from_str("500").unwrap(); let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3146 assert!(result.is_err());
3147 }
3148
3149 #[test]
3150 fn test_zero_total_supply() {
3151 let total_stake = BigDecimal::from_str("1000").unwrap();
3152 let total_supply = BigDecimal::from(0);
3153
3154 let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3155 assert!(result.is_err());
3156 }
3157
3158 #[test]
3159 fn test_valid_p_and_rp() {
3160 let total_stake = BigDecimal::from_str("5000").unwrap();
3161 let total_supply = BigDecimal::from_str("10000").unwrap();
3162
3163 let (p, rp) =
3164 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3165
3166 assert_eq!(p, BigDecimal::from_str("0.5").unwrap());
3167 assert!(rp > BigDecimal::from_str("0.0").unwrap());
3168 }
3169
3170 #[test]
3171 fn test_very_small_p() {
3172 let total_stake = BigDecimal::from_str("1").unwrap(); let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap(); let (p, rp) =
3176 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3177
3178 assert!(p > BigDecimal::from_str("0").unwrap());
3179 assert!(p < BigDecimal::from_str("1e-18").unwrap()); assert!(rp > BigDecimal::zero());
3181 }
3182
3183 #[test]
3184 fn test_p_very_close_to_one() {
3185 let total_stake = BigDecimal::from_str("9999999999999999999999999999").unwrap();
3186 let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap();
3187
3188 let (p, rp) =
3189 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3190
3191 assert!(p < BigDecimal::one());
3192 assert!(p > BigDecimal::from_str("0.999999999999999999999999999").unwrap());
3193 assert!(rp > BigDecimal::zero());
3194 }
3195
3196 #[test]
3200 fn test_reward_rate_rp() {
3201 let test_cases = [
3202 ("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"), ];
3214
3215 let tolerance = BigDecimal::from_str("0.0001").unwrap();
3216
3217 let total_supply = BigDecimal::from_u32(10_000).unwrap();
3218
3219 for (p, rp) in test_cases {
3220 let p = BigDecimal::from_str(p).unwrap();
3221 let expected_rp = BigDecimal::from_str(rp).unwrap();
3222
3223 let total_stake = &p * &total_supply;
3224
3225 let (computed_p, computed_rp) =
3226 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3227
3228 assert!(
3229 (&computed_p - &p).abs() < tolerance,
3230 "p mismatch: got {computed_p}, expected {p}"
3231 );
3232
3233 assert!(
3234 (&computed_rp - &expected_rp).abs() < tolerance,
3235 "R(p) mismatch for p={p}: got {computed_rp}, expected {expected_rp}"
3236 );
3237 }
3238 }
3239
3240 #[tokio::test(flavor = "multi_thread")]
3241 async fn test_dynamic_block_reward_with_expected_values() {
3242 let total_supply = U256::from_str("10000000000000000000000000000").unwrap();
3244 let total_supply_bd = BigDecimal::from_str(&total_supply.to_string()).unwrap();
3245
3246 let test_cases = [
3247 ("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"), ];
3281
3282 let tolerance = U256::from(100_000_000_000_000_000u128); for (p, rp, avg_block_time_ms, expected_reward) in test_cases {
3285 let p = BigDecimal::from_str(p).unwrap();
3286 let total_stake_bd = (&p * &total_supply_bd).round(0);
3287 println!("total_stake_bd={total_stake_bd}");
3288
3289 let total_stake = U256::from_str(&total_stake_bd.to_plain_string()).unwrap();
3290 let expected_reward = U256::from_str(expected_reward).unwrap();
3291
3292 let epoch = EpochNumber::new(0);
3293 let actual_reward =
3294 compute_block_reward(epoch, total_supply, total_stake, avg_block_time_ms)
3295 .unwrap()
3296 .0;
3297
3298 let diff = if actual_reward > expected_reward {
3299 actual_reward - expected_reward
3300 } else {
3301 expected_reward - actual_reward
3302 };
3303
3304 assert!(
3305 diff <= tolerance,
3306 "Reward mismatch for p = {p}, R(p) = {rp}, block_time = {avg_block_time_ms}: \
3307 expected = {expected_reward}, actual = {actual_reward}, diff = {diff}"
3308 );
3309 }
3310 }
3311
3312 #[derive(Debug, Clone, Copy)]
3314 enum EventType {
3315 Delegate,
3316 Undelegate,
3317 KeyUpdate,
3318 CommissionUpdate,
3319 Exit,
3320 X25519KeyUpdate,
3321 P2pAddrUpdate,
3322 }
3323
3324 #[rstest]
3329 #[case::delegate(EventType::Delegate)]
3330 #[case::undelegate(EventType::Undelegate)]
3331 #[case::key_update(EventType::KeyUpdate)]
3332 #[case::commission_update(EventType::CommissionUpdate)]
3333 #[case::exit(EventType::Exit)]
3334 #[case::x25519_key_update(EventType::X25519KeyUpdate)]
3335 #[case::p2p_addr_update(EventType::P2pAddrUpdate)]
3336 fn test_events_targeting_unauthenticated_validator(
3337 #[case] event_type: EventType,
3338 ) -> anyhow::Result<()> {
3339 let mut state = StakeTableState::default();
3340 let mut val = TestValidator::random();
3341 val.bls_sig = Default::default();
3342 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3343
3344 let validator = state.validators().get(&val.account).context("validator")?;
3345 assert!(!validator.authenticated);
3346
3347 let delegator = Address::random();
3348 let initial_amount = U256::from(1000);
3349 state.apply_event(StakeTableEvent::Delegate(Delegated {
3350 delegator,
3351 validator: val.account,
3352 amount: initial_amount,
3353 }))??;
3354
3355 match event_type {
3356 EventType::Delegate => {
3357 let new_delegator = Address::random();
3358 let amount = U256::from(500);
3359 state.apply_event(StakeTableEvent::Delegate(Delegated {
3360 delegator: new_delegator,
3361 validator: val.account,
3362 amount,
3363 }))??;
3364
3365 let validator = state.validators().get(&val.account).context("validator")?;
3366 assert_eq!(validator.stake, initial_amount + amount);
3367 assert_eq!(
3368 validator.delegators.get(&new_delegator).cloned(),
3369 Some(amount)
3370 );
3371 },
3372 EventType::Undelegate => {
3373 let undelegate_amount = U256::from(300);
3374 state.apply_event(StakeTableEvent::UndelegateV2(UndelegatedV2 {
3375 delegator,
3376 validator: val.account,
3377 undelegationId: 1,
3378 amount: undelegate_amount,
3379 unlocksAt: U256::from(1000u64),
3380 }))??;
3381
3382 let validator = state.validators().get(&val.account).context("validator")?;
3383 assert_eq!(validator.stake, initial_amount - undelegate_amount);
3384 assert_eq!(
3385 validator.delegators.get(&delegator).cloned(),
3386 Some(initial_amount - undelegate_amount)
3387 );
3388 },
3389 EventType::KeyUpdate => {
3390 let new_keys = val.randomize_keys();
3391 state.apply_event(StakeTableEvent::KeyUpdateV2((&new_keys).into()))??;
3392
3393 let validator = state.validators().get(&val.account).context("validator")?;
3394 let expected_bls =
3395 BLSPubKey::try_from(new_keys.bls_vk).expect("valid test BLS key");
3396 let expected_schnorr =
3397 SchnorrPubKey::try_from(new_keys.schnorr_vk).expect("valid test Schnorr key");
3398 assert_eq!(validator.stake_table_key.as_ref(), Some(&expected_bls));
3399 assert_eq!(validator.state_ver_key.as_ref(), Some(&expected_schnorr));
3400 assert!(validator.authenticated);
3404 },
3405 EventType::CommissionUpdate => {
3406 let new_commission: u16 = 5000;
3407 state.apply_event(StakeTableEvent::CommissionUpdate(CommissionUpdated {
3408 validator: val.account,
3409 timestamp: Default::default(),
3410 oldCommission: val.commission,
3411 newCommission: new_commission,
3412 }))??;
3413
3414 let validator = state.validators().get(&val.account).context("validator")?;
3415 assert_eq!(validator.commission, new_commission);
3416 },
3417 EventType::Exit => {
3418 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3419 validator: val.account,
3420 unlocksAt: U256::from(1000u64),
3421 }))??;
3422
3423 assert!(!state.validators().contains_key(&val.account));
3424 return Ok(());
3425 },
3426 EventType::X25519KeyUpdate => {
3427 let new_x25519_key = [99u8; 32];
3428 state.apply_event(val.x25519_update(new_x25519_key))??;
3429
3430 let validator = state.validators().get(&val.account).context("validator")?;
3431 let expected = x25519::PublicKey::try_from(new_x25519_key.as_slice())
3432 .expect("valid x25519 key");
3433 assert_eq!(validator.x25519_key, Some(expected));
3434 assert!(!validator.authenticated);
3435 },
3436 EventType::P2pAddrUpdate => {
3437 state.apply_event(val.p2p_update("10.0.0.1:9000"))??;
3438
3439 let validator = state.validators().get(&val.account).context("validator")?;
3440 let expected: NetAddr = "10.0.0.1:9000".parse().expect("valid p2p addr");
3441 assert_eq!(validator.p2p_addr, Some(expected));
3442 assert!(!validator.authenticated);
3443 },
3444 }
3445
3446 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
3451 match event_type {
3452 EventType::KeyUpdate => match active {
3453 Ok(map) => assert!(map.contains_key(&val.account)),
3454 Err(e) => bail!("expected validator to be selected after KeyUpdate, got {e}"),
3455 },
3456 _ => match active {
3457 Err(StakeTableError::NoValidValidators) => {},
3458 Err(e) => bail!("Unexpected error: {e}"),
3459 Ok(map) => assert!(!map.contains_key(&val.account)),
3460 },
3461 }
3462 Ok(())
3463 }
3464
3465 #[test]
3466 fn test_register_v3_sets_x25519_and_p2p() -> anyhow::Result<()> {
3467 let val = TestValidator::random();
3468
3469 let mut state = StakeTableState::default();
3470 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3471
3472 let registered = state.validators().get(&val.account).unwrap();
3473 assert!(registered.authenticated);
3474
3475 let expected_x25519 =
3476 x25519::PublicKey::try_from(val.x25519_key.as_slice()).expect("valid x25519 key");
3477 assert_eq!(registered.x25519_key, Some(expected_x25519));
3478
3479 let expected_p2p: NetAddr = val.p2p_addr.parse().expect("valid p2p addr");
3480 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3481
3482 Ok(())
3483 }
3484
3485 #[test]
3486 fn test_register_v3_invalid_sig() -> anyhow::Result<()> {
3487 let val = TestValidator::random();
3488 let other = TestValidator::random();
3489
3490 let bad_val = TestValidator {
3492 bls_sig: other.bls_sig,
3493 ..val.clone()
3494 };
3495 let event = StakeTableEvent::RegisterV3((&bad_val).into());
3496
3497 let mut state = StakeTableState::default();
3498 state.apply_event(event)??;
3499
3500 let registered = state.validators().get(&val.account).unwrap();
3501 assert!(!registered.authenticated);
3502
3503 Ok(())
3504 }
3505
3506 #[test]
3507 fn test_register_v3_hostname_p2p() -> anyhow::Result<()> {
3508 let val = TestValidator::random();
3509
3510 let mut state = StakeTableState::default();
3511 let val = val.with_p2p_addr("my-host:9000");
3514 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3515
3516 let registered = state.validators().get(&val.account).unwrap();
3517 let expected_p2p: NetAddr = "my-host:9000".parse().unwrap();
3518 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3519
3520 Ok(())
3521 }
3522
3523 #[test]
3524 fn test_register_v3_invalid_p2p_addr_degrades_to_none() -> anyhow::Result<()> {
3525 let val = TestValidator::random();
3526
3527 let mut state = StakeTableState::default();
3528 let val = val.with_p2p_addr("host:notaport");
3529 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3530
3531 let registered = state.validators().get(&val.account).unwrap();
3532 assert_eq!(registered.p2p_addr, None);
3533
3534 Ok(())
3535 }
3536
3537 #[test]
3538 fn test_x25519_key_update_sets_value() -> anyhow::Result<()> {
3539 let val = TestValidator::random();
3540
3541 let mut state = StakeTableState::default();
3542 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3543
3544 assert_eq!(
3545 state.validators().get(&val.account).unwrap().x25519_key,
3546 None
3547 );
3548
3549 let x25519_key = [99u8; 32];
3550 state.apply_event(val.x25519_update(x25519_key))??;
3551
3552 let registered = state.validators().get(&val.account).unwrap();
3553 let expected_x25519 =
3554 x25519::PublicKey::try_from(x25519_key.as_slice()).expect("valid x25519 key");
3555 assert_eq!(registered.x25519_key, Some(expected_x25519));
3556
3557 Ok(())
3558 }
3559
3560 #[test]
3561 fn test_p2p_addr_update_sets_value() -> anyhow::Result<()> {
3562 let val = TestValidator::random();
3563
3564 let mut state = StakeTableState::default();
3565 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3566
3567 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
3568
3569 let p2p_addr = "10.0.0.1:8080";
3570 state.apply_event(val.p2p_update(p2p_addr))??;
3571
3572 let registered = state.validators().get(&val.account).unwrap();
3573 let expected_p2p: NetAddr = p2p_addr.parse().expect("valid p2p addr");
3574 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3575
3576 Ok(())
3577 }
3578
3579 #[test]
3580 fn test_p2p_addr_unparsable_sets_none() -> anyhow::Result<()> {
3581 let val = TestValidator::random();
3582
3583 let mut state = StakeTableState::default();
3584 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3585
3586 state.apply_event(val.p2p_update("10.0.0.1:8080"))??;
3588 assert!(
3589 state
3590 .validators()
3591 .get(&val.account)
3592 .unwrap()
3593 .p2p_addr
3594 .is_some()
3595 );
3596
3597 state.apply_event(val.p2p_update("host:notaport"))??;
3599 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
3600
3601 Ok(())
3602 }
3603
3604 #[test]
3605 fn test_x25519_key_update_unknown_validator() {
3606 let mut state = StakeTableState::default();
3607 let unknown = TestValidator::random();
3608
3609 let result = state.apply_event(unknown.x25519_update([1u8; 32]));
3610 assert_matches!(
3611 result,
3612 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
3613 );
3614 }
3615
3616 #[test]
3617 fn test_p2p_addr_update_unknown_validator() {
3618 let mut state = StakeTableState::default();
3619 let unknown = TestValidator::random();
3620
3621 let result = state.apply_event(unknown.p2p_update("127.0.0.1:9000"));
3622 assert_matches!(
3623 result,
3624 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
3625 );
3626 }
3627
3628 #[test]
3629 fn test_x25519_key_update_duplicate() -> anyhow::Result<()> {
3630 let shared_key = [55u8; 32];
3631 let val1 = TestValidator::random().with_x25519_key(shared_key);
3632 let val2 = TestValidator::random().with_x25519_key([2u8; 32]);
3633
3634 let mut state = StakeTableState::default();
3635 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3637 state.apply_event(StakeTableEvent::RegisterV3((&val2).into()))??;
3638
3639 let result = state.apply_event(val2.x25519_update(shared_key));
3641 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
3642
3643 Ok(())
3644 }
3645
3646 #[test]
3647 fn test_p2p_addr_update_hostname() -> anyhow::Result<()> {
3648 let val = TestValidator::random();
3649
3650 let mut state = StakeTableState::default();
3651 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3652
3653 state.apply_event(val.p2p_update("my-node.example.com:9000"))??;
3654
3655 let registered = state.validators().get(&val.account).unwrap();
3656 let expected_p2p: NetAddr = "my-node.example.com:9000".parse().unwrap();
3657 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3658
3659 Ok(())
3660 }
3661
3662 #[test]
3663 fn test_register_v3_duplicate_bls_key() -> anyhow::Result<()> {
3664 let val1 = TestValidator::random();
3665 let mut val2 = TestValidator::random();
3666 val2.bls_vk = val1.bls_vk;
3667
3668 let mut state = StakeTableState::default();
3669 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3670 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3671 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
3672
3673 Ok(())
3674 }
3675
3676 #[test]
3677 fn test_register_v3_duplicate_schnorr_key() -> anyhow::Result<()> {
3678 let val1 = TestValidator::random();
3679 let mut val2 = TestValidator::random();
3680 val2.schnorr_vk = val1.schnorr_vk;
3681
3682 let mut state = StakeTableState::default();
3683 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3684 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3685 assert_matches!(result, Err(StakeTableError::SchnorrKeyAlreadyUsed(_)));
3686
3687 Ok(())
3688 }
3689
3690 #[test]
3691 fn test_register_v3_duplicate_x25519_key() -> anyhow::Result<()> {
3692 let shared_x25519 = [7u8; 32];
3693 let val1 = TestValidator::random().with_x25519_key(shared_x25519);
3694 let val2 = TestValidator::random().with_x25519_key(shared_x25519);
3695
3696 let mut state = StakeTableState::default();
3697 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
3698 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
3699 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
3700
3701 Ok(())
3702 }
3703
3704 #[test]
3705 fn test_register_v3_rejects_exited_validator() -> anyhow::Result<()> {
3706 let val = TestValidator::random();
3707
3708 let mut state = StakeTableState::default();
3709 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3710 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3711 validator: val.account,
3712 unlocksAt: U256::from(1000u64),
3713 }))??;
3714
3715 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
3716 assert_matches!(
3717 result,
3718 Err(StakeTableError::ValidatorAlreadyExited(addr)) if addr == val.account
3719 );
3720
3721 Ok(())
3722 }
3723
3724 #[test]
3725 fn test_register_v3_zero_x25519_soft_fails() -> anyhow::Result<()> {
3726 let val = TestValidator::random().with_x25519_key([0u8; 32]);
3727
3728 let mut state = StakeTableState::default();
3729 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3730
3731 let registered = state.validators().get(&val.account).unwrap();
3732 assert!(registered.x25519_key.is_none());
3733 assert!(state.used_x25519_keys().is_empty());
3734
3735 Ok(())
3736 }
3737
3738 #[test]
3739 fn test_x25519_key_update_zero_soft_fails() -> anyhow::Result<()> {
3740 let val = TestValidator::random();
3741
3742 let mut state = StakeTableState::default();
3743 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3744
3745 state.apply_event(val.x25519_update([0u8; 32]))??;
3746 let registered = state.validators().get(&val.account).unwrap();
3747 assert!(registered.x25519_key.is_none());
3748 assert_eq!(state.used_x25519_keys().len(), 1);
3749
3750 Ok(())
3751 }
3752
3753 fn noncanonical_x25519_key() -> [u8; 32] {
3756 let mut key = [0xffu8; 32];
3757 key[0] = 0xed;
3758 key[31] = 0x7f;
3759 key
3760 }
3761
3762 #[test]
3765 fn test_register_v3_noncanonical_x25519_soft_fails() -> anyhow::Result<()> {
3766 let val = TestValidator::random().with_x25519_key(noncanonical_x25519_key());
3767
3768 let mut state = StakeTableState::default();
3769 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3770
3771 let registered = state.validators().get(&val.account).unwrap();
3772 assert!(registered.x25519_key.is_none());
3773 assert!(state.used_x25519_keys().is_empty());
3774
3775 Ok(())
3776 }
3777
3778 #[test]
3781 fn test_x25519_key_update_noncanonical_soft_fails() -> anyhow::Result<()> {
3782 let val = TestValidator::random();
3783
3784 let mut state = StakeTableState::default();
3785 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3786 assert!(
3787 state
3788 .validators()
3789 .get(&val.account)
3790 .unwrap()
3791 .x25519_key
3792 .is_some()
3793 );
3794
3795 state.apply_event(val.x25519_update(noncanonical_x25519_key()))??;
3796
3797 let registered = state.validators().get(&val.account).unwrap();
3798 assert!(registered.x25519_key.is_none());
3799 assert_eq!(state.used_x25519_keys().len(), 1);
3800
3801 state.apply_event(val.x25519_update([42u8; 32]))??;
3802 let registered = state.validators().get(&val.account).unwrap();
3803 assert!(registered.x25519_key.is_some());
3804 assert_eq!(state.used_x25519_keys().len(), 2);
3805
3806 Ok(())
3807 }
3808
3809 #[test]
3813 fn test_state_commit_includes_used_x25519_keys() -> anyhow::Result<()> {
3814 let val = TestValidator::random();
3815
3816 let mut state_without = StakeTableState::default();
3817 state_without.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3818
3819 let mut state_with = state_without.clone();
3820 state_with
3821 .used_x25519_keys
3822 .insert(x25519::PublicKey::try_from([7u8; 32].as_slice()).unwrap());
3823
3824 assert_ne!(state_without.commit(), state_with.commit());
3825 Ok(())
3826 }
3827
3828 fn complete_mock_validator() -> RegisteredValidator<BLSPubKey> {
3832 let mut v = RegisteredValidator::<BLSPubKey>::mock();
3833 v.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
3834 v.p2p_addr = Some("127.0.0.1:9000".parse().unwrap());
3835 v
3836 }
3837
3838 #[test]
3840 fn test_select_pre_upgrade_includes_validators_missing_network_info() {
3841 let mut map = RegisteredValidatorMap::new();
3842
3843 let incomplete = RegisteredValidator::<BLSPubKey>::mock();
3844 assert!(incomplete.x25519_key.is_none());
3845 assert!(incomplete.p2p_addr.is_none());
3846 map.insert(incomplete.account, incomplete.clone());
3847
3848 let complete = complete_mock_validator();
3849 map.insert(complete.account, complete.clone());
3850
3851 let active = select_active_validator_set(&map, EPOCH_VERSION).unwrap();
3852 assert!(active.contains_key(&incomplete.account));
3853 assert!(active.contains_key(&complete.account));
3854 }
3855
3856 #[test]
3858 fn test_select_post_upgrade_excludes_missing_x25519() {
3859 let mut map = RegisteredValidatorMap::new();
3860
3861 let mut missing_x25519 = complete_mock_validator();
3862 missing_x25519.x25519_key = None;
3863 map.insert(missing_x25519.account, missing_x25519.clone());
3864
3865 let complete = complete_mock_validator();
3866 map.insert(complete.account, complete.clone());
3867
3868 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3869 assert!(!active.contains_key(&missing_x25519.account));
3870 assert!(active.contains_key(&complete.account));
3871 }
3872
3873 #[test]
3875 fn test_select_post_upgrade_excludes_missing_p2p() {
3876 let mut map = RegisteredValidatorMap::new();
3877
3878 let mut missing_p2p = complete_mock_validator();
3879 missing_p2p.p2p_addr = None;
3880 map.insert(missing_p2p.account, missing_p2p.clone());
3881
3882 let complete = complete_mock_validator();
3883 map.insert(complete.account, complete.clone());
3884
3885 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3886 assert!(!active.contains_key(&missing_p2p.account));
3887 assert!(active.contains_key(&complete.account));
3888 }
3889
3890 #[test]
3892 fn test_select_post_upgrade_keeps_complete() {
3893 let mut map = RegisteredValidatorMap::new();
3894 let complete = complete_mock_validator();
3895 map.insert(complete.account, complete.clone());
3896
3897 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3898 assert!(active.contains_key(&complete.account));
3899 }
3900
3901 #[test]
3904 fn test_select_post_upgrade_all_incomplete_fails() {
3905 let mut map = RegisteredValidatorMap::new();
3906 for _ in 0..5 {
3907 let v = RegisteredValidator::<BLSPubKey>::mock();
3908 map.insert(v.account, v);
3909 }
3910
3911 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
3912 assert_matches!(result, Err(StakeTableError::NoValidValidators));
3913 }
3914
3915 #[test]
3919 fn test_select_post_upgrade_top_n_only_complete() {
3920 let mut map = RegisteredValidatorMap::new();
3921 let mut complete_accounts = HashSet::new();
3922 let mut incomplete_accounts = HashSet::new();
3923
3924 for i in 0..(MAX_VALIDATORS + 50) {
3928 if i % 2 == 0 {
3929 let mut v = complete_mock_validator();
3930 v.stake = U256::from(1_000_000_000_u64 + i as u64);
3931 complete_accounts.insert(v.account);
3932 map.insert(v.account, v);
3933 } else {
3934 let mut v = RegisteredValidator::<BLSPubKey>::mock();
3935 v.stake = U256::from(10_u64);
3936 incomplete_accounts.insert(v.account);
3937 map.insert(v.account, v);
3938 }
3939 }
3940
3941 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
3942
3943 assert!(
3944 active.len() <= MAX_VALIDATORS,
3945 "active has {} validators, expected at most {}",
3946 active.len(),
3947 MAX_VALIDATORS
3948 );
3949 for addr in active.keys() {
3950 assert!(
3951 complete_accounts.contains(addr),
3952 "incomplete validator {addr:?} ended up in active set"
3953 );
3954 assert!(!incomplete_accounts.contains(addr));
3955 }
3956 }
3957
3958 #[test]
3962 fn test_select_version_boundary() {
3963 let mut v = complete_mock_validator();
3964 v.x25519_key = None;
3965 let mut map = RegisteredValidatorMap::new();
3966 map.insert(v.account, v.clone());
3967
3968 for protocol_version in [
3969 EPOCH_VERSION,
3970 DRB_AND_HEADER_UPGRADE_VERSION,
3971 EPOCH_REWARD_VERSION,
3972 ] {
3973 let active = select_active_validator_set(&map, protocol_version).unwrap();
3974 assert!(
3975 active.contains_key(&v.account),
3976 "missing x25519 validator should be included at protocol version \
3977 {protocol_version}",
3978 );
3979 }
3980
3981 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
3982 assert_matches!(result, Err(StakeTableError::NoValidValidators));
3983 }
3984
3985 #[test]
3988 fn test_p2p_parse_fail_pre_upgrade_included_post_upgrade_excluded() -> anyhow::Result<()> {
3989 let val = TestValidator::random().with_p2p_addr("host:notaport");
3990
3991 let mut state = StakeTableState::default();
3992 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3993 state.apply_event(StakeTableEvent::Delegate(Delegated {
3995 delegator: Address::random(),
3996 validator: val.account,
3997 amount: U256::from(100),
3998 }))??;
3999
4000 let registered = state.validators().get(&val.account).unwrap();
4001 assert_eq!(registered.p2p_addr, None);
4002
4003 let pre = select_active_validator_set(state.validators(), EPOCH_VERSION).unwrap();
4005 assert!(pre.contains_key(&val.account));
4006
4007 let post = select_active_validator_set(state.validators(), NEW_PROTOCOL_VERSION);
4009 assert_matches!(post, Err(StakeTableError::NoValidValidators));
4010
4011 Ok(())
4012 }
4013
4014 #[test]
4015 fn test_register_v1_zero_g2_excluded_from_active_set() {
4016 let account = Address::random();
4017 let mut state = StakeTableState::default();
4018 state
4019 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4020 account,
4021 blsVk: zero_g2(),
4022 schnorrVk: StateKeyPair::generate().ver_key().into(),
4023 commission: 0,
4024 }))
4025 .unwrap()
4026 .unwrap();
4027
4028 let v = state.validators().get(&account).expect("present");
4029 assert!(v.stake_table_key.is_none());
4030 assert!(!v.authenticated);
4031
4032 state
4033 .apply_event(StakeTableEvent::Delegate(Delegated {
4034 delegator: Address::random(),
4035 validator: account,
4036 amount: U256::from(100),
4037 }))
4038 .unwrap()
4039 .unwrap();
4040
4041 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4042 Err(StakeTableError::NoValidValidators) => {},
4043 Ok(map) => assert!(map.get(&account).is_none()),
4044 Err(e) => panic!("unexpected error: {e}"),
4045 }
4046 }
4047
4048 #[test]
4049 fn test_register_v2_zero_g2_excluded_from_active_set() {
4050 let account = Address::random();
4051 let mut state = StakeTableState::default();
4052 state
4053 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4054 account,
4055 blsVK: zero_g2(),
4056 schnorrVK: StateKeyPair::generate().ver_key().into(),
4057 commission: 0,
4058 blsSig: zero_g1().into(),
4059 schnorrSig: Bytes::default(),
4060 metadataUri: String::new(),
4061 }))
4062 .unwrap()
4063 .unwrap();
4064
4065 let v = state.validators().get(&account).expect("present");
4066 assert!(v.stake_table_key.is_none());
4067 assert!(!v.authenticated);
4068
4069 state
4070 .apply_event(StakeTableEvent::Delegate(Delegated {
4071 delegator: Address::random(),
4072 validator: account,
4073 amount: U256::from(100),
4074 }))
4075 .unwrap()
4076 .unwrap();
4077
4078 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4079 Err(StakeTableError::NoValidValidators) => {},
4080 Ok(map) => assert!(map.get(&account).is_none()),
4081 Err(e) => panic!("unexpected error: {e}"),
4082 }
4083 }
4084
4085 #[test]
4086 fn test_register_v3_zero_g2_excluded_from_active_set() {
4087 let val = TestValidator::random();
4088 let account = val.account;
4089 let mut state = StakeTableState::default();
4090 state
4091 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
4092 &val,
4093 zero_g2(),
4094 )))
4095 .unwrap()
4096 .unwrap();
4097
4098 let v = state.validators().get(&account).expect("present");
4099 assert!(v.stake_table_key.is_none());
4100 assert!(!v.authenticated);
4101
4102 state
4103 .apply_event(StakeTableEvent::Delegate(Delegated {
4104 delegator: Address::random(),
4105 validator: account,
4106 amount: U256::from(100),
4107 }))
4108 .unwrap()
4109 .unwrap();
4110
4111 assert_excluded_from_active_set(&state, account);
4112 }
4113
4114 fn zero_schnorr() -> hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4115 hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4116 x: U256::ZERO,
4117 y: U256::ZERO,
4118 }
4119 }
4120
4121 fn identity_schnorr() -> hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4124 hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4125 x: U256::ZERO,
4126 y: U256::from(1),
4127 }
4128 }
4129
4130 fn register_v3_with_bls(val: &TestValidator, bls: G2PointSol) -> ValidatorRegisteredV3 {
4134 let mut reg = ValidatorRegisteredV3::from(val);
4135 reg.blsVK = bls;
4136 reg
4137 }
4138
4139 fn assert_excluded_from_active_set(state: &StakeTableState, account: Address) {
4141 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4142 Err(StakeTableError::NoValidValidators) => {},
4143 Ok(map) => assert!(map.get(&account).is_none()),
4144 Err(e) => panic!("unexpected error: {e}"),
4145 }
4146 }
4147
4148 #[test]
4149 fn test_register_v1_identity_schnorr_excluded_from_active_set() {
4150 let account = Address::random();
4151 let bls: G2PointSol = BLSPubKey::generated_from_seed_indexed([8u8; 32], 0)
4152 .0
4153 .into();
4154 let mut state = StakeTableState::default();
4155 state
4156 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4157 account,
4158 blsVk: bls,
4159 schnorrVk: identity_schnorr(),
4160 commission: 0,
4161 }))
4162 .unwrap()
4163 .unwrap();
4164
4165 let v = state.validators().get(&account).expect("present");
4166 assert!(
4167 !v.authenticated,
4168 "validator with identity schnorr key must be unauthenticated"
4169 );
4170 assert!(v.state_ver_key.is_none());
4171
4172 state
4173 .apply_event(StakeTableEvent::Delegate(Delegated {
4174 delegator: Address::random(),
4175 validator: account,
4176 amount: U256::from(100),
4177 }))
4178 .unwrap()
4179 .unwrap();
4180
4181 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4182 Err(StakeTableError::NoValidValidators) => {},
4183 Ok(map) => assert!(map.get(&account).is_none()),
4184 Err(e) => panic!("unexpected error: {e}"),
4185 }
4186 }
4187
4188 #[test]
4189 fn test_register_v1_invalid_schnorr_excluded_from_active_set() {
4190 let account = Address::random();
4191 let bls: G2PointSol = BLSPubKey::generated_from_seed_indexed([9u8; 32], 0)
4192 .0
4193 .into();
4194 let mut state = StakeTableState::default();
4195 state
4196 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4197 account,
4198 blsVk: bls,
4199 schnorrVk: zero_schnorr(),
4200 commission: 0,
4201 }))
4202 .unwrap()
4203 .unwrap();
4204
4205 let v = state.validators().get(&account).expect("present");
4206 assert!(
4207 !v.authenticated,
4208 "validator with zero schnorr key must be unauthenticated"
4209 );
4210
4211 state
4212 .apply_event(StakeTableEvent::Delegate(Delegated {
4213 delegator: Address::random(),
4214 validator: account,
4215 amount: U256::from(100),
4216 }))
4217 .unwrap()
4218 .unwrap();
4219
4220 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4221 Err(StakeTableError::NoValidValidators) => {},
4222 Ok(map) => assert!(map.get(&account).is_none()),
4223 Err(e) => panic!("unexpected error: {e}"),
4224 }
4225 }
4226
4227 #[test]
4228 fn test_register_v3_identity_schnorr_excluded_from_active_set() {
4229 let val = TestValidator::random();
4230 let account = val.account;
4231 let mut reg = ValidatorRegisteredV3::from(&val);
4232 reg.schnorrVK = identity_schnorr();
4233 let mut state = StakeTableState::default();
4234 state
4235 .apply_event(StakeTableEvent::RegisterV3(reg))
4236 .unwrap()
4237 .unwrap();
4238
4239 let v = state.validators().get(&account).expect("present");
4240 assert!(
4241 !v.authenticated,
4242 "validator with identity schnorr key must be unauthenticated"
4243 );
4244 assert!(v.state_ver_key.is_none());
4245
4246 state
4247 .apply_event(StakeTableEvent::Delegate(Delegated {
4248 delegator: Address::random(),
4249 validator: account,
4250 amount: U256::from(100),
4251 }))
4252 .unwrap()
4253 .unwrap();
4254
4255 assert_excluded_from_active_set(&state, account);
4256 }
4257
4258 #[test]
4259 fn test_register_v3_invalid_schnorr_excluded_from_active_set() {
4260 let val = TestValidator::random();
4261 let account = val.account;
4262 let mut reg = ValidatorRegisteredV3::from(&val);
4263 reg.schnorrVK = zero_schnorr();
4264 let mut state = StakeTableState::default();
4265 state
4266 .apply_event(StakeTableEvent::RegisterV3(reg))
4267 .unwrap()
4268 .unwrap();
4269
4270 let v = state.validators().get(&account).expect("present");
4271 assert!(
4272 !v.authenticated,
4273 "validator with zero schnorr key must be unauthenticated"
4274 );
4275 assert!(v.state_ver_key.is_none());
4276
4277 state
4278 .apply_event(StakeTableEvent::Delegate(Delegated {
4279 delegator: Address::random(),
4280 validator: account,
4281 amount: U256::from(100),
4282 }))
4283 .unwrap()
4284 .unwrap();
4285
4286 assert_excluded_from_active_set(&state, account);
4287 }
4288
4289 #[test]
4290 fn test_zero_bls_key_register_unauthenticated() {
4291 let account = Address::random();
4292 let schnorr_key_pair = StateKeyPair::generate();
4293 let mut state = StakeTableState::default();
4294
4295 let reg_v2 = ValidatorRegisteredV2 {
4296 account,
4297 blsVK: zero_g2(),
4298 schnorrVK: schnorr_key_pair.ver_key().into(),
4299 commission: 0,
4300 blsSig: zero_g1().into(),
4301 schnorrSig: Bytes::default(),
4302 metadataUri: String::new(),
4303 };
4304 state
4305 .apply_event(StakeTableEvent::RegisterV2(reg_v2))
4306 .expect("no fatal error")
4307 .expect("registered as unauthenticated");
4308 let registered = state.validators().get(&account).expect("present");
4309 assert!(!registered.authenticated);
4310 assert!(registered.stake_table_key.is_none());
4311 assert!(AuthenticatedValidator::try_from(registered).is_err());
4312
4313 let account_v1 = Address::random();
4315 let schnorr_v1 = StateKeyPair::generate();
4316 let mut state_v1 = StakeTableState::default();
4317 let reg_v1 = ValidatorRegistered {
4318 account: account_v1,
4319 blsVk: zero_g2(),
4320 schnorrVk: schnorr_v1.ver_key().into(),
4321 commission: 0,
4322 };
4323 state_v1
4324 .apply_event(StakeTableEvent::Register(reg_v1))
4325 .expect("no fatal error")
4326 .expect("registered as unauthenticated");
4327 let registered_v1 = state_v1.validators().get(&account_v1).expect("present");
4328 assert!(!registered_v1.authenticated);
4329 assert!(registered_v1.stake_table_key.is_none());
4330
4331 let valid_val = TestValidator::random();
4333 state
4334 .apply_event(StakeTableEvent::RegisterV2((&valid_val).into()))
4335 .unwrap()
4336 .unwrap();
4337 let prior_key_v2 = state
4338 .validators()
4339 .get(&valid_val.account)
4340 .unwrap()
4341 .stake_table_key;
4342 let update = ConsensusKeysUpdatedV2 {
4343 account: valid_val.account,
4344 blsVK: zero_g2(),
4345 schnorrVK: schnorr_key_pair.ver_key().into(),
4346 blsSig: zero_g1().into(),
4347 schnorrSig: Bytes::default(),
4348 };
4349 assert_matches!(
4350 state.apply_event(StakeTableEvent::KeyUpdateV2(update)),
4351 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4352 );
4353 assert_eq!(
4354 state
4355 .validators()
4356 .get(&valid_val.account)
4357 .unwrap()
4358 .stake_table_key,
4359 prior_key_v2
4360 );
4361
4362 let valid_v1 = TestValidator::random();
4364 state_v1
4365 .apply_event(StakeTableEvent::Register((&valid_v1).into()))
4366 .unwrap()
4367 .unwrap();
4368 let prior_key = state_v1
4369 .validators()
4370 .get(&valid_v1.account)
4371 .unwrap()
4372 .stake_table_key;
4373 let update_v1 = ConsensusKeysUpdated {
4374 account: valid_v1.account,
4375 blsVK: zero_g2(),
4376 schnorrVK: schnorr_key_pair.ver_key().into(),
4377 };
4378 assert_matches!(
4379 state_v1.apply_event(StakeTableEvent::KeyUpdate(update_v1)),
4380 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4381 );
4382 assert_eq!(
4383 state_v1
4384 .validators()
4385 .get(&valid_v1.account)
4386 .unwrap()
4387 .stake_table_key,
4388 prior_key
4389 );
4390 }
4391
4392 #[test]
4396 fn test_zero_bls_key_registered_unauthenticated_in_from_l1_events() {
4397 let valid_val = TestValidator::random();
4398 let poison_account = Address::random();
4399 let schnorr_key_pair = StateKeyPair::generate();
4400
4401 let events = vec![
4402 StakeTableEvent::RegisterV2((&valid_val).into()),
4403 StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4404 account: poison_account,
4405 blsVK: zero_g2(),
4406 schnorrVK: schnorr_key_pair.ver_key().into(),
4407 commission: 0,
4408 blsSig: zero_g1().into(),
4409 schnorrSig: Bytes::default(),
4410 metadataUri: String::new(),
4411 }),
4412 StakeTableEvent::Delegate(Delegated {
4413 delegator: Address::random(),
4414 validator: poison_account,
4415 amount: U256::from(10),
4416 }),
4417 ];
4418
4419 let (validators, _) = validators_from_l1_events(events.into_iter()).expect("must not fail");
4420 let valid = validators
4421 .get(&valid_val.account)
4422 .expect("valid validator present");
4423 assert!(valid.authenticated);
4424
4425 let poisoned = validators
4426 .get(&poison_account)
4427 .expect("poison validator present as unauthenticated");
4428 assert!(!poisoned.authenticated);
4429 assert!(poisoned.stake_table_key.is_none());
4430 assert_eq!(poisoned.stake, U256::from(10));
4431 assert!(AuthenticatedValidator::try_from(poisoned).is_err());
4432 }
4433
4434 #[test]
4435 fn test_register_v3_zero_bls_key_unauthenticated_in_from_l1_events() {
4436 let valid_val = TestValidator::random();
4437 let poison_val = TestValidator::random();
4438 let poison_account = poison_val.account;
4439
4440 let events = vec![
4441 StakeTableEvent::RegisterV3((&valid_val).into()),
4442 StakeTableEvent::RegisterV3(register_v3_with_bls(&poison_val, zero_g2())),
4443 StakeTableEvent::Delegate(Delegated {
4444 delegator: Address::random(),
4445 validator: poison_account,
4446 amount: U256::from(10),
4447 }),
4448 ];
4449
4450 let (validators, _) = validators_from_l1_events(events.into_iter()).expect("must not fail");
4451 let valid = validators
4452 .get(&valid_val.account)
4453 .expect("valid validator present");
4454 assert!(valid.authenticated);
4455
4456 let poisoned = validators
4457 .get(&poison_account)
4458 .expect("poison validator present as unauthenticated");
4459 assert!(!poisoned.authenticated);
4460 assert!(poisoned.stake_table_key.is_none());
4461 assert_eq!(poisoned.stake, U256::from(10));
4462 assert!(AuthenticatedValidator::try_from(poisoned).is_err());
4463 }
4464
4465 #[test]
4468 fn test_two_distinct_unparsable_g2_keys_both_register() {
4469 let zero = zero_g2();
4470 let nonzero = G2PointSol {
4472 x0: U256::from(1),
4473 x1: U256::ZERO,
4474 y0: U256::ZERO,
4475 y1: U256::ZERO,
4476 };
4477 assert!(BLSPubKey::try_from(zero).is_err());
4478 assert!(BLSPubKey::try_from(nonzero).is_err());
4479 assert_ne!(zero, nonzero);
4480
4481 let acc_a = Address::random();
4482 let acc_b = Address::random();
4483
4484 let mut state = StakeTableState::default();
4485 state
4486 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4487 account: acc_a,
4488 blsVK: zero,
4489 schnorrVK: StateKeyPair::generate().ver_key().into(),
4490 commission: 0,
4491 blsSig: zero_g1().into(),
4492 schnorrSig: Bytes::default(),
4493 metadataUri: String::new(),
4494 }))
4495 .expect("first unparsable register must not fail")
4496 .expect("first unparsable register must apply");
4497 state
4498 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4499 account: acc_b,
4500 blsVK: nonzero,
4501 schnorrVK: StateKeyPair::generate().ver_key().into(),
4502 commission: 0,
4503 blsSig: zero_g1().into(),
4504 schnorrSig: Bytes::default(),
4505 metadataUri: String::new(),
4506 }))
4507 .expect("second unparsable register must not fail")
4508 .expect("second unparsable register must apply");
4509
4510 for account in [acc_a, acc_b] {
4511 let v = state.validators().get(&account).expect("present");
4512 assert!(!v.authenticated);
4513 assert!(v.stake_table_key.is_none());
4514 }
4515 assert!(state.used_bls_keys().is_empty());
4516 }
4517
4518 #[test]
4522 fn test_register_v3_two_distinct_unparsable_g2_keys_both_register() {
4523 let zero = zero_g2();
4524 let nonzero = G2PointSol {
4526 x0: U256::from(1),
4527 x1: U256::ZERO,
4528 y0: U256::ZERO,
4529 y1: U256::ZERO,
4530 };
4531 assert!(BLSPubKey::try_from(zero).is_err());
4532 assert!(BLSPubKey::try_from(nonzero).is_err());
4533 assert_ne!(zero, nonzero);
4534
4535 let val_a = TestValidator::random();
4536 let val_b = TestValidator::random();
4537 let acc_a = val_a.account;
4538 let acc_b = val_b.account;
4539
4540 let mut state = StakeTableState::default();
4541 state
4542 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
4543 &val_a, zero,
4544 )))
4545 .expect("first unparsable register must not fail")
4546 .expect("first unparsable register must apply");
4547 state
4548 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
4549 &val_b, nonzero,
4550 )))
4551 .expect("second unparsable register must not fail")
4552 .expect("second unparsable register must apply");
4553
4554 for account in [acc_a, acc_b] {
4555 let v = state.validators().get(&account).expect("present");
4556 assert!(!v.authenticated);
4557 assert!(v.stake_table_key.is_none());
4558 }
4559 assert!(state.used_bls_keys().is_empty());
4560 }
4561
4562 #[test]
4565 fn test_zero_g2_then_valid_key_update() {
4566 let mut state = StakeTableState::default();
4567 let valid_val = TestValidator::random();
4568 let account = valid_val.account;
4569
4570 state
4571 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4572 account,
4573 blsVK: zero_g2(),
4574 schnorrVK: StateKeyPair::generate().ver_key().into(),
4575 commission: 0,
4576 blsSig: zero_g1().into(),
4577 schnorrSig: Bytes::default(),
4578 metadataUri: String::new(),
4579 }))
4580 .unwrap()
4581 .unwrap();
4582 assert!(!state.validators().get(&account).unwrap().authenticated);
4583
4584 let new_keys = TestValidator::random_update_keys(account, 0);
4585 let update: ConsensusKeysUpdatedV2 = (&new_keys).into();
4586 state
4587 .apply_event(StakeTableEvent::KeyUpdateV2(update))
4588 .unwrap()
4589 .unwrap();
4590
4591 let v = state.validators().get(&account).unwrap();
4592 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).unwrap();
4593 assert_eq!(v.stake_table_key.as_ref(), Some(&expected_bls));
4594 assert!(v.authenticated);
4597 }
4598
4599 #[test]
4600 fn test_register_v3_zero_g2_then_valid_key_update() {
4601 let val = TestValidator::random();
4602 let account = val.account;
4603 let mut state = StakeTableState::default();
4604
4605 state
4606 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
4607 &val,
4608 zero_g2(),
4609 )))
4610 .unwrap()
4611 .unwrap();
4612 assert!(!state.validators().get(&account).unwrap().authenticated);
4613
4614 let new_keys = TestValidator::random_update_keys(account, 0);
4615 let update: ConsensusKeysUpdatedV2 = (&new_keys).into();
4616 state
4617 .apply_event(StakeTableEvent::KeyUpdateV2(update))
4618 .unwrap()
4619 .unwrap();
4620
4621 let v = state.validators().get(&account).unwrap();
4622 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).unwrap();
4623 assert_eq!(v.stake_table_key.as_ref(), Some(&expected_bls));
4624 assert!(v.authenticated);
4627 }
4628
4629 #[test]
4630 fn test_key_update_bad_key_no_corruption() {
4631 let mut state_v1 = StakeTableState::default();
4633 let val = TestValidator::random();
4634 state_v1
4635 .apply_event(StakeTableEvent::Register((&val).into()))
4636 .unwrap()
4637 .unwrap();
4638 let prior_v1 = state_v1
4639 .validators()
4640 .get(&val.account)
4641 .unwrap()
4642 .stake_table_key;
4643 assert_matches!(
4644 state_v1.apply_event(StakeTableEvent::KeyUpdate(ConsensusKeysUpdated {
4645 account: val.account,
4646 blsVK: zero_g2(),
4647 schnorrVK: StateKeyPair::generate().ver_key().into(),
4648 })),
4649 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4650 );
4651 assert_eq!(
4652 state_v1
4653 .validators()
4654 .get(&val.account)
4655 .unwrap()
4656 .stake_table_key,
4657 prior_v1
4658 );
4659
4660 let mut state_v2 = StakeTableState::default();
4662 let val2 = TestValidator::random();
4663 state_v2
4664 .apply_event(StakeTableEvent::RegisterV2((&val2).into()))
4665 .unwrap()
4666 .unwrap();
4667 let prior_v2 = state_v2
4668 .validators()
4669 .get(&val2.account)
4670 .unwrap()
4671 .stake_table_key;
4672 assert_matches!(
4673 state_v2.apply_event(StakeTableEvent::KeyUpdateV2(ConsensusKeysUpdatedV2 {
4674 account: val2.account,
4675 blsVK: zero_g2(),
4676 schnorrVK: StateKeyPair::generate().ver_key().into(),
4677 blsSig: zero_g1().into(),
4678 schnorrSig: Bytes::default(),
4679 })),
4680 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4681 );
4682 assert_eq!(
4683 state_v2
4684 .validators()
4685 .get(&val2.account)
4686 .unwrap()
4687 .stake_table_key,
4688 prior_v2
4689 );
4690 }
4691}
4692
4693#[cfg(test)]
4697mod proptest_x25519_key {
4698 use alloy::{
4699 primitives::FixedBytes,
4700 providers::{ProviderBuilder, WalletProvider},
4701 };
4702 use proptest::{
4703 prelude::*,
4704 test_runner::{Config as ProptestConfig, TestRunner},
4705 };
4706
4707 use super::{testing::TestValidator, *};
4708
4709 fn x25519_key_strategy() -> impl Strategy<Value = [u8; 32]> {
4710 let mut p = [0xffu8; 32];
4712 p[0] = 0xed;
4713 p[31] = 0x7f;
4714 prop_oneof![
4715 any::<[u8; 32]>(),
4717 Just([0u8; 32]),
4719 Just(p),
4721 (0..19u8).prop_map(move |k| {
4723 let mut key = p;
4724 key[0] += k;
4725 key
4726 }),
4727 Just({
4729 let mut key = p;
4730 key[0] = 0xec;
4731 key
4732 }),
4733 Just([0xffu8; 32]),
4736 Just({
4738 let mut key = [0u8; 32];
4739 key[31] = 0x80;
4740 key
4741 }),
4742 (0..=2u8).prop_map(|v| {
4744 let mut key = [0u8; 32];
4745 key[0] = v;
4746 key
4747 }),
4748 ]
4749 }
4750
4751 #[test]
4752 fn solidity_rust_x25519_validation_equivalence() {
4753 let rt = tokio::runtime::Runtime::new().unwrap();
4754
4755 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
4756 let sender = provider.default_signer_address();
4757 let contract_addr = rt.block_on(async {
4758 let contract = StakeTableV3::deploy(&provider).await.unwrap();
4759 *contract.address()
4760 });
4761 let contract = StakeTableV3::new(contract_addr, &provider);
4762
4763 let registered = TestValidator::random_update_keys(sender, 100).with_x25519_key([7u8; 32]);
4766 rt.block_on(async {
4767 let receipt = contract
4768 .registerValidatorV3(
4769 registered.bls_vk,
4770 registered.schnorr_vk,
4771 registered.bls_sig.into(),
4772 registered.schnorr_sig.clone(),
4773 registered.commission,
4774 String::new(),
4775 FixedBytes(registered.x25519_key),
4776 registered.p2p_addr.clone(),
4777 )
4778 .send()
4779 .await
4780 .unwrap()
4781 .get_receipt()
4782 .await
4783 .unwrap();
4784 assert!(receipt.status());
4785 });
4786
4787 let mut registered_state = StakeTableState::default();
4789 registered_state
4790 .apply_event(StakeTableEvent::RegisterV3((®istered).into()))
4791 .unwrap()
4792 .unwrap();
4793
4794 let base = TestValidator::random();
4795
4796 let cases = std::env::var("PROPTEST_CASES")
4797 .ok()
4798 .and_then(|v| v.parse().ok())
4799 .unwrap_or(512);
4800 let mut runner = TestRunner::new(ProptestConfig {
4801 cases,
4802 ..ProptestConfig::default()
4803 });
4804
4805 runner
4807 .run(&x25519_key_strategy(), |key| {
4808 let sol_valid = rt.block_on(async {
4809 contract
4810 .registerValidatorV3(
4811 base.bls_vk,
4812 base.schnorr_vk,
4813 base.bls_sig.into(),
4814 base.schnorr_sig.clone(),
4815 base.commission,
4816 String::new(),
4817 FixedBytes(key),
4818 base.p2p_addr.clone(),
4819 )
4820 .from(base.account)
4821 .call()
4822 .await
4823 .is_ok()
4824 });
4825
4826 let val = base.clone().with_x25519_key(key);
4827 let mut state = StakeTableState::default();
4828 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
4829
4830 if sol_valid {
4831 prop_assert!(
4832 result.is_ok(),
4833 "Solidity accepted registration with x25519 key {key:?} but apply_event \
4834 failed: {result:?}"
4835 );
4836 let stored = state.validators().get(&val.account).unwrap().x25519_key;
4837 prop_assert!(stored.is_some());
4840 prop_assert_eq!(stored, parse_x25519_key(key).ok());
4841 }
4842 Ok(())
4843 })
4844 .unwrap();
4845
4846 runner
4848 .run(&x25519_key_strategy(), |key| {
4849 let sol_valid = rt.block_on(async {
4850 contract
4851 .updateX25519Key(FixedBytes(key))
4852 .from(sender)
4853 .call()
4854 .await
4855 .is_ok()
4856 });
4857
4858 let mut state = registered_state.clone();
4859 let result = state.apply_event(registered.x25519_update(key));
4860
4861 if sol_valid {
4862 prop_assert!(
4863 result.is_ok(),
4864 "Solidity accepted x25519 key update {key:?} but apply_event failed: \
4865 {result:?}"
4866 );
4867 let stored = state.validators().get(&sender).unwrap().x25519_key;
4868 prop_assert!(stored.is_some());
4869 prop_assert_eq!(stored, parse_x25519_key(key).ok());
4870 }
4871 Ok(())
4872 })
4873 .unwrap();
4874 }
4875}