1use std::{
2 cmp::{max, 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, address},
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::{
88 v0_1::{ChainId, DECAF_CHAIN_ID, MAINNET_CHAIN_ID},
89 v0_3::{
90 COMMISSION_BASIS_POINTS, EventSortingError, ExpectedStakeTableError, FetchRewardError,
91 MILLISECONDS_PER_YEAR, RewardAmount, StakeTableError,
92 },
93};
94
95pub type RegisteredValidatorMap = IndexMap<Address, RegisteredValidator<BLSPubKey>>;
96pub type AuthenticatedValidatorMap = IndexMap<Address, AuthenticatedValidator<BLSPubKey>>;
97
98pub fn to_registered_validator_map(
99 validators: &AuthenticatedValidatorMap,
100) -> RegisteredValidatorMap {
101 validators
102 .iter()
103 .map(|(addr, v)| (*addr, v.clone().into()))
104 .collect()
105}
106
107pub type StakeTableHash = Commitment<StakeTableState>;
108
109type ApplyEventResult<T> = Result<Result<T, ExpectedStakeTableError>, StakeTableError>;
114
115#[cfg_attr(not(feature = "node"), allow(dead_code))]
117trait DisplayLog {
118 fn display(&self) -> String;
119}
120
121impl DisplayLog for Log {
122 fn display(&self) -> String {
123 let block = self.block_number.unwrap_or_default();
127 let index = self.log_index.unwrap_or_default();
128 let hash = self.transaction_hash.unwrap_or_default();
129 format!("Log(block={block},index={index},transaction_hash={hash})")
130 }
131}
132
133impl TryFrom<StakeTableV3Events> for StakeTableEvent {
134 type Error = anyhow::Error;
135
136 fn try_from(value: StakeTableV3Events) -> anyhow::Result<Self> {
137 match value {
138 StakeTableV3Events::ValidatorRegistered(v) => Ok(StakeTableEvent::Register(v)),
139 StakeTableV3Events::ValidatorRegisteredV2(v) => Ok(StakeTableEvent::RegisterV2(v)),
140 StakeTableV3Events::ValidatorRegisteredV3(v) => Ok(StakeTableEvent::RegisterV3(v)),
141 StakeTableV3Events::ValidatorExit(v) => Ok(StakeTableEvent::Deregister(v)),
142 StakeTableV3Events::ValidatorExitV2(v) => Ok(StakeTableEvent::DeregisterV2(v)),
143 StakeTableV3Events::Delegated(v) => Ok(StakeTableEvent::Delegate(v)),
144 StakeTableV3Events::Undelegated(v) => Ok(StakeTableEvent::Undelegate(v)),
145 StakeTableV3Events::UndelegatedV2(v) => Ok(StakeTableEvent::UndelegateV2(v)),
146 StakeTableV3Events::ConsensusKeysUpdated(v) => Ok(StakeTableEvent::KeyUpdate(v)),
147 StakeTableV3Events::ConsensusKeysUpdatedV2(v) => Ok(StakeTableEvent::KeyUpdateV2(v)),
148 StakeTableV3Events::CommissionUpdated(v) => Ok(StakeTableEvent::CommissionUpdate(v)),
149 StakeTableV3Events::X25519KeyUpdated(v) => Ok(StakeTableEvent::X25519KeyUpdate(v)),
150 StakeTableV3Events::P2pAddrUpdated(v) => Ok(StakeTableEvent::P2pAddrUpdate(v)),
151 StakeTableV3Events::ExitEscrowPeriodUpdated(v) => Err(anyhow::anyhow!(
152 "Unsupported StakeTableV3Events::ExitEscrowPeriodUpdated({v:?})"
153 )),
154 StakeTableV3Events::Initialized(v) => Err(anyhow::anyhow!(
155 "Unsupported StakeTableV3Events::Initialized({v:?})"
156 )),
157 StakeTableV3Events::MaxCommissionIncreaseUpdated(v) => Err(anyhow::anyhow!(
158 "Unsupported StakeTableV3Events::MaxCommissionIncreaseUpdated({v:?})"
159 )),
160 StakeTableV3Events::MinCommissionUpdateIntervalUpdated(v) => Err(anyhow::anyhow!(
161 "Unsupported StakeTableV3Events::MinCommissionUpdateIntervalUpdated({v:?})"
162 )),
163 StakeTableV3Events::OwnershipTransferred(v) => Err(anyhow::anyhow!(
164 "Unsupported StakeTableV3Events::OwnershipTransferred({v:?})"
165 )),
166 StakeTableV3Events::Paused(v) => Err(anyhow::anyhow!(
167 "Unsupported StakeTableV3Events::Paused({v:?})"
168 )),
169 StakeTableV3Events::RoleAdminChanged(v) => Err(anyhow::anyhow!(
170 "Unsupported StakeTableV3Events::RoleAdminChanged({v:?})"
171 )),
172 StakeTableV3Events::RoleGranted(v) => Err(anyhow::anyhow!(
173 "Unsupported StakeTableV3Events::RoleGranted({v:?})"
174 )),
175 StakeTableV3Events::RoleRevoked(v) => Err(anyhow::anyhow!(
176 "Unsupported StakeTableV3Events::RoleRevoked({v:?})"
177 )),
178 StakeTableV3Events::Unpaused(v) => Err(anyhow::anyhow!(
179 "Unsupported StakeTableV3Events::Unpaused({v:?})"
180 )),
181 StakeTableV3Events::Upgraded(v) => Err(anyhow::anyhow!(
182 "Unsupported StakeTableV3Events::Upgraded({v:?})"
183 )),
184 StakeTableV3Events::WithdrawalClaimed(v) => Err(anyhow::anyhow!(
185 "Unsupported StakeTableV3Events::WithdrawalClaimed({v:?})"
186 )),
187 StakeTableV3Events::ValidatorExitClaimed(v) => Err(anyhow::anyhow!(
188 "Unsupported StakeTableV3Events::ValidatorExitClaimed({v:?})"
189 )),
190 StakeTableV3Events::Withdrawal(v) => Err(anyhow::anyhow!(
191 "Unsupported StakeTableV3Events::Withdrawal({v:?})"
192 )),
193 StakeTableV3Events::MetadataUriUpdated(v) => Err(anyhow::anyhow!(
194 "Unsupported StakeTableV3Events::MetadataUriUpdated({v:?})"
195 )),
196 StakeTableV3Events::MinDelegateAmountUpdated(v) => Err(anyhow::anyhow!(
197 "Unsupported StakeTableV3Events::MinDelegateAmountUpdated({v:?})"
198 )),
199 }
200 }
201}
202
203#[cfg_attr(not(feature = "node"), allow(dead_code))]
204fn sort_stake_table_events(
205 event_logs: Vec<(StakeTableV3Events, Log)>,
206) -> Result<Vec<(EventKey, StakeTableEvent)>, EventSortingError> {
207 let mut events: Vec<(EventKey, StakeTableEvent)> = Vec::new();
208
209 let key = |log: &Log| -> Result<EventKey, EventSortingError> {
210 let block_number = log
211 .block_number
212 .ok_or(EventSortingError::MissingBlockNumber)?;
213 let log_index = log.log_index.ok_or(EventSortingError::MissingLogIndex)?;
214 Ok((block_number, log_index))
215 };
216
217 for (e, log) in event_logs {
218 let k = key(&log)?;
219 let evt: StakeTableEvent = e
220 .try_into()
221 .map_err(|_| EventSortingError::InvalidStakeTableEvent)?;
222 events.push((k, evt));
223 }
224
225 events.sort_by_key(|(key, _)| *key);
226 Ok(events)
227}
228
229#[derive(Clone, Debug, Default, PartialEq)]
230pub struct StakeTableState {
231 validators: RegisteredValidatorMap,
232 validator_exits: HashSet<Address>,
233 used_bls_keys: HashSet<BLSPubKey>,
234 used_schnorr_keys: HashSet<SchnorrPubKey>,
235 used_x25519_keys: HashSet<x25519::PublicKey>,
236}
237
238#[cfg(feature = "rlp")]
239mod stake_table_state_rlp {
240 use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable};
241
242 use super::*;
243
244 #[derive(RlpEncodable, RlpDecodable)]
245 struct StakeTableStateRlp {
246 validators: Vec<RegisteredValidator<BLSPubKey>>,
247 validator_exits: Vec<Address>,
248 used_bls_keys: Vec<Vec<u8>>,
249 used_schnorr_keys: Vec<Vec<u8>>,
250 used_x25519_keys: Vec<Vec<u8>>,
251 }
252
253 impl TryFrom<&StakeTableState> for StakeTableStateRlp {
254 type Error = anyhow::Error;
255
256 fn try_from(state: &StakeTableState) -> Result<Self, Self::Error> {
257 let mut validators = state.validators.values().cloned().collect::<Vec<_>>();
260 validators.sort_by_key(|v| v.account);
261
262 let mut validator_exits = state.validator_exits.iter().copied().collect::<Vec<_>>();
263 validator_exits.sort();
264
265 let mut used_bls_keys = state
266 .used_bls_keys
267 .iter()
268 .map(bincode::serialize)
269 .collect::<Result<Vec<_>, _>>()
270 .context("serializing used_bls_keys")?;
271 used_bls_keys.sort();
272
273 let mut used_schnorr_keys = state
274 .used_schnorr_keys
275 .iter()
276 .map(bincode::serialize)
277 .collect::<Result<Vec<_>, _>>()
278 .context("serializing used_schnorr_keys")?;
279 used_schnorr_keys.sort();
280
281 let mut used_x25519_keys = state
282 .used_x25519_keys
283 .iter()
284 .map(bincode::serialize)
285 .collect::<Result<Vec<_>, _>>()
286 .context("serializing used_x25519_keys")?;
287 used_x25519_keys.sort();
288
289 Ok(Self {
290 validators,
291 validator_exits,
292 used_bls_keys,
293 used_schnorr_keys,
294 used_x25519_keys,
295 })
296 }
297 }
298
299 impl TryFrom<StakeTableStateRlp> for StakeTableState {
300 type Error = anyhow::Error;
301
302 fn try_from(state: StakeTableStateRlp) -> Result<Self, Self::Error> {
303 Ok(Self {
304 validators: state
305 .validators
306 .into_iter()
307 .map(|v| (v.account, v))
308 .collect(),
309 validator_exits: state.validator_exits.into_iter().collect(),
310 used_bls_keys: state
311 .used_bls_keys
312 .iter()
313 .map(|bytes| bincode::deserialize(bytes))
314 .collect::<Result<HashSet<_>, _>>()
315 .context("deserializing used_bls_keys")?,
316 used_schnorr_keys: state
317 .used_schnorr_keys
318 .iter()
319 .map(|bytes| bincode::deserialize(bytes))
320 .collect::<Result<HashSet<_>, _>>()
321 .context("deserializing used_schnorr_keys")?,
322 used_x25519_keys: state
323 .used_x25519_keys
324 .iter()
325 .map(|bytes| bincode::deserialize(bytes))
326 .collect::<Result<HashSet<_>, _>>()
327 .context("deserializing used_x25519_keys")?,
328 })
329 }
330 }
331
332 impl Encodable for StakeTableState {
333 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
334 StakeTableStateRlp::try_from(self)
335 .expect("failed to canonicalize StakeTableState")
336 .encode(out);
337 }
338
339 fn length(&self) -> usize {
340 StakeTableStateRlp::try_from(self)
341 .expect("failed to canonicalize StakeTableState")
342 .length()
343 }
344 }
345
346 impl Decodable for StakeTableState {
347 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
348 let rlp = StakeTableStateRlp::decode(buf)?;
349 rlp.try_into().map_err(|err| {
350 tracing::error!("failed to parse canonical StakeTableState: {err:#}");
351 alloy_rlp::Error::Custom("failed to parse canonical StakeTableState")
352 })
353 }
354 }
355}
356
357impl Committable for StakeTableState {
358 fn commit(&self) -> committable::Commitment<Self> {
359 let mut builder = RawCommitmentBuilder::new(&Self::tag());
360
361 for (_, validator) in self.validators.iter().sorted_by_key(|(a, _)| *a) {
362 builder = builder.field("validator", validator.commit());
363 }
364
365 builder = builder.constant_str("used_bls_keys");
366 for key in self.used_bls_keys.iter().sorted() {
367 builder = builder.var_size_bytes(&key.to_bytes());
368 }
369
370 builder = builder.constant_str("used_schnorr_keys");
371 for key in self
372 .used_schnorr_keys
373 .iter()
374 .sorted_by(|a, b| a.to_affine().xy().cmp(&b.to_affine().xy()))
375 {
376 let mut schnorr_key_bytes = vec![];
377 key.serialize_with_mode(&mut schnorr_key_bytes, ark_serialize::Compress::Yes)
378 .unwrap();
379 builder = builder.var_size_bytes(&schnorr_key_bytes);
380 }
381
382 if !self.used_x25519_keys.is_empty() {
387 builder = builder.constant_str("used_x25519_keys");
388 for key in self.used_x25519_keys.iter().sorted() {
389 builder = builder.var_size_bytes(key.as_slice());
390 }
391 }
392
393 builder = builder.constant_str("validator_exits");
394
395 for key in self.validator_exits.iter().sorted() {
396 builder = builder.fixed_size_bytes(&key.into_array());
397 }
398
399 builder.finalize()
400 }
401
402 fn tag() -> String {
403 "STAKE_TABLE".to_string()
404 }
405}
406
407impl StakeTableState {
408 pub fn new(
409 validators: RegisteredValidatorMap,
410 validator_exits: HashSet<Address>,
411 used_bls_keys: HashSet<BLSPubKey>,
412 used_schnorr_keys: HashSet<SchnorrPubKey>,
413 used_x25519_keys: HashSet<x25519::PublicKey>,
414 ) -> Self {
415 Self {
416 validators,
417 validator_exits,
418 used_bls_keys,
419 used_schnorr_keys,
420 used_x25519_keys,
421 }
422 }
423
424 pub fn validators(&self) -> &RegisteredValidatorMap {
425 &self.validators
426 }
427
428 pub fn into_validators(self) -> RegisteredValidatorMap {
429 self.validators
430 }
431
432 pub fn used_bls_keys(&self) -> &HashSet<BLSPubKey> {
433 &self.used_bls_keys
434 }
435
436 pub fn used_schnorr_keys(&self) -> &HashSet<SchnorrPubKey> {
437 &self.used_schnorr_keys
438 }
439
440 pub fn used_x25519_keys(&self) -> &HashSet<x25519::PublicKey> {
441 &self.used_x25519_keys
442 }
443
444 pub fn validator_exits(&self) -> &HashSet<Address> {
445 &self.validator_exits
446 }
447
448 pub fn apply_event(&mut self, event: StakeTableEvent) -> ApplyEventResult<()> {
454 match event {
455 StakeTableEvent::Register(ValidatorRegistered {
456 account,
457 blsVk,
458 schnorrVk,
459 commission,
460 }) => {
461 let stake_table_key = BLSPubKey::try_from(blsVk).ok();
462 if stake_table_key.is_none() {
463 tracing::warn!(
464 ?account,
465 "registering unauthenticated validator (invalid BLS key)"
466 );
467 }
468 let state_ver_key = SchnorrPubKey::try_from(schnorrVk).ok();
469 if state_ver_key.is_none() {
470 tracing::warn!(
471 ?account,
472 "registering unauthenticated validator (invalid Schnorr key)"
473 );
474 }
475 let authenticated = stake_table_key.is_some() && state_ver_key.is_some();
476
477 if self.validator_exits.contains(&account) {
478 return Err(StakeTableError::ValidatorAlreadyExited(account));
479 }
480
481 let entry = self.validators.entry(account);
482 if let indexmap::map::Entry::Occupied(_) = entry {
483 return Err(StakeTableError::AlreadyRegistered(account));
484 }
485
486 if let Some(k) = stake_table_key.as_ref()
490 && self.used_bls_keys.contains(k)
491 {
492 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
493 }
494
495 if let Some(k) = state_ver_key.as_ref()
498 && self.used_schnorr_keys.contains(k)
499 {
500 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
501 k.to_string(),
502 )));
503 }
504
505 if let Some(k) = stake_table_key.as_ref() {
507 self.used_bls_keys.insert(*k);
508 }
509 if let Some(k) = state_ver_key.as_ref() {
510 self.used_schnorr_keys.insert(k.clone());
511 }
512
513 entry.or_insert(RegisteredValidator {
514 account,
515 stake_table_key,
516 state_ver_key,
517 stake: U256::ZERO,
518 commission,
519 delegators: HashMap::new(),
520 authenticated,
521 x25519_key: None,
522 p2p_addr: None,
523 });
524 },
525
526 StakeTableEvent::RegisterV2(ref reg) => {
527 let (stake_table_key, state_ver_key, authenticated) = match reg.authenticate() {
528 Ok((bls, schnorr)) => (Some(bls), Some(schnorr), true),
529 Err(e) => {
530 tracing::warn!(
531 account = ?reg.account,
532 %e,
533 "registering unauthenticated validator",
534 );
535 (
536 BLSPubKey::try_from(reg.blsVK).ok(),
537 SchnorrPubKey::try_from(reg.schnorrVK).ok(),
538 false,
539 )
540 },
541 };
542
543 let ValidatorRegisteredV2 {
544 account,
545 commission,
546 ..
547 } = reg;
548
549 if self.validator_exits.contains(account) {
551 return Err(StakeTableError::ValidatorAlreadyExited(*account));
552 }
553
554 let entry = self.validators.entry(*account);
555 if let indexmap::map::Entry::Occupied(_) = entry {
556 return Err(StakeTableError::AlreadyRegistered(*account));
557 }
558
559 if let Some(k) = stake_table_key.as_ref()
561 && self.used_bls_keys.contains(k)
562 {
563 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
564 }
565
566 let (state_ver_key, authenticated) = match state_ver_key {
570 Some(k) if self.used_schnorr_keys.contains(&k) => {
571 tracing::warn!(
572 ?account,
573 schnorr_key = %k,
574 "registering unauthenticated validator (schnorr key already used)"
575 );
576 (None, false)
577 },
578 key => (key, authenticated),
579 };
580
581 if let Some(k) = stake_table_key.as_ref() {
583 self.used_bls_keys.insert(*k);
584 }
585 if let Some(k) = state_ver_key.as_ref() {
586 self.used_schnorr_keys.insert(k.clone());
587 }
588
589 entry.or_insert(RegisteredValidator {
590 account: *account,
591 stake_table_key,
592 state_ver_key,
593 stake: U256::ZERO,
594 commission: *commission,
595 delegators: HashMap::new(),
596 authenticated,
597 x25519_key: None,
598 p2p_addr: None,
599 });
600 },
601
602 StakeTableEvent::Deregister(ValidatorExit { validator })
603 | StakeTableEvent::DeregisterV2(ValidatorExitV2 { validator, .. }) => {
604 if !self.validators.contains_key(&validator) {
605 return Err(StakeTableError::ValidatorNotFound(validator));
606 }
607
608 self.validator_exits.insert(validator);
610 self.validators.shift_remove(&validator);
611 },
612
613 StakeTableEvent::Delegate(delegated) => {
614 let Delegated {
615 delegator,
616 validator,
617 amount,
618 } = delegated;
619
620 if amount.is_zero() {
622 return Err(StakeTableError::ZeroDelegatorStake(delegator));
623 }
624
625 let val = self
626 .validators
627 .get_mut(&validator)
628 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
629
630 val.stake = val.stake.checked_add(amount).unwrap_or_else(|| {
633 panic!(
634 "validator stake overflow: validator={validator}, stake={}, \
635 amount={amount}",
636 val.stake
637 )
638 });
639 val.delegators
642 .entry(delegator)
643 .and_modify(|stake| {
644 *stake = stake.checked_add(amount).unwrap_or_else(|| {
645 panic!(
646 "delegator stake overflow: delegator={delegator}, stake={stake}, \
647 amount={amount}"
648 )
649 });
650 })
651 .or_insert(amount);
652 },
653
654 StakeTableEvent::Undelegate(Undelegated {
655 delegator,
656 validator,
657 amount,
658 })
659 | StakeTableEvent::UndelegateV2(UndelegatedV2 {
660 delegator,
661 validator,
662 amount,
663 ..
664 }) => {
665 let val = self
666 .validators
667 .get_mut(&validator)
668 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
669
670 if val.stake < amount {
671 tracing::warn!("validator_stake={}, amount={amount}", val.stake);
672 return Err(StakeTableError::InsufficientStake);
673 }
674
675 let delegator_stake = val
676 .delegators
677 .get_mut(&delegator)
678 .ok_or(StakeTableError::DelegatorNotFound(delegator))?;
679
680 if *delegator_stake < amount {
681 tracing::warn!("delegator_stake={delegator_stake}, amount={amount}");
682 return Err(StakeTableError::InsufficientStake);
683 }
684
685 let new_delegator_stake = delegator_stake.checked_sub(amount).unwrap();
687
688 val.stake = val.stake.checked_sub(amount).unwrap();
691
692 if new_delegator_stake.is_zero() {
693 val.delegators.remove(&delegator);
694 } else {
695 *delegator_stake = new_delegator_stake;
696 }
697 },
698
699 StakeTableEvent::KeyUpdate(update) => {
700 let ConsensusKeysUpdated {
701 account,
702 blsVK,
703 schnorrVK,
704 } = update;
705
706 let Ok(stake_table_key) = BLSPubKey::try_from(blsVK) else {
707 return Ok(Err(ExpectedStakeTableError::InvalidBlsKey));
708 };
709 let Ok(state_ver_key) = SchnorrPubKey::try_from(schnorrVK) else {
710 return Ok(Err(ExpectedStakeTableError::InvalidSchnorrKey));
711 };
712
713 if !self.validators.contains_key(&account) {
714 return Err(StakeTableError::ValidatorNotFound(account));
715 }
716
717 if self.used_bls_keys.contains(&stake_table_key) {
718 return Err(StakeTableError::BlsKeyAlreadyUsed(
719 stake_table_key.to_string(),
720 ));
721 }
722
723 if self.used_schnorr_keys.contains(&state_ver_key) {
726 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
727 state_ver_key.to_string(),
728 )));
729 }
730
731 self.used_bls_keys.insert(stake_table_key);
733 self.used_schnorr_keys.insert(state_ver_key.clone());
734 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
736 panic!("validator {account} must exist after contains_key check")
737 });
738 validator.stake_table_key = Some(stake_table_key);
739 validator.state_ver_key = Some(state_ver_key);
740 validator.authenticated = true;
742 },
743
744 StakeTableEvent::KeyUpdateV2(update) => {
745 let (stake_table_key, state_ver_key) = match update.authenticate() {
748 Ok(parsed) => parsed,
749 Err(StakeTableSolError::InvalidBlsKey) => {
750 return Ok(Err(ExpectedStakeTableError::InvalidBlsKey));
751 },
752 Err(StakeTableSolError::InvalidSchnorrKey) => {
753 return Ok(Err(ExpectedStakeTableError::InvalidSchnorrKey));
754 },
755 Err(e) => {
756 return Err(StakeTableError::AuthenticationFailed(e.to_string()));
757 },
758 };
759
760 let ConsensusKeysUpdatedV2 { account, .. } = update;
761
762 if !self.validators.contains_key(&account) {
763 return Err(StakeTableError::ValidatorNotFound(account));
764 }
765
766 if self.used_bls_keys.contains(&stake_table_key) {
768 return Err(StakeTableError::BlsKeyAlreadyUsed(
769 stake_table_key.to_string(),
770 ));
771 }
772
773 if self.used_schnorr_keys.contains(&state_ver_key) {
774 return Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(
775 state_ver_key.to_string(),
776 )));
777 }
778
779 self.used_bls_keys.insert(stake_table_key);
781 self.used_schnorr_keys.insert(state_ver_key.clone());
782
783 let validator = self.validators.get_mut(&account).unwrap_or_else(|| {
785 panic!("validator {account} must exist after contains_key check")
786 });
787 validator.stake_table_key = Some(stake_table_key);
788 validator.state_ver_key = Some(state_ver_key);
789 validator.authenticated = true;
790 },
791
792 StakeTableEvent::CommissionUpdate(CommissionUpdated {
793 validator,
794 newCommission,
795 ..
796 }) => {
797 if newCommission > COMMISSION_BASIS_POINTS {
800 return Err(StakeTableError::InvalidCommission(validator, newCommission));
801 }
802
803 let val = self
807 .validators
808 .get_mut(&validator)
809 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
810 val.commission = newCommission;
811 },
812
813 StakeTableEvent::RegisterV3(ref reg) => {
814 let (stake_table_key, state_ver_key, authenticated) = match reg.authenticate() {
815 Ok((bls, schnorr)) => (Some(bls), Some(schnorr), true),
816 Err(e) => {
817 tracing::warn!(
818 account = ?reg.account,
819 %e,
820 "registering unauthenticated validator",
821 );
822 (
823 BLSPubKey::try_from(reg.blsVK).ok(),
824 SchnorrPubKey::try_from(reg.schnorrVK).ok(),
825 false,
826 )
827 },
828 };
829
830 let ValidatorRegisteredV3 {
831 account,
832 commission,
833 x25519Key,
834 p2pAddr,
835 ..
836 } = reg;
837
838 let x25519_key = parse_x25519_key(x25519Key.0)
839 .inspect_err(|e| tracing::warn!(%e, ?account, "Invalid x25519 key"))
840 .ok();
841
842 let p2p_addr = match p2pAddr.parse::<NetAddr>() {
844 Ok(addr) => Some(addr),
845 Err(e) => {
846 if !p2pAddr.is_empty() {
847 tracing::warn!(%e, account = ?account, "Failed to parse p2p addr");
848 }
849 None
850 },
851 };
852
853 if self.validator_exits.contains(account) {
854 return Err(StakeTableError::ValidatorAlreadyExited(*account));
855 }
856
857 let entry = self.validators.entry(*account);
858 if let indexmap::map::Entry::Occupied(_) = entry {
859 return Err(StakeTableError::AlreadyRegistered(*account));
860 }
861
862 if let Some(k) = stake_table_key.as_ref()
864 && self.used_bls_keys.contains(k)
865 {
866 return Err(StakeTableError::BlsKeyAlreadyUsed(k.to_string()));
867 }
868
869 if let Some(k) = x25519_key.as_ref()
870 && self.used_x25519_keys.contains(k)
871 {
872 return Err(StakeTableError::X25519KeyAlreadyUsed(k.to_string()));
873 }
874
875 let (state_ver_key, authenticated) = match state_ver_key {
876 Some(k) if self.used_schnorr_keys.contains(&k) => {
877 tracing::warn!(
878 ?account,
879 schnorr_key = %k,
880 "registering unauthenticated validator (schnorr key already used)"
881 );
882 (None, false)
883 },
884 key => (key, authenticated),
885 };
886
887 if let Some(k) = stake_table_key.as_ref() {
889 self.used_bls_keys.insert(*k);
890 }
891 if let Some(k) = state_ver_key.as_ref() {
892 self.used_schnorr_keys.insert(k.clone());
893 }
894 if let Some(k) = x25519_key.as_ref() {
895 self.used_x25519_keys.insert(*k);
896 }
897
898 entry.or_insert(RegisteredValidator {
899 account: *account,
900 stake_table_key,
901 state_ver_key,
902 stake: U256::ZERO,
903 commission: *commission,
904 delegators: HashMap::new(),
905 authenticated,
906 x25519_key,
907 p2p_addr,
908 });
909 },
910
911 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
912 validator,
913 x25519Key,
914 }) => {
915 let val = self
916 .validators
917 .get_mut(&validator)
918 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
919
920 let x25519_key = parse_x25519_key(x25519Key.0)
921 .inspect_err(|e| tracing::warn!(%e, ?validator, "Invalid x25519 key"))
922 .ok();
923
924 if let Some(key) = x25519_key {
925 if self.used_x25519_keys.contains(&key) {
926 return Err(StakeTableError::X25519KeyAlreadyUsed(key.to_string()));
927 }
928 self.used_x25519_keys.insert(key);
929 }
930 val.x25519_key = x25519_key;
931 },
932
933 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
934 validator,
935 ref p2pAddr,
936 }) => {
937 let val = self
938 .validators
939 .get_mut(&validator)
940 .ok_or(StakeTableError::ValidatorNotFound(validator))?;
941
942 match p2pAddr.parse::<NetAddr>() {
943 Ok(addr) => val.p2p_addr = Some(addr),
944 Err(e) => {
945 tracing::warn!(%e, validator = ?validator, "Failed to parse p2p addr");
946 val.p2p_addr = None;
947 },
948 }
949 },
950 }
951
952 Ok(Ok(()))
953 }
954}
955
956fn parse_x25519_key(bytes: [u8; 32]) -> anyhow::Result<x25519::PublicKey> {
962 ensure!(bytes != [0u8; 32], "zero key");
963 Ok(x25519::PublicKey::try_from(bytes.as_slice())?)
964}
965
966pub fn validators_from_l1_events<I: Iterator<Item = StakeTableEvent>>(
967 events: I,
968) -> Result<(RegisteredValidatorMap, StakeTableHash), StakeTableError> {
969 let mut state = StakeTableState::default();
970 for event in events {
971 match state.apply_event(event.clone()) {
972 Ok(Ok(())) => {
973 },
975 Ok(Err(expected_err)) => {
976 tracing::warn!("Expected error while applying event {event:?}: {expected_err}");
978 },
979 Err(err) => {
980 tracing::error!("Fatal error in applying event {event:?}: {err}");
981 return Err(err);
982 },
983 }
984 }
985 let commit = state.commit();
986 Ok((state.into_validators(), commit))
987}
988
989pub(crate) fn select_active_validator_set(
995 candidates: &RegisteredValidatorMap,
996 protocol_version: Version,
997) -> Result<AuthenticatedValidatorMap, StakeTableError> {
998 let total_candidates = candidates.len();
999
1000 let valid_validators: AuthenticatedValidatorMap = candidates
1001 .iter()
1002 .filter_map(
1003 |(address, validator)| match AuthenticatedValidator::try_from(validator) {
1004 Err(e) => {
1005 tracing::debug!("{e}");
1006 None
1007 },
1008 Ok(cv) => {
1009 if cv.delegators.is_empty() {
1010 tracing::debug!("Validator {address:?} does not have any delegator");
1011 return None;
1012 }
1013 if cv.stake.is_zero() {
1014 tracing::debug!("Validator {address:?} does not have any stake");
1015 return None;
1016 }
1017 if let Err(err) = cv.ensure_eligible(protocol_version) {
1018 tracing::debug!(
1019 ?address,
1020 %err,
1021 "Validator not eligible at protocol version {protocol_version}"
1022 );
1023 return None;
1024 }
1025 Some((*address, cv))
1026 },
1027 },
1028 )
1029 .collect();
1030
1031 tracing::debug!(
1032 total_candidates,
1033 filtered = valid_validators.len(),
1034 "Filtered out invalid validators"
1035 );
1036
1037 if valid_validators.is_empty() {
1038 tracing::warn!("Validator selection failed: no validators passed minimum criteria");
1039 return Err(StakeTableError::NoValidValidators);
1040 }
1041
1042 let maximum_stake = valid_validators.values().map(|v| v.stake).max().unwrap();
1043
1044 let minimum_stake = maximum_stake
1045 .checked_div(U256::from(VID_TARGET_TOTAL_STAKE))
1046 .ok_or_else(|| {
1047 tracing::error!("Overflow while calculating minimum stake threshold");
1048 StakeTableError::MinimumStakeOverflow
1049 })?;
1050
1051 let mut valid_stakers: Vec<_> = valid_validators
1052 .iter()
1053 .filter(|(_, v)| v.stake >= minimum_stake)
1054 .map(|(addr, v)| (*addr, v.stake))
1055 .collect();
1056
1057 tracing::info!(
1058 count = valid_stakers.len(),
1059 "Number of validators above minimum stake threshold"
1060 );
1061
1062 valid_stakers.sort_by_key(|(_, stake)| std::cmp::Reverse(*stake));
1064
1065 if valid_stakers.len() > MAX_VALIDATORS {
1066 valid_stakers.truncate(MAX_VALIDATORS);
1067 }
1068
1069 let selected_addresses: HashSet<_> = valid_stakers.iter().map(|(addr, _)| *addr).collect();
1070 let selected_validators: AuthenticatedValidatorMap = valid_validators
1071 .into_iter()
1072 .filter(|(address, _)| selected_addresses.contains(address))
1073 .collect();
1074
1075 tracing::info!(
1076 final_count = selected_validators.len(),
1077 "Selected active validator set"
1078 );
1079
1080 Ok(selected_validators)
1081}
1082
1083#[derive(Clone, Debug)]
1084pub struct ValidatorSet {
1085 pub(crate) all_validators: RegisteredValidatorMap,
1086 pub(crate) active_validators: AuthenticatedValidatorMap,
1087 pub(crate) stake_table_hash: Option<StakeTableHash>,
1088 pub(crate) protocol_version: Version,
1090}
1091
1092impl ValidatorSet {
1093 pub fn from_state(
1095 state: &StakeTableState,
1096 protocol_version: Version,
1097 ) -> Result<Self, StakeTableError> {
1098 let active_validators = select_active_validator_set(state.validators(), protocol_version)?;
1099 Ok(Self {
1100 all_validators: state.validators().clone(),
1101 active_validators,
1102 stake_table_hash: Some(state.commit()),
1103 protocol_version,
1104 })
1105 }
1106
1107 pub fn from_l1_events<I: Iterator<Item = StakeTableEvent>>(
1109 events: I,
1110 protocol_version: Version,
1111 ) -> Result<Self, StakeTableError> {
1112 let (all_validators, stake_table_hash) = validators_from_l1_events(events)?;
1113 let active_validators = select_active_validator_set(&all_validators, protocol_version)?;
1114 Ok(Self {
1115 all_validators,
1116 active_validators,
1117 stake_table_hash: Some(stake_table_hash),
1118 protocol_version,
1119 })
1120 }
1121
1122 pub fn all_validators(&self) -> &RegisteredValidatorMap {
1124 &self.all_validators
1125 }
1126
1127 pub fn active_validators(&self) -> &AuthenticatedValidatorMap {
1129 &self.active_validators
1130 }
1131
1132 pub fn stake_table_hash(&self) -> Option<StakeTableHash> {
1134 self.stake_table_hash
1135 }
1136
1137 pub fn protocol_version(&self) -> Version {
1139 self.protocol_version
1140 }
1141}
1142
1143impl std::fmt::Debug for StakeTableEvent {
1144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1145 match self {
1146 StakeTableEvent::Register(event) => write!(f, "Register({:?})", event.account),
1147 StakeTableEvent::RegisterV2(event) => write!(f, "RegisterV2({:?})", event.account),
1148 StakeTableEvent::Deregister(event) => write!(f, "Deregister({:?})", event.validator),
1149 StakeTableEvent::DeregisterV2(event) => {
1150 write!(f, "DeregisterV2({:?})", event.validator)
1151 },
1152 StakeTableEvent::Delegate(event) => write!(f, "Delegate({:?})", event.delegator),
1153 StakeTableEvent::Undelegate(event) => write!(f, "Undelegate({:?})", event.delegator),
1154 StakeTableEvent::UndelegateV2(event) => {
1155 write!(f, "UndelegateV2({:?})", event.delegator)
1156 },
1157 StakeTableEvent::KeyUpdate(event) => write!(f, "KeyUpdate({:?})", event.account),
1158 StakeTableEvent::KeyUpdateV2(event) => write!(f, "KeyUpdateV2({:?})", event.account),
1159 StakeTableEvent::CommissionUpdate(event) => {
1160 write!(f, "CommissionUpdate({:?})", event.validator)
1161 },
1162 StakeTableEvent::RegisterV3(event) => {
1163 write!(f, "RegisterV3({:?})", event.account)
1164 },
1165 StakeTableEvent::X25519KeyUpdate(event) => {
1166 write!(f, "X25519KeyUpdate({:?})", event.validator)
1167 },
1168 StakeTableEvent::P2pAddrUpdate(event) => {
1169 write!(f, "P2pAddrUpdate({:?})", event.validator)
1170 },
1171 }
1172 }
1173}
1174
1175const MAINNET_INITIAL_SUPPLY_WEI: u128 = 3_590_000_000_000_000_000_000_000_000;
1177const DECAF_INITIAL_SUPPLY_WEI: u128 = 10_000_000_000_000_000_000_000_000_000;
1179
1180#[cfg_attr(not(feature = "node"), allow(dead_code))]
1182const STORE_EVENTS_ATTEMPTS: usize = 5;
1183#[cfg_attr(not(feature = "node"), allow(dead_code))]
1185const STORE_EVENTS_RETRY_DELAY: Duration = Duration::from_secs(1);
1186
1187#[cfg_attr(not(feature = "node"), allow(dead_code))]
1189const PROGRESS_INTERVAL: Duration = Duration::from_secs(30);
1190
1191const MAINNET_STAKE_TABLE_CONTRACT: Address =
1192 address!("0xcef474d372b5b09defe2af187bf17338dc704451");
1193const DECAF_STAKE_TABLE_CONTRACT: Address = address!("0x40304fbe94d5e7d1492dd90c53a2d63e8506a037");
1194
1195fn known_initial_supply(chain_id: ChainId, stake_table_contract: Address) -> Option<U256> {
1213 if chain_id == MAINNET_CHAIN_ID && stake_table_contract == MAINNET_STAKE_TABLE_CONTRACT {
1214 Some(U256::from(MAINNET_INITIAL_SUPPLY_WEI))
1215 } else if chain_id == DECAF_CHAIN_ID && stake_table_contract == DECAF_STAKE_TABLE_CONTRACT {
1216 Some(U256::from(DECAF_INITIAL_SUPPLY_WEI))
1217 } else {
1218 None
1219 }
1220}
1221
1222impl Fetcher {
1223 #[cfg(feature = "node")]
1224 pub fn new(
1225 peers: Arc<dyn StateCatchup>,
1226 persistence: Arc<AsyncMutex<dyn MembershipPersistence>>,
1227 l1_client: L1Client,
1228 chain_config: ChainConfig,
1229 ) -> Self {
1230 Self {
1231 peers,
1232 persistence,
1233 l1_client,
1234 chain_config: Arc::new(AsyncMutex::new(chain_config)),
1235 update_task: StakeTableUpdateTask(AsyncMutex::new(None)).into(),
1236 initial_supply: Arc::new(AsyncRwLock::new(None)),
1237 }
1238 }
1239
1240 #[cfg(feature = "node")]
1241 pub async fn spawn_update_loop(&self) {
1242 let mut update_task = self.update_task.0.lock().await;
1243 if update_task.is_none() {
1244 *update_task = Some(spawn(self.update_loop()));
1245 }
1246 }
1247
1248 #[cfg(feature = "node")]
1253 fn update_loop(&self) -> impl Future<Output = ()> + use<> {
1254 let span = tracing::warn_span!("Stake table update loop");
1255 let self_clone = self.clone();
1256 let state = self.l1_client.state.clone();
1257 let l1_retry = self.l1_client.options().l1_retry_delay;
1258 let update_delay = self.l1_client.options().stake_table_update_interval;
1259 let chain_config = self.chain_config.clone();
1260
1261 async move {
1262 let stake_contract_address = loop {
1267 let contract = chain_config.lock().await.stake_table_contract;
1268 match contract {
1269 Some(addr) => break addr,
1270 None => {
1271 tracing::debug!(
1272 "Stake table contract address not found. Retrying in {l1_retry:?}...",
1273 );
1274 },
1275 }
1276 sleep(l1_retry).await;
1277 };
1278
1279 loop {
1281 let finalized_block = loop {
1282 let last_finalized = state.lock().await.last_finalized;
1283 if let Some(block) = last_finalized {
1284 break block;
1285 }
1286 tracing::debug!("Finalized block not yet available. Retrying in {l1_retry:?}",);
1287 sleep(l1_retry).await;
1288 };
1289
1290 tracing::debug!("Attempting to fetch stake table at L1 block {finalized_block:?}",);
1291
1292 loop {
1293 match self_clone
1294 .fetch_and_store_stake_table_events(stake_contract_address, finalized_block)
1295 .await
1296 {
1297 Ok(events) => {
1298 tracing::info!(
1299 "Successfully fetched and stored stake table events at \
1300 block={finalized_block:?}"
1301 );
1302 tracing::debug!("events={events:?}");
1303 break;
1304 },
1305 Err(e) => {
1306 tracing::error!(
1307 "Error fetching stake table at block {finalized_block:?}. err= \
1308 {e:#}",
1309 );
1310 sleep(l1_retry).await;
1311 },
1312 }
1313 }
1314
1315 tracing::debug!("Waiting {update_delay:?} before next stake table update...",);
1316 sleep(update_delay).await;
1317 }
1318 }
1319 .instrument(span)
1320 }
1321
1322 #[cfg(feature = "node")]
1329 pub async fn fetch_and_store_stake_table_events(
1330 &self,
1331 contract: Address,
1332 to_block: u64,
1333 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
1334 let (read_l1_offset, persistence_events) = {
1335 let persistence_lock = self.persistence.lock().await;
1336 persistence_lock.load_events(0, to_block).await?
1337 };
1338
1339 tracing::info!("loaded events from storage to_block={to_block:?}");
1340
1341 if let Some(EventsPersistenceRead::Complete) = read_l1_offset {
1344 return Ok(persistence_events);
1345 }
1346
1347 let from_block = read_l1_offset
1348 .map(|read| match read {
1349 EventsPersistenceRead::UntilL1Block(block) => Ok(block + 1),
1350 EventsPersistenceRead::Complete => Err(anyhow::anyhow!(
1351 "Unexpected state. offset is complete after returning early"
1352 )),
1353 })
1354 .transpose()?;
1355
1356 ensure!(
1357 Some(to_block) >= from_block,
1358 "to_block {to_block:?} is less than from_block {from_block:?}"
1359 );
1360
1361 tracing::info!(%to_block, from_block = ?from_block, "Fetching events from contract");
1362
1363 let contract_events = Self::fetch_events_from_contract(
1364 self.l1_client.clone(),
1365 contract,
1366 from_block,
1367 to_block,
1368 )
1369 .await?;
1370
1371 tracing::info!(
1373 "storing {} new events in storage to_block={to_block:?}",
1374 contract_events.len()
1375 );
1376 for attempt in 1..=STORE_EVENTS_ATTEMPTS {
1379 let result = {
1380 let persistence_lock = self.persistence.lock().await;
1381 persistence_lock
1382 .store_events(to_block, contract_events.clone())
1383 .await
1384 };
1385 match result {
1386 Ok(()) => break,
1387 Err(e) if attempt < STORE_EVENTS_ATTEMPTS => {
1388 tracing::warn!(attempt, %e, "failed to store stake table events, retrying");
1389 sleep(STORE_EVENTS_RETRY_DELAY).await;
1390 },
1391 Err(e) => return Err(e.context("storing stake table events")),
1392 }
1393 }
1394
1395 let mut events = match from_block {
1396 Some(_) => persistence_events
1397 .into_iter()
1398 .chain(contract_events)
1399 .collect(),
1400 None => contract_events,
1401 };
1402
1403 let len_before_dedup = events.len();
1408 events.dedup();
1409 let len_after_dedup = events.len();
1410 if len_before_dedup != len_after_dedup {
1411 tracing::warn!("Duplicate events found and removed. This should not normally happen.")
1412 }
1413
1414 Ok(events)
1415 }
1416
1417 #[cfg_attr(not(feature = "node"), allow(dead_code))]
1424 fn validate_event(event: &StakeTableV3Events, log: &Log) -> Result<bool, StakeTableError> {
1425 match event {
1426 StakeTableV3Events::ConsensusKeysUpdatedV2(evt) => {
1427 if let Err(err) = evt.authenticate() {
1428 tracing::warn!(
1429 %err,
1430 "Failed to authenticate ConsensusKeysUpdatedV2 event: {}",
1431 log.display()
1432 );
1433 return Ok(false);
1434 }
1435 },
1436 StakeTableV3Events::CommissionUpdated(evt)
1437 if evt.newCommission > COMMISSION_BASIS_POINTS =>
1438 {
1439 return Err(StakeTableError::InvalidCommission(
1440 evt.validator,
1441 evt.newCommission,
1442 ));
1443 },
1444 _ => {},
1445 }
1446
1447 Ok(true)
1448 }
1449
1450 #[cfg_attr(not(feature = "node"), allow(dead_code))]
1452 fn block_range_chunks(
1453 from_block: u64,
1454 to_block: u64,
1455 chunk_size: u64,
1456 ) -> impl Iterator<Item = (u64, u64)> {
1457 let mut start = from_block;
1458 let end = to_block;
1459 std::iter::from_fn(move || {
1460 let chunk_end = min(start + chunk_size - 1, end);
1461 if chunk_end < start {
1462 return None;
1463 }
1464 let chunk = (start, chunk_end);
1465 start = chunk_end + 1;
1466 Some(chunk)
1467 })
1468 }
1469
1470 #[cfg_attr(not(feature = "node"), allow(dead_code))]
1475 fn block_range_chunks_rev(
1476 from_block: u64,
1477 to_block: u64,
1478 chunk_size: u64,
1479 ) -> impl Iterator<Item = (u64, u64)> {
1480 let chunk_size = chunk_size.max(1);
1481 let mut end = to_block;
1482 let mut exhausted = to_block < from_block;
1483 std::iter::from_fn(move || {
1484 if exhausted {
1485 return None;
1486 }
1487 let chunk_end = end;
1488 let chunk_start = max(from_block, chunk_end.saturating_sub(chunk_size - 1));
1489 exhausted = chunk_start == from_block;
1490 end = chunk_start.saturating_sub(1);
1491 Some((chunk_start, chunk_end))
1492 })
1493 }
1494
1495 #[cfg(feature = "node")]
1497 pub async fn fetch_events_from_contract(
1498 l1_client: L1Client,
1499 contract: Address,
1500 from_block: Option<u64>,
1501 to_block: u64,
1502 ) -> Result<Vec<(EventKey, StakeTableEvent)>, StakeTableError> {
1503 let stake_table_contract = StakeTableV3::new(contract, l1_client.provider.clone());
1504 let max_retry_duration = l1_client.options().l1_events_max_retry_duration;
1505 let retry_delay = l1_client.options().l1_retry_delay;
1506 let from_block = match from_block {
1509 Some(block) => block,
1510 None => {
1511 let start = Instant::now();
1512 loop {
1513 match stake_table_contract.initializedAtBlock().call().await {
1514 Ok(init_block) => break init_block.to::<u64>(),
1515 Err(err) => {
1516 if start.elapsed() >= max_retry_duration {
1517 panic!(
1518 "Failed to retrieve initial block after `{}`: {err}",
1519 format_duration(max_retry_duration)
1520 );
1521 }
1522 tracing::warn!(%err, "Failed to retrieve initial block, retrying...");
1523 sleep(retry_delay).await;
1524 },
1525 }
1526 }
1527 },
1528 };
1529
1530 let chunk_size = l1_client.options().l1_events_max_block_range;
1534 let chunks = Self::block_range_chunks(from_block, to_block, chunk_size);
1535 let scan_start = Instant::now();
1536 let mut last_report = Instant::now();
1537
1538 let mut events = vec![];
1539
1540 for (chunk, (from, to)) in chunks.enumerate() {
1541 let provider = l1_client.provider.clone();
1542
1543 tracing::debug!(from, to, "fetch all stake table events in range");
1544 let logs: Vec<Log> = retry(
1547 retry_delay,
1548 max_retry_duration,
1549 "stake table events fetch",
1550 move || {
1551 let provider = provider.clone();
1552
1553 Box::pin(async move {
1554 provider
1555 .get_logs(&Self::events_filter(contract, from, to))
1556 .await
1557 })
1558 },
1559 )
1560 .await;
1561
1562 events.extend(Self::decode_events(logs)?);
1563
1564 if last_report.elapsed() >= PROGRESS_INTERVAL {
1566 last_report = Instant::now();
1567 tracing::info!(
1568 target: "announce",
1569 chunks = chunk + 1,
1570 at_block = to,
1571 to_block,
1572 events = events.len(),
1573 elapsed_secs = scan_start.elapsed().as_secs(),
1574 "scanning stake table event history"
1575 );
1576 }
1577 }
1578
1579 sort_stake_table_events(events).map_err(Into::into)
1580 }
1581
1582 #[cfg(feature = "node")]
1584 fn events_filter(contract: Address, from_block: u64, to_block: u64) -> Filter {
1585 Filter::new()
1586 .events([
1587 ValidatorRegistered::SIGNATURE,
1588 ValidatorRegisteredV2::SIGNATURE,
1589 ValidatorRegisteredV3::SIGNATURE,
1590 ValidatorExit::SIGNATURE,
1591 ValidatorExitV2::SIGNATURE,
1592 Delegated::SIGNATURE,
1593 Undelegated::SIGNATURE,
1594 UndelegatedV2::SIGNATURE,
1595 ConsensusKeysUpdated::SIGNATURE,
1596 ConsensusKeysUpdatedV2::SIGNATURE,
1597 CommissionUpdated::SIGNATURE,
1598 X25519KeyUpdated::SIGNATURE,
1599 P2pAddrUpdated::SIGNATURE,
1600 ])
1601 .address(contract)
1602 .from_block(from_block)
1603 .to_block(to_block)
1604 }
1605
1606 #[cfg(feature = "node")]
1608 fn decode_events(logs: Vec<Log>) -> Result<Vec<(StakeTableV3Events, Log)>, StakeTableError> {
1609 logs.into_iter()
1610 .filter_map(|log| {
1611 let event =
1612 StakeTableV3Events::decode_raw_log(log.topics(), &log.data().data).ok()?;
1613 match Self::validate_event(&event, &log) {
1614 Ok(true) => Some(Ok((event, log))),
1615 Ok(false) => None,
1616 Err(e) => Some(Err(e)),
1617 }
1618 })
1619 .collect()
1620 }
1621
1622 #[cfg(feature = "node")]
1629 pub async fn try_fetch_events_from_contract(
1630 l1_client: L1Client,
1631 contract: Address,
1632 from_block: u64,
1633 to_block: u64,
1634 ) -> anyhow::Result<Vec<(EventKey, StakeTableEvent)>> {
1635 let logs = l1_client
1636 .provider
1637 .get_logs(&Self::events_filter(contract, from_block, to_block))
1638 .await?;
1639 Ok(sort_stake_table_events(Self::decode_events(logs)?)?)
1640 }
1641
1642 #[cfg(feature = "node")]
1644 pub async fn fetch_all_validators_from_contract(
1645 l1_client: L1Client,
1646 contract: Address,
1647 to_block: u64,
1648 ) -> anyhow::Result<(RegisteredValidatorMap, StakeTableHash)> {
1649 let events = Self::fetch_events_from_contract(l1_client, contract, None, to_block).await?;
1650
1651 validators_from_l1_events(events.into_iter().map(|(_, e)| e))
1653 .context("failed to construct validators set from l1 events")
1654 }
1655
1656 #[cfg(feature = "node")]
1661 pub async fn initial_supply_or_fetch(&self) -> Result<U256, FetchRewardError> {
1662 if let Some(supply) = self.known_initial_supply().await {
1663 return Ok(supply);
1664 }
1665
1666 let supply = *self.initial_supply.read().await;
1668 match supply {
1669 Some(supply) => Ok(supply),
1670 None => self.fetch_and_update_initial_supply().await,
1671 }
1672 }
1673
1674 #[cfg(not(feature = "node"))]
1676 pub async fn initial_supply_or_fetch(&self) -> Result<U256, FetchRewardError> {
1677 if let Some(supply) = self.known_initial_supply().await {
1678 return Ok(supply);
1679 }
1680
1681 Ok((*self.initial_supply.read().await).expect("initial supply pre-populated"))
1682 }
1683
1684 async fn known_initial_supply(&self) -> Option<U256> {
1687 let chain_config = *self.chain_config.lock().await;
1688 known_initial_supply(chain_config.chain_id, chain_config.stake_table_contract?)
1689 }
1690
1691 #[cfg(feature = "node")]
1694 pub async fn fetch_fixed_block_reward(&self) -> Result<RewardAmount, FetchRewardError> {
1695 let initial_supply = self.initial_supply_or_fetch().await?;
1696
1697 let reward = ((initial_supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
1698 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
1699 .ok_or(FetchRewardError::DivisionByZero(
1700 "COMMISSION_BASIS_POINTS is zero",
1701 ))?;
1702
1703 Ok(RewardAmount(reward))
1704 }
1705
1706 #[cfg(feature = "node")]
1721 pub async fn fetch_and_update_initial_supply(&self) -> Result<U256, FetchRewardError> {
1722 tracing::info!("Fetching token initial supply");
1723 let chain_config = *self.chain_config.lock().await;
1724
1725 let stake_table_contract = chain_config
1726 .stake_table_contract
1727 .ok_or(FetchRewardError::MissingStakeTableContract)?;
1728
1729 let provider = self.l1_client.provider.clone();
1730 let stake_table = StakeTableV3::new(stake_table_contract, provider.clone());
1731
1732 let stake_table_init_block = stake_table
1736 .initializedAtBlock()
1737 .block(BlockId::finalized())
1738 .call()
1739 .await
1740 .map_err(FetchRewardError::ContractCall)?
1741 .to::<u64>();
1742
1743 tracing::info!("stake table init block ={stake_table_init_block}");
1744
1745 let token_address = stake_table
1746 .token()
1747 .block(BlockId::finalized())
1748 .call()
1749 .await
1750 .map_err(FetchRewardError::TokenAddressFetch)?;
1751
1752 let token = EspToken::new(token_address, provider.clone());
1753
1754 let init_logs = token
1757 .Initialized_filter()
1758 .from_block(0u64)
1759 .to_block(BlockNumberOrTag::Finalized)
1760 .query()
1761 .await;
1762
1763 let init_log = match init_logs {
1764 Ok(init_logs) => {
1765 if init_logs.is_empty() {
1766 tracing::error!(
1767 "Token Initialized event logs are empty. This should never happen"
1768 );
1769 return Err(FetchRewardError::MissingInitializedEvent);
1770 }
1771
1772 let (_, init_log) = init_logs[0].clone();
1773
1774 tracing::debug!(tx_hash = ?init_log.transaction_hash, "Found token `Initialized` event");
1775 init_log
1776 },
1777 Err(err) => {
1778 tracing::warn!(
1779 "RPC returned error {err:?}. will fallback to scanning over fixed block range"
1780 );
1781 self.scan_token_contract_initialized_event_log(
1782 stake_table_init_block,
1783 token.clone(),
1784 )
1785 .await?
1786 },
1787 };
1788
1789 let init_block = init_log
1790 .block_number
1791 .ok_or(FetchRewardError::MissingBlockNumber)?;
1792
1793 let init_tx_hash =
1794 init_log
1795 .transaction_hash
1796 .ok_or_else(|| FetchRewardError::MissingTransactionHash {
1797 init_log: init_log.clone().into(),
1798 })?;
1799
1800 let transfer_logs = token
1804 .Transfer_filter()
1805 .from_block(init_block)
1806 .to_block(init_block)
1807 .query()
1808 .await
1809 .map_err(FetchRewardError::TransferEventQuery)?;
1810
1811 let (mint_transfer, _) = transfer_logs
1812 .iter()
1813 .find(|(transfer, log)| {
1814 log.transaction_hash == Some(init_tx_hash) && transfer.from == Address::ZERO
1815 })
1816 .ok_or(FetchRewardError::MissingTransferEvent)?;
1817
1818 tracing::debug!("mint transfer event ={mint_transfer:?}");
1819
1820 let initial_supply = mint_transfer.value;
1821
1822 tracing::info!("Initial token amount: {} ESP", format_ether(initial_supply));
1823
1824 let mut writer = self.initial_supply.write().await;
1825 *writer = Some(initial_supply);
1826
1827 Ok(initial_supply)
1828 }
1829
1830 #[cfg(feature = "node")]
1838 pub async fn scan_token_contract_initialized_event_log(
1839 &self,
1840 stake_table_init_block: u64,
1841 token: EspTokenInstance<L1Provider>,
1842 ) -> Result<Log, FetchRewardError> {
1843 let max_events_range = self.l1_client.options().l1_events_max_block_range;
1844 const MAX_BLOCKS_SCANNED: u64 = 200_000;
1845
1846 let lowest_block = stake_table_init_block.saturating_sub(MAX_BLOCKS_SCANNED - 1);
1847 let chunks =
1848 Self::block_range_chunks_rev(lowest_block, stake_table_init_block, max_events_range);
1849
1850 for (from_block, to_block) in chunks {
1851 let init_logs = token
1852 .Initialized_filter()
1853 .from_block(from_block)
1854 .to_block(to_block)
1855 .query()
1856 .await
1857 .map_err(FetchRewardError::ScanQueryFailed)?;
1858
1859 if !init_logs.is_empty() {
1860 let (_, init_log) = init_logs[0].clone();
1861 tracing::info!(
1862 from_block,
1863 tx_hash = ?init_log.transaction_hash,
1864 "Found token Initialized event during scan"
1865 );
1866 return Ok(init_log);
1867 }
1868 }
1869
1870 tracing::error!(
1871 lowest_block,
1872 stake_table_init_block,
1873 "Exceeded maximum scan range while searching for token Initialized event"
1874 );
1875 Err(FetchRewardError::ExceededMaxScanRange(MAX_BLOCKS_SCANNED))
1876 }
1877
1878 pub async fn update_chain_config(&self, header: &Header) -> anyhow::Result<()> {
1879 let chain_config = self.get_chain_config(header).await?;
1880 *self.chain_config.lock().await = chain_config;
1882
1883 Ok(())
1884 }
1885
1886 #[cfg(feature = "node")]
1887 pub async fn fetch(&self, epoch: EpochNumber, header: &Header) -> anyhow::Result<ValidatorSet> {
1888 let chain_config = *self.chain_config.lock().await;
1889 let Some(address) = chain_config.stake_table_contract else {
1890 bail!("No stake table contract address found in Chain config");
1891 };
1892
1893 let Some(l1_finalized_block_info) = header.l1_finalized() else {
1894 bail!(
1895 "The epoch root for epoch {epoch} is missing the L1 finalized block info. This is \
1896 a fatal error. Consensus is blocked and will not recover."
1897 );
1898 };
1899
1900 let events = match self
1901 .fetch_and_store_stake_table_events(address, l1_finalized_block_info.number())
1902 .await
1903 .map_err(GetStakeTablesError::L1ClientFetchError)
1904 {
1905 Ok(events) => events,
1906 Err(e) => {
1907 bail!("failed to fetch stake table events {e:?}");
1908 },
1909 };
1910
1911 match ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), header.version()) {
1916 Ok(res) => Ok(res),
1917 Err(e) => {
1918 bail!("failed to construct stake table {e:?}");
1919 },
1920 }
1921 }
1922
1923 pub(crate) async fn get_chain_config(&self, header: &Header) -> anyhow::Result<ChainConfig> {
1926 let chain_config = self.chain_config.lock().await;
1927 let peers = self.peers.clone();
1928 let header_cf = header.chain_config();
1929 if chain_config.commit() == header_cf.commit() {
1930 return Ok(*chain_config);
1931 }
1932
1933 let cf = match header_cf.resolve() {
1934 Some(cf) => cf,
1935 None => peers
1936 .fetch_chain_config(header_cf.commit())
1937 .await
1938 .inspect_err(|err| {
1939 tracing::error!("failed to get chain_config from peers. err: {err:?}");
1940 })?,
1941 };
1942
1943 Ok(cf)
1944 }
1945
1946 #[cfg(any(test, feature = "testing"))]
1947 pub fn mock() -> Self {
1948 use crate::{mock, v0_1::NoStorage};
1949 let chain_config = ChainConfig::default();
1950 let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
1951 .expect("Failed to create L1 client");
1952
1953 let peers = Arc::new(mock::MockStateCatchup::default());
1954 let persistence = NoStorage;
1955
1956 Self::new(
1957 peers,
1958 Arc::new(AsyncMutex::new(persistence)),
1959 l1,
1960 chain_config,
1961 )
1962 }
1963}
1964
1965#[cfg_attr(not(feature = "node"), allow(dead_code))]
1966async fn retry<F, T, E>(
1967 retry_delay: Duration,
1968 max_duration: Duration,
1969 operation_name: &str,
1970 mut operation: F,
1971) -> T
1972where
1973 F: FnMut() -> BoxFuture<'static, Result<T, E>>,
1974 E: std::fmt::Display,
1975{
1976 let start = Instant::now();
1977 loop {
1978 match operation().await {
1979 Ok(result) => return result,
1980 Err(err) => {
1981 if start.elapsed() >= max_duration {
1982 panic!(
1983 r#"
1984 Failed to complete operation `{operation_name}` after `{}`.
1985 error: {err}
1986
1987
1988 This might be caused by:
1989 - The current block range being too large for your RPC provider.
1990 - The event query returning more data than your RPC allows as
1991 some RPC providers limit the number of events returned.
1992 - RPC provider outage
1993
1994 Suggested solution:
1995 - Reduce the value of the environment variable
1996 `ESPRESSO_L1_EVENTS_MAX_BLOCK_RANGE` to query smaller ranges.
1997 - Add multiple RPC providers
1998 - Use a different RPC provider with higher rate limits."#,
1999 format_duration(max_duration)
2000 );
2001 }
2002 tracing::warn!(%err, "Retrying `{operation_name}` after error");
2003 sleep(retry_delay).await;
2004 },
2005 }
2006 }
2007}
2008
2009pub fn calculate_proportion_staked_and_reward_rate(
2019 total_stake: &BigDecimal,
2020 total_supply: &BigDecimal,
2021) -> anyhow::Result<(BigDecimal, BigDecimal)> {
2022 if total_supply.is_zero() {
2023 return Err(anyhow::anyhow!("Total supply cannot be zero"));
2024 }
2025
2026 let proportion_staked = total_stake / total_supply;
2027
2028 if proportion_staked < BigDecimal::zero() || proportion_staked > BigDecimal::one() {
2029 return Err(anyhow::anyhow!("Stake ratio p must be in the range [0, 1]"));
2030 }
2031
2032 let two = BigDecimal::from_u32(2).unwrap();
2033 let min_stake_ratio = BigDecimal::from_str("0.01")?;
2034 let numerator = BigDecimal::from_str("0.03")?;
2035
2036 let denominator = (&two * (&proportion_staked).max(&min_stake_ratio))
2037 .sqrt()
2038 .context("Failed to compute sqrt in R(p)")?;
2039
2040 let reward_rate = numerator / denominator;
2041
2042 tracing::debug!("rp={reward_rate}");
2043
2044 Ok((proportion_staked, reward_rate))
2045}
2046
2047pub(crate) fn compute_block_reward(
2048 epoch: EpochNumber,
2049 total_supply: U256,
2050 total_stake: U256,
2051 avg_block_time_ms: u64,
2052) -> anyhow::Result<RewardAmount> {
2053 let total_stake_bd = BigDecimal::from_str(&total_stake.to_string())?;
2055 let total_supply_bd = BigDecimal::from_str(&(total_supply.to_string()))?;
2056
2057 tracing::debug!(?epoch, "total_stake={total_stake}");
2058 tracing::debug!(?epoch, "total_supply_bd={total_supply_bd}");
2059
2060 let (proportion, reward_rate) =
2061 calculate_proportion_staked_and_reward_rate(&total_stake_bd, &total_supply_bd)?;
2062 let inflation_rate = proportion * reward_rate;
2063
2064 tracing::debug!(?epoch, "inflation_rate={inflation_rate:?}");
2065
2066 let blocks_per_year = MILLISECONDS_PER_YEAR
2067 .checked_div(avg_block_time_ms.into())
2068 .context("avg_block_time_ms is zero")?;
2069
2070 tracing::debug!(?epoch, "blocks_per_year={blocks_per_year:?}");
2071
2072 ensure!(!blocks_per_year.is_zero(), "blocks per year is zero");
2073 let block_reward = (total_supply_bd * inflation_rate) / blocks_per_year;
2074
2075 let block_reward_u256 = U256::from_str(&block_reward.round(0).to_string())?;
2076
2077 Ok(block_reward_u256.into())
2078}
2079
2080#[derive(Error, Debug)]
2081#[cfg_attr(not(feature = "node"), allow(dead_code))]
2083enum GetStakeTablesError {
2084 #[error("Error fetching from L1: {0}")]
2085 L1ClientFetchError(anyhow::Error),
2086}
2087
2088#[cfg(any(test, feature = "testing"))]
2089impl super::v0_3::StakeTable {
2090 pub fn mock(n: u64) -> Self {
2092 use hotshot_types::PeerConfig;
2093
2094 use super::SeqTypes;
2095
2096 (0..n)
2097 .map(|_| PeerConfig::test_default())
2098 .collect::<Vec<PeerConfig<SeqTypes>>>()
2099 .into()
2100 }
2101}
2102
2103#[cfg(any(test, feature = "testing"))]
2104impl DAMembers {
2105 pub fn mock(n: u64) -> Self {
2107 use hotshot_types::PeerConfig;
2108
2109 use super::SeqTypes;
2110
2111 (0..n)
2112 .map(|_| PeerConfig::test_default())
2113 .collect::<Vec<PeerConfig<SeqTypes>>>()
2114 .into()
2115 }
2116}
2117
2118#[cfg(any(test, feature = "testing"))]
2119pub mod testing {
2120 use alloy::primitives::Bytes;
2121 use hotshot_contract_adapter::{
2122 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
2123 stake_table::{StateSignatureSol, sign_address_bls, sign_address_schnorr},
2124 };
2125 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
2126 use rand::{Rng as _, RngCore as _};
2127
2128 use super::*;
2129
2130 #[derive(Debug, Clone)]
2133 pub struct TestValidator {
2134 pub account: Address,
2135 pub bls_vk: G2PointSol,
2136 pub schnorr_vk: EdOnBN254PointSol,
2137 pub commission: u16,
2138 pub bls_sig: G1PointSol,
2139 pub schnorr_sig: Bytes,
2140 pub x25519_key: [u8; 32],
2141 pub p2p_addr: String,
2142 }
2143
2144 impl TestValidator {
2145 pub fn random() -> Self {
2146 let account = Address::random();
2147 let commission = rand::thread_rng().gen_range(0..10000);
2148 Self::random_update_keys(account, commission)
2149 }
2150
2151 pub fn randomize_keys(&self) -> Self {
2152 Self::random_update_keys(self.account, self.commission)
2153 }
2154
2155 pub fn random_update_keys(account: Address, commission: u16) -> Self {
2156 let mut rng = &mut rand::thread_rng();
2157 let mut seed = [0u8; 32];
2158 rng.fill_bytes(&mut seed);
2159 let bls_key_pair = BLSKeyPair::generate(&mut rng);
2160 let bls_sig = sign_address_bls(&bls_key_pair, account);
2161 let schnorr_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
2162 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, account);
2163 let mut x25519_key = [0u8; 32];
2164 rng.fill_bytes(&mut x25519_key);
2165 Self {
2166 account,
2167 bls_vk: bls_key_pair.ver_key().to_affine().into(),
2168 schnorr_vk: schnorr_key_pair.ver_key().to_affine().into(),
2169 commission,
2170 bls_sig: bls_sig.into(),
2171 schnorr_sig: StateSignatureSol::from(schnorr_sig).into(),
2172 x25519_key,
2173 p2p_addr: "127.0.0.1:9000".to_string(),
2174 }
2175 }
2176
2177 pub fn with_x25519_key(mut self, x25519_key: [u8; 32]) -> Self {
2178 self.x25519_key = x25519_key;
2179 self
2180 }
2181
2182 pub fn with_p2p_addr(mut self, p2p_addr: impl Into<String>) -> Self {
2183 self.p2p_addr = p2p_addr.into();
2184 self
2185 }
2186
2187 pub fn x25519_update(&self, x25519_key: [u8; 32]) -> StakeTableEvent {
2188 StakeTableEvent::X25519KeyUpdate(X25519KeyUpdated {
2189 validator: self.account,
2190 x25519Key: alloy::primitives::FixedBytes(x25519_key),
2191 })
2192 }
2193
2194 pub fn p2p_update(&self, p2p_addr: impl Into<String>) -> StakeTableEvent {
2195 StakeTableEvent::P2pAddrUpdate(P2pAddrUpdated {
2196 validator: self.account,
2197 p2pAddr: p2p_addr.into(),
2198 })
2199 }
2200 }
2201
2202 impl From<&TestValidator> for ValidatorRegistered {
2203 fn from(value: &TestValidator) -> Self {
2204 Self {
2205 account: value.account,
2206 blsVk: value.bls_vk,
2207 schnorrVk: value.schnorr_vk,
2208 commission: value.commission,
2209 }
2210 }
2211 }
2212
2213 impl From<&TestValidator> for ValidatorRegisteredV2 {
2214 fn from(value: &TestValidator) -> Self {
2215 Self {
2216 account: value.account,
2217 blsVK: value.bls_vk,
2218 schnorrVK: value.schnorr_vk,
2219 commission: value.commission,
2220 blsSig: value.bls_sig.into(),
2221 schnorrSig: value.schnorr_sig.clone(),
2222 metadataUri: "dummy-meta".to_string(),
2223 }
2224 }
2225 }
2226
2227 impl From<&TestValidator> for ValidatorRegisteredV3 {
2228 fn from(value: &TestValidator) -> Self {
2229 Self {
2230 account: value.account,
2231 blsVK: value.bls_vk,
2232 schnorrVK: value.schnorr_vk,
2233 commission: value.commission,
2234 blsSig: value.bls_sig.into(),
2235 schnorrSig: value.schnorr_sig.clone(),
2236 metadataUri: "dummy-meta".to_string(),
2237 x25519Key: alloy::primitives::FixedBytes(value.x25519_key),
2238 p2pAddr: value.p2p_addr.clone(),
2239 }
2240 }
2241 }
2242
2243 impl From<&TestValidator> for ConsensusKeysUpdated {
2244 fn from(value: &TestValidator) -> Self {
2245 Self {
2246 account: value.account,
2247 blsVK: value.bls_vk,
2248 schnorrVK: value.schnorr_vk,
2249 }
2250 }
2251 }
2252
2253 impl From<&TestValidator> for ConsensusKeysUpdatedV2 {
2254 fn from(value: &TestValidator) -> Self {
2255 Self {
2256 account: value.account,
2257 blsVK: value.bls_vk,
2258 schnorrVK: value.schnorr_vk,
2259 blsSig: value.bls_sig.into(),
2260 schnorrSig: value.schnorr_sig.clone(),
2261 }
2262 }
2263 }
2264
2265 impl From<&TestValidator> for ValidatorExit {
2266 fn from(value: &TestValidator) -> Self {
2267 Self {
2268 validator: value.account,
2269 }
2270 }
2271 }
2272
2273 impl RegisteredValidator<BLSPubKey> {
2274 pub fn mock() -> RegisteredValidator<BLSPubKey> {
2275 let val = TestValidator::random();
2276 let rng = &mut rand::thread_rng();
2277 let mut seed = [1u8; 32];
2278 rng.fill_bytes(&mut seed);
2279 let mut validator_stake = alloy::primitives::U256::from(0);
2280 let mut delegators = HashMap::new();
2281 for _i in 0..=5000 {
2282 let stake: u64 = rng.gen_range(0..10000);
2283 delegators.insert(Address::random(), alloy::primitives::U256::from(stake));
2284 validator_stake += alloy::primitives::U256::from(stake);
2285 }
2286
2287 let stake_table_key = BLSPubKey::try_from(val.bls_vk).expect("valid test BLS key");
2288 let state_ver_key =
2289 SchnorrPubKey::try_from(val.schnorr_vk).expect("valid test Schnorr key");
2290
2291 RegisteredValidator {
2292 account: val.account,
2293 stake_table_key: Some(stake_table_key),
2294 state_ver_key: Some(state_ver_key),
2295 stake: validator_stake,
2296 commission: val.commission,
2297 delegators,
2298 authenticated: true,
2299 x25519_key: None,
2300 p2p_addr: None,
2301 }
2302 }
2303 }
2304
2305 impl AuthenticatedValidator<BLSPubKey> {
2306 pub fn mock() -> AuthenticatedValidator<BLSPubKey> {
2307 RegisteredValidator::mock()
2308 .try_into()
2309 .expect("mock validator is always authenticated")
2310 }
2311
2312 pub fn mock_with_commission(commission: u16) -> AuthenticatedValidator<BLSPubKey> {
2313 let mut inner = RegisteredValidator::mock();
2314 inner.commission = commission;
2315 inner
2316 .try_into()
2317 .expect("mock validator is always authenticated")
2318 }
2319 }
2320}
2321
2322#[cfg(test)]
2323mod tests {
2324 use alloy::{
2325 primitives::{Address, Bytes},
2326 rpc::types::Log,
2327 };
2328 use hotshot_contract_adapter::{
2329 sol_types::{G1PointSol, G2PointSol},
2330 stake_table::{StakeTableContractVersion, sign_address_bls},
2331 };
2332 use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
2333 use pretty_assertions::assert_matches;
2334 use rstest::rstest;
2335 use versions::{
2336 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
2337 };
2338
2339 use super::*;
2340 use crate::{L1ClientOptions, v0::impls::testing::*};
2341
2342 fn zero_g2() -> G2PointSol {
2343 G2PointSol {
2344 x0: U256::ZERO,
2345 x1: U256::ZERO,
2346 y0: U256::ZERO,
2347 y1: U256::ZERO,
2348 }
2349 }
2350
2351 fn zero_g1() -> G1PointSol {
2352 G1PointSol {
2353 x: U256::ZERO,
2354 y: U256::ZERO,
2355 }
2356 }
2357
2358 #[test_log::test]
2363 fn test_block_range_chunks_never_exceed_chunk_size() {
2364 const MAX_RANGE: u64 = 10_000;
2365 const INIT_BLOCK: u64 = 1_000_000;
2366 const LOWEST_BLOCK: u64 = INIT_BLOCK - 200_000 + 1;
2367
2368 let width = |(from, to): &(u64, u64)| to - from + 1;
2369
2370 let descending: Vec<_> =
2371 Fetcher::block_range_chunks_rev(LOWEST_BLOCK, INIT_BLOCK, MAX_RANGE).collect();
2372
2373 assert_eq!(descending[0], (INIT_BLOCK - MAX_RANGE + 1, INIT_BLOCK));
2374 assert_eq!(descending.last().unwrap().0, LOWEST_BLOCK);
2375 for chunk in &descending {
2376 assert_eq!(width(chunk), MAX_RANGE, "chunk {chunk:?} is missized");
2377 }
2378 for pair in descending.windows(2) {
2379 assert_eq!(pair[0].0, pair[1].1 + 1, "gap or overlap at {pair:?}");
2380 }
2381
2382 let ascending: Vec<_> =
2383 Fetcher::block_range_chunks(LOWEST_BLOCK, INIT_BLOCK, MAX_RANGE).collect();
2384 for chunk in &ascending {
2385 assert!(
2386 width(chunk) <= MAX_RANGE,
2387 "chunk {chunk:?} exceeds {MAX_RANGE}"
2388 );
2389 }
2390
2391 assert_eq!(
2393 Fetcher::block_range_chunks_rev(0, 5, MAX_RANGE).collect::<Vec<_>>(),
2394 vec![(0, 5)]
2395 );
2396 assert_eq!(
2397 Fetcher::block_range_chunks_rev(0, MAX_RANGE, MAX_RANGE).collect::<Vec<_>>(),
2398 vec![(1, MAX_RANGE), (0, 0)]
2399 );
2400 }
2401
2402 #[test_log::test]
2403 fn test_from_l1_events() -> anyhow::Result<()> {
2404 let val_1 = TestValidator::random();
2406 let val_1_new_keys = val_1.randomize_keys();
2407 let val_2 = TestValidator::random();
2408 let val_2_new_keys = val_2.randomize_keys();
2409 let delegator = Address::random();
2410 let mut events: Vec<StakeTableEvent> = [
2411 ValidatorRegistered::from(&val_1).into(),
2412 ValidatorRegisteredV2::from(&val_2).into(),
2413 Delegated {
2414 delegator,
2415 validator: val_1.account,
2416 amount: U256::from(10),
2417 }
2418 .into(),
2419 ConsensusKeysUpdated::from(&val_1_new_keys).into(),
2420 ConsensusKeysUpdatedV2::from(&val_2_new_keys).into(),
2421 Undelegated {
2422 delegator,
2423 validator: val_1.account,
2424 amount: U256::from(7),
2425 }
2426 .into(),
2427 Delegated {
2429 delegator,
2430 validator: val_1.account,
2431 amount: U256::from(5),
2432 }
2433 .into(),
2434 Delegated {
2436 delegator: Address::random(),
2437 validator: val_2.account,
2438 amount: U256::from(3),
2439 }
2440 .into(),
2441 ]
2442 .to_vec();
2443
2444 let validators_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
2445 let st = validators_set.active_validators;
2446 let st_val_1 = st.get(&val_1.account).unwrap();
2447 assert_eq!(st_val_1.stake, U256::from(8));
2449 assert_eq!(st_val_1.commission, val_1.commission);
2450 assert_eq!(st_val_1.delegators.len(), 1);
2451 assert_eq!(*st_val_1.delegators.get(&delegator).unwrap(), U256::from(8));
2453
2454 let st_val_2 = st.get(&val_2.account).unwrap();
2455 assert_eq!(st_val_2.stake, U256::from(3));
2456 assert_eq!(st_val_2.commission, val_2.commission);
2457 assert_eq!(st_val_2.delegators.len(), 1);
2458
2459 events.push(ValidatorExit::from(&val_1).into());
2460
2461 let validator_set = ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION)?;
2462 let st = validator_set.active_validators;
2463 assert_eq!(st.get(&val_1.account), None);
2465
2466 let st_val_2 = st.get(&val_2.account).unwrap();
2468 assert_eq!(st_val_2.stake, U256::from(3));
2469 assert_eq!(st_val_2.commission, val_2.commission);
2470 assert_eq!(st_val_2.delegators.len(), 1);
2471
2472 events.push(ValidatorExit::from(&val_2).into());
2474
2475 assert!(ValidatorSet::from_l1_events(events.iter().cloned(), EPOCH_VERSION).is_err());
2477
2478 Ok(())
2479 }
2480
2481 #[test]
2482 fn test_from_l1_events_failures() -> anyhow::Result<()> {
2483 let val = TestValidator::random();
2484 let delegator = Address::random();
2485
2486 let register: StakeTableEvent = ValidatorRegistered::from(&val).into();
2487 let register_v2: StakeTableEvent = ValidatorRegisteredV2::from(&val).into();
2488 let delegate: StakeTableEvent = Delegated {
2489 delegator,
2490 validator: val.account,
2491 amount: U256::from(10),
2492 }
2493 .into();
2494 let key_update: StakeTableEvent = ConsensusKeysUpdated::from(&val).into();
2495 let key_update_v2: StakeTableEvent = ConsensusKeysUpdatedV2::from(&val).into();
2496 let undelegate: StakeTableEvent = Undelegated {
2497 delegator,
2498 validator: val.account,
2499 amount: U256::from(7),
2500 }
2501 .into();
2502
2503 let exit: StakeTableEvent = ValidatorExit::from(&val).into();
2504
2505 let cases = [
2506 vec![exit],
2507 vec![undelegate.clone()],
2508 vec![delegate.clone()],
2509 vec![key_update],
2510 vec![key_update_v2],
2511 vec![register.clone(), register.clone()],
2512 vec![register_v2.clone(), register_v2.clone()],
2513 vec![register.clone(), register_v2.clone()],
2514 vec![register_v2.clone(), register.clone()],
2515 vec![
2516 register,
2517 delegate.clone(),
2518 undelegate.clone(),
2519 undelegate.clone(),
2520 ],
2521 vec![register_v2, delegate, undelegate.clone(), undelegate],
2522 ];
2523
2524 for events in cases.iter() {
2525 let res = validators_from_l1_events(events.iter().cloned());
2529 assert!(
2530 res.is_err(),
2531 "events {res:?}, not a valid sequence of events"
2532 );
2533 }
2534 Ok(())
2535 }
2536
2537 #[test]
2538 fn test_validators_selection() {
2539 let mut candidates = IndexMap::new();
2540 let mut highest_stake = alloy::primitives::U256::ZERO;
2541
2542 for _i in 0..3000 {
2543 let candidate = RegisteredValidator::mock();
2544 candidates.insert(candidate.account, candidate.clone());
2545
2546 if candidate.stake > highest_stake {
2547 highest_stake = candidate.stake;
2548 }
2549 }
2550
2551 let minimum_stake = highest_stake / U256::from(VID_TARGET_TOTAL_STAKE);
2552
2553 let selected_validators = select_active_validator_set(&candidates, EPOCH_VERSION)
2554 .expect("Failed to select validators");
2555 assert!(
2556 selected_validators.len() <= MAX_VALIDATORS,
2557 "validators len is {}, expected at most {MAX_VALIDATORS}",
2558 selected_validators.len()
2559 );
2560
2561 let mut selected_validators_highest_stake = alloy::primitives::U256::ZERO;
2562 for (address, validator) in &selected_validators {
2564 assert!(
2565 validator.stake >= minimum_stake,
2566 "Validator {:?} has stake below minimum: {}",
2567 address,
2568 validator.stake
2569 );
2570
2571 if validator.stake > selected_validators_highest_stake {
2572 selected_validators_highest_stake = validator.stake;
2573 }
2574 }
2575 }
2576
2577 #[rstest::rstest]
2580 fn test_regression_non_unique_bls_keys_not_discarded(
2581 #[values(
2582 StakeTableContractVersion::V1,
2583 StakeTableContractVersion::V2,
2584 StakeTableContractVersion::V3
2585 )]
2586 version: StakeTableContractVersion,
2587 ) {
2588 let val = TestValidator::random();
2589 let register: StakeTableEvent = match version {
2590 StakeTableContractVersion::V1 => ValidatorRegistered::from(&val).into(),
2591 StakeTableContractVersion::V2 => ValidatorRegisteredV2::from(&val).into(),
2592 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&val).into()),
2593 };
2594 let delegate: StakeTableEvent = Delegated {
2595 delegator: Address::random(),
2596 validator: val.account,
2597 amount: U256::from(10),
2598 }
2599 .into();
2600
2601 assert!(
2603 ValidatorSet::from_l1_events(
2604 vec![register.clone(), delegate.clone()].into_iter(),
2605 EPOCH_VERSION,
2606 )
2607 .is_ok()
2608 );
2609
2610 let key_update = ConsensusKeysUpdated::from(&val).into();
2612 let err = ValidatorSet::from_l1_events(
2613 vec![register, delegate, key_update].into_iter(),
2614 EPOCH_VERSION,
2615 )
2616 .unwrap_err();
2617
2618 let bls = BLSPubKey::try_from(val.bls_vk).expect("valid test BLS key");
2619 assert!(matches!(err, StakeTableError::BlsKeyAlreadyUsed(addr) if addr == bls.to_string()));
2620 }
2621
2622 #[test]
2625 fn test_regression_reregister_eth_account() {
2626 let val1 = TestValidator::random();
2627 let val2 = val1.randomize_keys();
2628 let account = val1.account;
2629
2630 let register1 = ValidatorRegisteredV2::from(&val1).into();
2631 let deregister1 = ValidatorExit::from(&val1).into();
2632 let register2 = ValidatorRegisteredV2::from(&val2).into();
2633 let events = [register1, deregister1, register2];
2634 let error = validators_from_l1_events(events.iter().cloned()).unwrap_err();
2635 assert_matches!(error, StakeTableError::ValidatorAlreadyExited(addr) if addr == account);
2636 }
2637
2638 #[test]
2639 fn test_display_log() {
2640 let serialized = r#"{"address":"0x0000000000000000000000000000000000000069",
2641 "topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],
2642 "data":"0x69",
2643 "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2644 "blockNumber":"0x69","blockTimestamp":"0x69",
2645 "transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069",
2646 "transactionIndex":"0x69","logIndex":"0x70","removed":false}"#;
2647 let log: Log = serde_json::from_str(serialized).unwrap();
2648 assert_eq!(
2649 log.display(),
2650 "Log(block=105,index=112,\
2651 transaction_hash=0x0000000000000000000000000000000000000000000000000000000000000069)"
2652 )
2653 }
2654
2655 #[rstest]
2656 #[case::v1(StakeTableContractVersion::V1)]
2657 #[case::v2(StakeTableContractVersion::V2)]
2658 #[case::v3(StakeTableContractVersion::V3)]
2659 fn test_register_validator(#[case] version: StakeTableContractVersion) {
2660 let mut state = StakeTableState::default();
2661 let validator = TestValidator::random();
2662
2663 let event = match version {
2664 StakeTableContractVersion::V1 => StakeTableEvent::Register((&validator).into()),
2665 StakeTableContractVersion::V2 => StakeTableEvent::RegisterV2((&validator).into()),
2666 StakeTableContractVersion::V3 => StakeTableEvent::RegisterV3((&validator).into()),
2667 };
2668
2669 state.apply_event(event).unwrap().unwrap();
2670
2671 let stored = state.validators.get(&validator.account).unwrap();
2672 assert_eq!(stored.account, validator.account);
2673 }
2674
2675 #[rstest]
2676 #[case::v1(StakeTableContractVersion::V1)]
2677 #[case::v2(StakeTableContractVersion::V2)]
2678 #[case::v3(StakeTableContractVersion::V3)]
2679 fn test_validator_already_registered(#[case] version: StakeTableContractVersion) {
2680 let mut stake_table_state = StakeTableState::default();
2681
2682 let test_validator = TestValidator::random();
2683
2684 match version {
2686 StakeTableContractVersion::V1 => {
2687 stake_table_state.apply_event(StakeTableEvent::Register((&test_validator).into()))
2688 },
2689 StakeTableContractVersion::V2 => {
2690 stake_table_state.apply_event(StakeTableEvent::RegisterV2((&test_validator).into()))
2691 },
2692 StakeTableContractVersion::V3 => {
2693 stake_table_state.apply_event(StakeTableEvent::RegisterV3((&test_validator).into()))
2694 },
2695 }
2696 .unwrap()
2697 .unwrap(); let v1_already_registered_result = stake_table_state
2701 .clone()
2702 .apply_event(StakeTableEvent::Register((&test_validator).into()));
2703
2704 pretty_assertions::assert_matches!(
2705 v1_already_registered_result, Err(StakeTableError::AlreadyRegistered(account))
2706 if account == test_validator.account,
2707 "Expected AlreadyRegistered error. version ={version:?} result={v1_already_registered_result:?}",
2708 );
2709
2710 let v2_already_registered_result = stake_table_state
2712 .clone()
2713 .apply_event(StakeTableEvent::RegisterV2((&test_validator).into()));
2714
2715 pretty_assertions::assert_matches!(
2716 v2_already_registered_result,
2717 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2718 "Expected AlreadyRegistered error. version ={version:?} result={v2_already_registered_result:?}",
2719
2720 );
2721
2722 let v3_already_registered_result =
2725 stake_table_state
2726 .clone()
2727 .apply_event(StakeTableEvent::RegisterV3(
2728 (&test_validator
2729 .clone()
2730 .with_x25519_key([43u8; 32])
2731 .with_p2p_addr("127.0.0.1:9001"))
2732 .into(),
2733 ));
2734
2735 pretty_assertions::assert_matches!(
2736 v3_already_registered_result,
2737 Err(StakeTableError::AlreadyRegistered(account)) if account == test_validator.account,
2738 "Expected AlreadyRegistered error. version ={version:?} result={v3_already_registered_result:?}",
2739 );
2740 }
2741
2742 #[test]
2743 fn test_register_validator_v2_auth_fails_marks_as_unauthenticated() {
2744 let mut state = StakeTableState::default();
2745 let mut val = TestValidator::random();
2746 val.bls_sig = Default::default();
2747 let event = StakeTableEvent::RegisterV2((&val).into());
2748
2749 let result = state.apply_event(event);
2750 assert!(
2751 result.is_ok(),
2752 "Validator with invalid auth should still be accepted"
2753 );
2754
2755 let validator = state
2756 .validators()
2757 .get(&val.account)
2758 .expect("validator should exist");
2759 assert!(
2760 !validator.authenticated,
2761 "Validator should be marked as not authenticated"
2762 );
2763
2764 let event = StakeTableEvent::Delegate(Delegated {
2765 delegator: Address::random(),
2766 validator: val.account,
2767 amount: U256::from(100),
2768 });
2769 state.apply_event(event).unwrap().unwrap();
2770
2771 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
2772 match active {
2773 Err(_) => {}, Ok(map) => {
2775 assert!(
2776 map.get(&val.account).is_none(),
2777 "Unauthenticated validator should not be in active set"
2778 );
2779 },
2780 }
2781 }
2782
2783 #[test]
2784 fn test_register_v2_bad_sig_preserves_parsed_bls_key() {
2785 let val = TestValidator::random();
2786 let other = TestValidator::random();
2787 let bad_val = TestValidator {
2788 bls_sig: other.bls_sig,
2789 ..val.clone()
2790 };
2791
2792 let mut state = StakeTableState::default();
2793 state
2794 .apply_event(StakeTableEvent::RegisterV2((&bad_val).into()))
2795 .expect("no fatal error")
2796 .expect("registered unauthenticated");
2797
2798 let registered = state.validators().get(&val.account).expect("present");
2799 assert!(!registered.authenticated);
2800 let expected_bls = BLSPubKey::try_from(val.bls_vk).expect("valid bls key");
2801 assert_eq!(registered.stake_table_key.as_ref(), Some(&expected_bls));
2802 }
2803
2804 #[test]
2805 fn test_register_v3_bad_sig_preserves_parsed_bls_key() {
2806 let val = TestValidator::random();
2807 let other = TestValidator::random();
2808 let bad_val = TestValidator {
2809 bls_sig: other.bls_sig,
2810 ..val.clone()
2811 };
2812
2813 let mut state = StakeTableState::default();
2814 state
2815 .apply_event(StakeTableEvent::RegisterV3((&bad_val).into()))
2816 .expect("no fatal error")
2817 .expect("registered unauthenticated");
2818
2819 let registered = state.validators().get(&val.account).expect("present");
2820 assert!(!registered.authenticated);
2821 let expected_bls = BLSPubKey::try_from(val.bls_vk).expect("valid bls key");
2822 assert_eq!(registered.stake_table_key.as_ref(), Some(&expected_bls));
2823 }
2824
2825 #[test]
2826 fn test_authenticated_validator_deserialize_rejects_unauthenticated() {
2827 let mut validator = RegisteredValidator::<BLSPubKey>::mock();
2828 validator.authenticated = false;
2829
2830 let json = serde_json::to_string(&validator).unwrap();
2831 let result: Result<AuthenticatedValidator<BLSPubKey>, _> = serde_json::from_str(&json);
2832
2833 assert!(result.is_err());
2834 let err = result.unwrap_err().to_string();
2835 assert!(
2836 err.contains("cannot deserialize unauthenticated validator"),
2837 "unexpected error: {err}"
2838 );
2839 }
2840
2841 #[rstest]
2842 #[case::v1(StakeTableContractVersion::V1)]
2843 #[case::v2(StakeTableContractVersion::V2)]
2844 #[case::v3(StakeTableContractVersion::V3)]
2845 fn test_deregister_validator(#[case] version: StakeTableContractVersion) {
2846 let mut state = StakeTableState::default();
2847 let val = TestValidator::random();
2848
2849 let reg = StakeTableEvent::Register((&val).into());
2850 state.apply_event(reg).unwrap().unwrap();
2851
2852 let dereg = match version {
2853 StakeTableContractVersion::V1 => StakeTableEvent::Deregister((&val).into()),
2854 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2855 StakeTableEvent::DeregisterV2(ValidatorExitV2 {
2856 validator: val.account,
2857 unlocksAt: U256::from(1000u64),
2858 })
2859 },
2860 };
2861 state.apply_event(dereg).unwrap().unwrap();
2862 assert!(!state.validators.contains_key(&val.account));
2863 }
2864
2865 #[rstest]
2866 #[case::v1(StakeTableContractVersion::V1)]
2867 #[case::v2(StakeTableContractVersion::V2)]
2868 #[case::v3(StakeTableContractVersion::V3)]
2869 fn test_delegate_and_undelegate(#[case] version: StakeTableContractVersion) {
2870 let mut state = StakeTableState::default();
2871 let val = TestValidator::random();
2872 state
2873 .apply_event(StakeTableEvent::Register((&val).into()))
2874 .unwrap()
2875 .unwrap();
2876
2877 let delegator = Address::random();
2878 let amount = U256::from(1000);
2879 let delegate_event = StakeTableEvent::Delegate(Delegated {
2880 delegator,
2881 validator: val.account,
2882 amount,
2883 });
2884 state.apply_event(delegate_event).unwrap().unwrap();
2885
2886 let validator = state.validators.get(&val.account).unwrap();
2887 assert_eq!(validator.delegators.get(&delegator).cloned(), Some(amount));
2888
2889 let undelegate_event = match version {
2890 StakeTableContractVersion::V1 => StakeTableEvent::Undelegate(Undelegated {
2891 delegator,
2892 validator: val.account,
2893 amount,
2894 }),
2895 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2896 StakeTableEvent::UndelegateV2(UndelegatedV2 {
2897 delegator,
2898 validator: val.account,
2899 amount,
2900 unlocksAt: U256::from(2000u64),
2901 undelegationId: 1,
2902 })
2903 },
2904 };
2905 state.apply_event(undelegate_event).unwrap().unwrap();
2906 let validator = state.validators.get(&val.account).unwrap();
2907 assert!(!validator.delegators.contains_key(&delegator));
2908 }
2909
2910 #[rstest]
2911 #[case::v1(StakeTableContractVersion::V1)]
2912 #[case::v2(StakeTableContractVersion::V2)]
2913 #[case::v3(StakeTableContractVersion::V3)]
2914 fn test_key_update_event(#[case] version: StakeTableContractVersion) {
2915 let mut state = StakeTableState::default();
2916 let val = TestValidator::random();
2917
2918 state
2920 .apply_event(StakeTableEvent::Register((&val).into()))
2921 .unwrap()
2922 .unwrap();
2923
2924 let new_keys = val.randomize_keys();
2925
2926 let event = match version {
2927 StakeTableContractVersion::V1 => StakeTableEvent::KeyUpdate((&new_keys).into()),
2928 StakeTableContractVersion::V2 | StakeTableContractVersion::V3 => {
2929 StakeTableEvent::KeyUpdateV2((&new_keys).into())
2930 },
2931 };
2932
2933 state.apply_event(event).unwrap().unwrap();
2934
2935 let updated = state.validators.get(&val.account).unwrap();
2936 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).expect("valid test BLS key");
2937 assert_eq!(updated.stake_table_key.as_ref(), Some(&expected_bls));
2938 let expected_schnorr =
2939 SchnorrPubKey::try_from(new_keys.schnorr_vk).expect("valid test Schnorr key");
2940 assert_eq!(updated.state_ver_key.as_ref(), Some(&expected_schnorr));
2941 }
2942
2943 #[test]
2944 fn test_duplicate_bls_key() {
2945 let mut state = StakeTableState::default();
2946 let val = TestValidator::random();
2947 let event1 = StakeTableEvent::Register((&val).into());
2948 let mut val2 = TestValidator::random();
2949 val2.bls_vk = val.bls_vk;
2950 val2.account = Address::random();
2951
2952 let event2 = StakeTableEvent::Register((&val2).into());
2953 state.apply_event(event1).unwrap().unwrap();
2954 let result = state.apply_event(event2);
2955
2956 let expected_bls_key = BLSPubKey::try_from(val.bls_vk)
2957 .expect("valid test BLS key")
2958 .to_string();
2959
2960 assert_matches!(
2961 result,
2962 Err(StakeTableError::BlsKeyAlreadyUsed(key))
2963 if key == expected_bls_key,
2964 "Expected BlsKeyAlreadyUsed({expected_bls_key}), but got: {result:?}",
2965 );
2966 }
2967
2968 #[test]
2969 fn test_duplicate_schnorr_key() {
2970 let mut state = StakeTableState::default();
2971 let val = TestValidator::random();
2972 let event1 = StakeTableEvent::Register((&val).into());
2973 let mut val2 = TestValidator::random();
2974 val2.schnorr_vk = val.schnorr_vk;
2975 val2.account = Address::random();
2976 val2.bls_vk = val2.randomize_keys().bls_vk;
2977
2978 let event2 = StakeTableEvent::Register((&val2).into());
2979 state.apply_event(event1).unwrap().unwrap();
2980 let result = state.apply_event(event2);
2981
2982 let schnorr = SchnorrPubKey::try_from(val.schnorr_vk).expect("valid test Schnorr key");
2983 assert_matches!(
2984 result,
2985 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
2986 if key == schnorr.to_string(),
2987 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
2988
2989 );
2990 }
2991
2992 #[test]
2993 fn test_duplicate_schnorr_key_v2_during_update() {
2994 let mut state = StakeTableState::default();
2995
2996 let val1 = TestValidator::random();
2997
2998 let mut rng = &mut rand::thread_rng();
2999 let bls_key_pair = BLSKeyPair::generate(&mut rng);
3000
3001 let val2 = TestValidator {
3002 bls_vk: bls_key_pair.ver_key().to_affine().into(),
3003 bls_sig: sign_address_bls(&bls_key_pair, val1.account).into(),
3004 ..val1.clone()
3005 };
3006 let event1 = StakeTableEvent::RegisterV2((&val1).into());
3007 let event2 = StakeTableEvent::KeyUpdateV2((&val2).into());
3008
3009 state.apply_event(event1).unwrap().unwrap();
3010 let result = state.apply_event(event2);
3011
3012 let schnorr = SchnorrPubKey::try_from(val1.schnorr_vk).expect("valid test Schnorr key");
3013 assert_matches!(
3014 result,
3015 Ok(Err(ExpectedStakeTableError::SchnorrKeyAlreadyUsed(key)))
3016 if key == schnorr.to_string(),
3017 "Expected SchnorrKeyAlreadyUsed({schnorr}), but got: {result:?}",
3018 );
3019 }
3020
3021 #[test]
3022 fn test_register_and_deregister_validator() {
3023 let mut state = StakeTableState::default();
3024 let validator = TestValidator::random();
3025 let event = StakeTableEvent::Register((&validator).into());
3026 state.apply_event(event).unwrap().unwrap();
3027
3028 let deregister_event = StakeTableEvent::Deregister((&validator).into());
3029 assert!(state.apply_event(deregister_event).unwrap().is_ok());
3030 }
3031
3032 #[test]
3033 fn test_commission_validation_exceeds_basis_points() {
3034 let validator = TestValidator::random();
3036 let mut stake_table = StakeTableState::default();
3037
3038 let registration_event = ValidatorRegistered::from(&validator).into();
3040 stake_table
3041 .apply_event(registration_event)
3042 .unwrap()
3043 .unwrap();
3044
3045 let valid_commission_event = CommissionUpdated {
3047 validator: validator.account,
3048 timestamp: Default::default(),
3049 oldCommission: 0,
3050 newCommission: COMMISSION_BASIS_POINTS, }
3052 .into();
3053 stake_table
3054 .apply_event(valid_commission_event)
3055 .unwrap()
3056 .unwrap();
3057
3058 let invalid_commission = COMMISSION_BASIS_POINTS + 1;
3059 let invalid_commission_event = CommissionUpdated {
3060 validator: validator.account,
3061 timestamp: Default::default(),
3062 oldCommission: 0,
3063 newCommission: invalid_commission,
3064 }
3065 .into();
3066
3067 let err = stake_table
3068 .apply_event(invalid_commission_event)
3069 .unwrap_err();
3070
3071 assert_matches!(
3072 err,
3073 StakeTableError::InvalidCommission(addr, invalid_commission)
3074 if addr == addr && invalid_commission == invalid_commission);
3075 }
3076
3077 #[test]
3078 fn test_delegate_zero_amount_is_rejected() {
3079 let mut state = StakeTableState::default();
3080 let validator = TestValidator::random();
3081 let account = validator.account;
3082 state
3083 .apply_event(StakeTableEvent::Register((&validator).into()))
3084 .unwrap()
3085 .unwrap();
3086
3087 let delegator = Address::random();
3088 let amount = U256::ZERO;
3089 let event = StakeTableEvent::Delegate(Delegated {
3090 delegator,
3091 validator: account,
3092 amount,
3093 });
3094 let result = state.apply_event(event);
3095
3096 assert_matches!(
3097 result,
3098 Err(StakeTableError::ZeroDelegatorStake(addr))
3099 if addr == delegator,
3100 "delegator stake is zero"
3101
3102 );
3103 }
3104
3105 #[test]
3106 fn test_undelegate_more_than_stake_fails() {
3107 let mut state = StakeTableState::default();
3108 let validator = TestValidator::random();
3109 let account = validator.account;
3110 state
3111 .apply_event(StakeTableEvent::Register((&validator).into()))
3112 .unwrap()
3113 .unwrap();
3114
3115 let delegator = Address::random();
3116 let event = StakeTableEvent::Delegate(Delegated {
3117 delegator,
3118 validator: account,
3119 amount: U256::from(10u64),
3120 });
3121 state.apply_event(event).unwrap().unwrap();
3122
3123 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3124 delegator,
3125 validator: account,
3126 amount: U256::from(20u64),
3127 }));
3128 assert_matches!(
3129 result,
3130 Err(StakeTableError::InsufficientStake),
3131 "Expected InsufficientStake error, got: {result:?}",
3132 );
3133 }
3134
3135 #[test]
3136 fn test_apply_event_does_not_modify_state_on_error() {
3137 let mut state = StakeTableState::default();
3138 let validator = TestValidator::random();
3139 let delegator = Address::random();
3140
3141 state
3142 .apply_event(StakeTableEvent::Register((&validator).into()))
3143 .unwrap()
3144 .unwrap();
3145
3146 let state_before = state.clone();
3148 let result = state.apply_event(StakeTableEvent::Register((&validator).into()));
3149 assert_matches!(result, Err(StakeTableError::AlreadyRegistered(_)));
3150 assert_eq!(
3151 state, state_before,
3152 "State should not change on AlreadyRegistered error"
3153 );
3154
3155 let state_before = state.clone();
3157 let mut validator2 = TestValidator::random();
3158 validator2.bls_vk = validator.bls_vk; let result = state.apply_event(StakeTableEvent::Register((&validator2).into()));
3160 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
3161 assert_eq!(
3162 state, state_before,
3163 "State should not change on BlsKeyAlreadyUsed error"
3164 );
3165
3166 let state_before = state.clone();
3168 let nonexistent_validator = TestValidator::random();
3169 let result =
3170 state.apply_event(StakeTableEvent::Deregister((&nonexistent_validator).into()));
3171 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
3172 assert_eq!(
3173 state, state_before,
3174 "State should not change on ValidatorNotFound error"
3175 );
3176
3177 let state_before = state.clone();
3179 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3180 delegator: Address::random(),
3181 validator: Address::random(),
3182 amount: U256::from(100u64),
3183 }));
3184 assert_matches!(result, Err(StakeTableError::ValidatorNotFound(_)));
3185 assert_eq!(
3186 state, state_before,
3187 "State should not change on ValidatorNotFound error for Undelegate"
3188 );
3189
3190 state
3191 .apply_event(StakeTableEvent::Delegate(Delegated {
3192 delegator,
3193 validator: validator.account,
3194 amount: U256::from(100u64),
3195 }))
3196 .unwrap()
3197 .unwrap();
3198
3199 let state_before = state.clone();
3201 let non_existent_delegator = Address::random();
3202 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3203 delegator: non_existent_delegator,
3204 validator: validator.account,
3205 amount: U256::from(50u64),
3206 }));
3207 assert_matches!(result, Err(StakeTableError::DelegatorNotFound(_)));
3208 assert_eq!(
3209 state, state_before,
3210 "State should not change on DelegatorNotFound error"
3211 );
3212
3213 let state_before = state.clone();
3215 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3216 delegator,
3217 validator: validator.account,
3218 amount: U256::from(200u64),
3219 }));
3220 assert_matches!(result, Err(StakeTableError::InsufficientStake));
3221 assert_eq!(
3222 state, state_before,
3223 "State should not change on InsufficientStake error"
3224 );
3225
3226 let validator2 = TestValidator::random();
3228 let delegator2 = Address::random();
3229
3230 state
3231 .apply_event(StakeTableEvent::Register((&validator2).into()))
3232 .unwrap()
3233 .unwrap();
3234
3235 state
3236 .apply_event(StakeTableEvent::Delegate(Delegated {
3237 delegator: delegator2,
3238 validator: validator2.account,
3239 amount: U256::from(50u64),
3240 }))
3241 .unwrap()
3242 .unwrap();
3243 let state_before = state.clone();
3244 let result = state.apply_event(StakeTableEvent::Undelegate(Undelegated {
3245 delegator: delegator2,
3246 validator: validator2.account,
3247 amount: U256::from(100u64),
3248 }));
3249 assert_matches!(result, Err(StakeTableError::InsufficientStake));
3250 assert_eq!(state, state_before,);
3251
3252 let state_before = state.clone();
3254 let result = state.apply_event(StakeTableEvent::Delegate(Delegated {
3255 delegator: Address::random(),
3256 validator: validator.account,
3257 amount: U256::ZERO,
3258 }));
3259 assert_matches!(result, Err(StakeTableError::ZeroDelegatorStake(_)));
3260 assert_eq!(
3261 state, state_before,
3262 "State should not change on ZeroDelegatorStake error"
3263 );
3264 }
3265
3266 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3267 async fn test_decaf_stake_table() {
3268 let events_json =
3304 std::fs::read_to_string("../../../data/v3/decaf_stake_table_events.json").unwrap();
3305 let events: Vec<(EventKey, StakeTableEvent)> = serde_json::from_str(&events_json).unwrap();
3306
3307 let reconstructed_stake_table =
3309 ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), EPOCH_VERSION)
3310 .unwrap()
3311 .active_validators;
3312
3313 let stake_table_json =
3314 std::fs::read_to_string("../../../data/v3/decaf_stake_table.json").unwrap();
3315 let expected: AuthenticatedValidatorMap = serde_json::from_str(&stake_table_json).unwrap();
3316
3317 assert_eq!(
3318 reconstructed_stake_table, expected,
3319 "Stake table reconstructed from events does not match the expected stake table "
3320 );
3321 }
3322
3323 async fn snapshot_stake_table_commit(
3324 network: &str,
3325 rpc_url: &str,
3326 contract: &str,
3327 to_block: u64,
3328 ) {
3329 let l1 = L1ClientOptions {
3330 l1_events_max_retry_duration: Duration::from_secs(120),
3331 l1_events_max_block_range: 10_000,
3332 l1_retry_delay: Duration::from_secs(2),
3333 ..Default::default()
3334 }
3335 .connect(vec![rpc_url.parse().unwrap()])
3336 .expect("unable to construct l1 client");
3337
3338 let events =
3339 Fetcher::fetch_events_from_contract(l1, contract.parse().unwrap(), None, to_block)
3340 .await
3341 .unwrap();
3342
3343 let validator_set =
3344 ValidatorSet::from_l1_events(events.into_iter().map(|(_, e)| e), EPOCH_VERSION)
3345 .expect("failed to build validator set");
3346
3347 let active_as_registered = to_registered_validator_map(validator_set.active_validators());
3348 let active_state = StakeTableState::new(
3349 active_as_registered,
3350 Default::default(),
3351 Default::default(),
3352 Default::default(),
3353 Default::default(),
3354 );
3355 let active_commit = active_state.commit();
3356
3357 let summary = format!(
3358 "network: {network}\nto_block: {to_block}\nstake_table_contract: \
3359 {contract}\nstake_table_hash: {}\nactive_validators_commit: {}\nall_validators: \
3360 {}\nactive_validators: {}\n",
3361 validator_set
3362 .stake_table_hash()
3363 .expect("stake_table_hash should be set"),
3364 active_commit,
3365 validator_set.all_validators().len(),
3366 validator_set.active_validators().len(),
3367 );
3368
3369 let mut settings = insta::Settings::clone_current();
3370 let data_dir = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
3371 .join("../../../data/insta_snapshots");
3372 settings.set_snapshot_path(data_dir);
3373 settings.set_prepend_module_to_snapshot(false);
3374 settings.bind(|| {
3375 insta::assert_snapshot!(format!("{network}_stake_table_snapshot"), summary);
3376 });
3377 }
3378
3379 #[ignore = "talks to public Sepolia RPC"]
3380 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3381 async fn snapshot_decaf_stake_table_commit() {
3382 snapshot_stake_table_commit(
3383 "decaf",
3384 "https://ethereum-sepolia.publicnode.com",
3385 "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037",
3386 10_935_000,
3387 )
3388 .await;
3389 }
3390
3391 #[ignore = "talks to public Ethereum mainnet RPC"]
3392 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3393 async fn snapshot_mainnet_stake_table_commit() {
3394 snapshot_stake_table_commit(
3395 "mainnet",
3396 "https://ethereum-rpc.publicnode.com",
3397 "0xcef474d372b5b09defe2af187bf17338dc704451",
3398 25_188_000,
3399 )
3400 .await;
3401 }
3402
3403 async fn assert_known_initial_supply_matches_l1(
3410 rpc_url: &str,
3411 chain_id: ChainId,
3412 stake_table_contract: Address,
3413 ) {
3414 let expected = known_initial_supply(chain_id, stake_table_contract)
3415 .expect("stake table contract must have a hardcoded initial supply");
3416
3417 let l1 = L1ClientOptions {
3418 l1_events_max_retry_duration: Duration::from_secs(120),
3419 l1_events_max_block_range: 10_000,
3420 l1_retry_delay: Duration::from_secs(2),
3421 ..Default::default()
3422 }
3423 .connect(vec![rpc_url.parse().unwrap()])
3424 .expect("unable to construct l1 client");
3425
3426 let fetcher = Fetcher::new(
3427 Arc::new(crate::mock::MockStateCatchup::default()),
3428 Arc::new(AsyncMutex::new(crate::v0_1::NoStorage)),
3429 l1,
3430 ChainConfig {
3431 chain_id,
3432 stake_table_contract: Some(stake_table_contract),
3433 ..Default::default()
3434 },
3435 );
3436
3437 let fetched = fetcher
3438 .fetch_and_update_initial_supply()
3439 .await
3440 .expect("failed to fetch initial supply from L1");
3441
3442 assert_eq!(
3443 fetched, expected,
3444 "hardcoded initial supply for {stake_table_contract} does not match L1"
3445 );
3446 }
3447
3448 #[ignore = "talks to public Ethereum mainnet RPC"]
3454 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3455 async fn fetch_initial_supply_matches_mainnet_constant() {
3456 assert_known_initial_supply_matches_l1(
3457 "https://mainnet.gateway.tenderly.co",
3458 MAINNET_CHAIN_ID,
3459 MAINNET_STAKE_TABLE_CONTRACT,
3460 )
3461 .await;
3462 }
3463
3464 #[ignore = "talks to public Sepolia RPC"]
3471 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3472 async fn fetch_initial_supply_matches_decaf_constant() {
3473 assert_known_initial_supply_matches_l1(
3474 "https://sepolia.gateway.tenderly.co",
3475 DECAF_CHAIN_ID,
3476 DECAF_STAKE_TABLE_CONTRACT,
3477 )
3478 .await;
3479 }
3480
3481 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3482 #[should_panic]
3483 async fn test_large_max_events_range_panic() {
3484 let contract_address = "0x40304fbe94d5e7d1492dd90c53a2d63e8506a037";
3486
3487 let l1 = L1ClientOptions {
3488 l1_events_max_retry_duration: Duration::from_secs(30),
3489 l1_events_max_block_range: 10_u64.pow(9),
3491 l1_retry_delay: Duration::from_secs(1),
3492 ..Default::default()
3493 }
3494 .connect(vec![
3495 "https://ethereum-sepolia.publicnode.com".parse().unwrap(),
3496 ])
3497 .expect("unable to construct l1 client");
3498
3499 let latest_block = l1.provider.get_block_number().await.unwrap();
3500 let _events = Fetcher::fetch_events_from_contract(
3501 l1,
3502 contract_address.parse().unwrap(),
3503 None,
3504 latest_block,
3505 )
3506 .await
3507 .unwrap();
3508 }
3509
3510 #[test]
3516 fn block_reward_for_known_initial_supplies() {
3517 let reward = |supply: U256| {
3518 ((supply * U256::from(INFLATION_RATE)) / U256::from(BLOCKS_PER_YEAR))
3519 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
3520 .unwrap()
3521 };
3522
3523 assert_eq!(
3524 reward(U256::from(MAINNET_INITIAL_SUPPLY_WEI)),
3525 U256::from(6_830_289_193_302_891_933u128)
3526 );
3527 assert_eq!(
3528 reward(U256::from(DECAF_INITIAL_SUPPLY_WEI)),
3529 U256::from(19_025_875_190_258_751_902u128)
3530 );
3531 }
3532
3533 #[test]
3535 fn known_initial_supply_values() {
3536 assert_eq!(
3537 known_initial_supply(MAINNET_CHAIN_ID, MAINNET_STAKE_TABLE_CONTRACT),
3538 Some(U256::from_str("3590000000000000000000000000").unwrap())
3539 );
3540 assert_eq!(
3541 known_initial_supply(DECAF_CHAIN_ID, DECAF_STAKE_TABLE_CONTRACT),
3542 Some(U256::from_str("10000000000000000000000000000").unwrap())
3543 );
3544 assert_eq!(
3545 known_initial_supply(MAINNET_CHAIN_ID, Address::repeat_byte(9)),
3546 None
3547 );
3548 assert_eq!(
3549 known_initial_supply(DECAF_CHAIN_ID, MAINNET_STAKE_TABLE_CONTRACT),
3550 None
3551 );
3552 assert_eq!(
3553 known_initial_supply(ChainId(U256::from(9)), MAINNET_STAKE_TABLE_CONTRACT),
3554 None
3555 );
3556 }
3557
3558 #[test]
3561 fn known_chains_match_genesis() {
3562 let genesis = |network: &str| {
3563 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
3564 .join("../../../data/genesis")
3565 .join(format!("{network}.toml"));
3566 let genesis: toml::Value =
3567 toml::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
3568 let chain_config = &genesis["chain_config"];
3569 (
3570 ChainId(U256::from(chain_config["chain_id"].as_integer().unwrap())),
3571 chain_config["stake_table_contract"]
3572 .as_str()
3573 .unwrap()
3574 .parse::<Address>()
3575 .unwrap(),
3576 )
3577 };
3578
3579 assert_eq!(
3580 genesis("mainnet"),
3581 (MAINNET_CHAIN_ID, MAINNET_STAKE_TABLE_CONTRACT)
3582 );
3583 assert_eq!(
3584 genesis("decaf"),
3585 (DECAF_CHAIN_ID, DECAF_STAKE_TABLE_CONTRACT)
3586 );
3587 }
3588
3589 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3593 async fn initial_supply_or_fetch_skips_l1_for_known_chains() {
3594 for (chain_id, contract, expected) in [
3595 (
3596 MAINNET_CHAIN_ID,
3597 MAINNET_STAKE_TABLE_CONTRACT,
3598 MAINNET_INITIAL_SUPPLY_WEI,
3599 ),
3600 (
3601 DECAF_CHAIN_ID,
3602 DECAF_STAKE_TABLE_CONTRACT,
3603 DECAF_INITIAL_SUPPLY_WEI,
3604 ),
3605 ] {
3606 let fetcher = Fetcher::mock();
3607 {
3608 let mut chain_config = fetcher.chain_config.lock().await;
3609 chain_config.chain_id = chain_id;
3610 chain_config.stake_table_contract = Some(contract);
3611 }
3612
3613 let supply =
3614 tokio::time::timeout(Duration::from_secs(5), fetcher.initial_supply_or_fetch())
3615 .await
3616 .expect(
3617 "initial_supply_or_fetch reached the L1 fetch path for a known contract",
3618 )
3619 .unwrap();
3620
3621 assert_eq!(supply, U256::from(expected));
3622 }
3623 }
3624
3625 #[test]
3626 fn sanity_check_p_and_rp() {
3627 let total_stake = BigDecimal::from_str("1000").unwrap();
3628 let total_supply = BigDecimal::from_str("10000").unwrap(); let (p, rp) =
3631 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3632
3633 assert!(p > BigDecimal::zero());
3634 assert!(p < BigDecimal::one());
3635 assert!(rp > BigDecimal::zero());
3636 }
3637
3638 #[test]
3639 fn test_p_out_of_range() {
3640 let total_stake = BigDecimal::from_str("1000").unwrap();
3641 let total_supply = BigDecimal::from_str("500").unwrap(); let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3644 assert!(result.is_err());
3645 }
3646
3647 #[test]
3648 fn test_zero_total_supply() {
3649 let total_stake = BigDecimal::from_str("1000").unwrap();
3650 let total_supply = BigDecimal::from(0);
3651
3652 let result = calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply);
3653 assert!(result.is_err());
3654 }
3655
3656 #[test]
3657 fn test_valid_p_and_rp() {
3658 let total_stake = BigDecimal::from_str("5000").unwrap();
3659 let total_supply = BigDecimal::from_str("10000").unwrap();
3660
3661 let (p, rp) =
3662 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3663
3664 assert_eq!(p, BigDecimal::from_str("0.5").unwrap());
3665 assert!(rp > BigDecimal::from_str("0.0").unwrap());
3666 }
3667
3668 #[test]
3669 fn test_very_small_p() {
3670 let total_stake = BigDecimal::from_str("1").unwrap(); let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap(); let (p, rp) =
3674 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3675
3676 assert!(p > BigDecimal::from_str("0").unwrap());
3677 assert!(p < BigDecimal::from_str("1e-18").unwrap()); assert!(rp > BigDecimal::zero());
3679 }
3680
3681 #[test]
3682 fn test_p_very_close_to_one() {
3683 let total_stake = BigDecimal::from_str("9999999999999999999999999999").unwrap();
3684 let total_supply = BigDecimal::from_str("10000000000000000000000000000").unwrap();
3685
3686 let (p, rp) =
3687 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3688
3689 assert!(p < BigDecimal::one());
3690 assert!(p > BigDecimal::from_str("0.999999999999999999999999999").unwrap());
3691 assert!(rp > BigDecimal::zero());
3692 }
3693
3694 #[test]
3698 fn test_reward_rate_rp() {
3699 let test_cases = [
3700 ("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"), ];
3712
3713 let tolerance = BigDecimal::from_str("0.0001").unwrap();
3714
3715 let total_supply = BigDecimal::from_u32(10_000).unwrap();
3716
3717 for (p, rp) in test_cases {
3718 let p = BigDecimal::from_str(p).unwrap();
3719 let expected_rp = BigDecimal::from_str(rp).unwrap();
3720
3721 let total_stake = &p * &total_supply;
3722
3723 let (computed_p, computed_rp) =
3724 calculate_proportion_staked_and_reward_rate(&total_stake, &total_supply).unwrap();
3725
3726 assert!(
3727 (&computed_p - &p).abs() < tolerance,
3728 "p mismatch: got {computed_p}, expected {p}"
3729 );
3730
3731 assert!(
3732 (&computed_rp - &expected_rp).abs() < tolerance,
3733 "R(p) mismatch for p={p}: got {computed_rp}, expected {expected_rp}"
3734 );
3735 }
3736 }
3737
3738 #[tokio::test(flavor = "multi_thread")]
3739 async fn test_dynamic_block_reward_with_expected_values() {
3740 let total_supply = U256::from_str("10000000000000000000000000000").unwrap();
3742 let total_supply_bd = BigDecimal::from_str(&total_supply.to_string()).unwrap();
3743
3744 let test_cases = [
3745 ("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"), ];
3779
3780 let tolerance = U256::from(100_000_000_000_000_000u128); for (p, rp, avg_block_time_ms, expected_reward) in test_cases {
3783 let p = BigDecimal::from_str(p).unwrap();
3784 let total_stake_bd = (&p * &total_supply_bd).round(0);
3785 println!("total_stake_bd={total_stake_bd}");
3786
3787 let total_stake = U256::from_str(&total_stake_bd.to_plain_string()).unwrap();
3788 let expected_reward = U256::from_str(expected_reward).unwrap();
3789
3790 let epoch = EpochNumber::new(0);
3791 let actual_reward =
3792 compute_block_reward(epoch, total_supply, total_stake, avg_block_time_ms)
3793 .unwrap()
3794 .0;
3795
3796 let diff = if actual_reward > expected_reward {
3797 actual_reward - expected_reward
3798 } else {
3799 expected_reward - actual_reward
3800 };
3801
3802 assert!(
3803 diff <= tolerance,
3804 "Reward mismatch for p = {p}, R(p) = {rp}, block_time = {avg_block_time_ms}: \
3805 expected = {expected_reward}, actual = {actual_reward}, diff = {diff}"
3806 );
3807 }
3808 }
3809
3810 #[derive(Debug, Clone, Copy)]
3812 enum EventType {
3813 Delegate,
3814 Undelegate,
3815 KeyUpdate,
3816 CommissionUpdate,
3817 Exit,
3818 X25519KeyUpdate,
3819 P2pAddrUpdate,
3820 }
3821
3822 #[rstest]
3827 #[case::delegate(EventType::Delegate)]
3828 #[case::undelegate(EventType::Undelegate)]
3829 #[case::key_update(EventType::KeyUpdate)]
3830 #[case::commission_update(EventType::CommissionUpdate)]
3831 #[case::exit(EventType::Exit)]
3832 #[case::x25519_key_update(EventType::X25519KeyUpdate)]
3833 #[case::p2p_addr_update(EventType::P2pAddrUpdate)]
3834 fn test_events_targeting_unauthenticated_validator(
3835 #[case] event_type: EventType,
3836 ) -> anyhow::Result<()> {
3837 let mut state = StakeTableState::default();
3838 let mut val = TestValidator::random();
3839 val.bls_sig = Default::default();
3840 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
3841
3842 let validator = state.validators().get(&val.account).context("validator")?;
3843 assert!(!validator.authenticated);
3844
3845 let delegator = Address::random();
3846 let initial_amount = U256::from(1000);
3847 state.apply_event(StakeTableEvent::Delegate(Delegated {
3848 delegator,
3849 validator: val.account,
3850 amount: initial_amount,
3851 }))??;
3852
3853 match event_type {
3854 EventType::Delegate => {
3855 let new_delegator = Address::random();
3856 let amount = U256::from(500);
3857 state.apply_event(StakeTableEvent::Delegate(Delegated {
3858 delegator: new_delegator,
3859 validator: val.account,
3860 amount,
3861 }))??;
3862
3863 let validator = state.validators().get(&val.account).context("validator")?;
3864 assert_eq!(validator.stake, initial_amount + amount);
3865 assert_eq!(
3866 validator.delegators.get(&new_delegator).cloned(),
3867 Some(amount)
3868 );
3869 },
3870 EventType::Undelegate => {
3871 let undelegate_amount = U256::from(300);
3872 state.apply_event(StakeTableEvent::UndelegateV2(UndelegatedV2 {
3873 delegator,
3874 validator: val.account,
3875 undelegationId: 1,
3876 amount: undelegate_amount,
3877 unlocksAt: U256::from(1000u64),
3878 }))??;
3879
3880 let validator = state.validators().get(&val.account).context("validator")?;
3881 assert_eq!(validator.stake, initial_amount - undelegate_amount);
3882 assert_eq!(
3883 validator.delegators.get(&delegator).cloned(),
3884 Some(initial_amount - undelegate_amount)
3885 );
3886 },
3887 EventType::KeyUpdate => {
3888 let new_keys = val.randomize_keys();
3889 state.apply_event(StakeTableEvent::KeyUpdateV2((&new_keys).into()))??;
3890
3891 let validator = state.validators().get(&val.account).context("validator")?;
3892 let expected_bls =
3893 BLSPubKey::try_from(new_keys.bls_vk).expect("valid test BLS key");
3894 let expected_schnorr =
3895 SchnorrPubKey::try_from(new_keys.schnorr_vk).expect("valid test Schnorr key");
3896 assert_eq!(validator.stake_table_key.as_ref(), Some(&expected_bls));
3897 assert_eq!(validator.state_ver_key.as_ref(), Some(&expected_schnorr));
3898 assert!(validator.authenticated);
3902 },
3903 EventType::CommissionUpdate => {
3904 let new_commission: u16 = 5000;
3905 state.apply_event(StakeTableEvent::CommissionUpdate(CommissionUpdated {
3906 validator: val.account,
3907 timestamp: Default::default(),
3908 oldCommission: val.commission,
3909 newCommission: new_commission,
3910 }))??;
3911
3912 let validator = state.validators().get(&val.account).context("validator")?;
3913 assert_eq!(validator.commission, new_commission);
3914 },
3915 EventType::Exit => {
3916 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
3917 validator: val.account,
3918 unlocksAt: U256::from(1000u64),
3919 }))??;
3920
3921 assert!(!state.validators().contains_key(&val.account));
3922 return Ok(());
3923 },
3924 EventType::X25519KeyUpdate => {
3925 let new_x25519_key = [99u8; 32];
3926 state.apply_event(val.x25519_update(new_x25519_key))??;
3927
3928 let validator = state.validators().get(&val.account).context("validator")?;
3929 let expected = x25519::PublicKey::try_from(new_x25519_key.as_slice())
3930 .expect("valid x25519 key");
3931 assert_eq!(validator.x25519_key, Some(expected));
3932 assert!(!validator.authenticated);
3933 },
3934 EventType::P2pAddrUpdate => {
3935 state.apply_event(val.p2p_update("10.0.0.1:9000"))??;
3936
3937 let validator = state.validators().get(&val.account).context("validator")?;
3938 let expected: NetAddr = "10.0.0.1:9000".parse().expect("valid p2p addr");
3939 assert_eq!(validator.p2p_addr, Some(expected));
3940 assert!(!validator.authenticated);
3941 },
3942 }
3943
3944 let active = select_active_validator_set(state.validators(), EPOCH_VERSION);
3949 match event_type {
3950 EventType::KeyUpdate => match active {
3951 Ok(map) => assert!(map.contains_key(&val.account)),
3952 Err(e) => bail!("expected validator to be selected after KeyUpdate, got {e}"),
3953 },
3954 _ => match active {
3955 Err(StakeTableError::NoValidValidators) => {},
3956 Err(e) => bail!("Unexpected error: {e}"),
3957 Ok(map) => assert!(!map.contains_key(&val.account)),
3958 },
3959 }
3960 Ok(())
3961 }
3962
3963 #[test]
3964 fn test_register_v3_sets_x25519_and_p2p() -> anyhow::Result<()> {
3965 let val = TestValidator::random();
3966
3967 let mut state = StakeTableState::default();
3968 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
3969
3970 let registered = state.validators().get(&val.account).unwrap();
3971 assert!(registered.authenticated);
3972
3973 let expected_x25519 =
3974 x25519::PublicKey::try_from(val.x25519_key.as_slice()).expect("valid x25519 key");
3975 assert_eq!(registered.x25519_key, Some(expected_x25519));
3976
3977 let expected_p2p: NetAddr = val.p2p_addr.parse().expect("valid p2p addr");
3978 assert_eq!(registered.p2p_addr, Some(expected_p2p));
3979
3980 Ok(())
3981 }
3982
3983 #[test]
3984 fn test_register_v3_invalid_sig() -> anyhow::Result<()> {
3985 let val = TestValidator::random();
3986 let other = TestValidator::random();
3987
3988 let bad_val = TestValidator {
3990 bls_sig: other.bls_sig,
3991 ..val.clone()
3992 };
3993 let event = StakeTableEvent::RegisterV3((&bad_val).into());
3994
3995 let mut state = StakeTableState::default();
3996 state.apply_event(event)??;
3997
3998 let registered = state.validators().get(&val.account).unwrap();
3999 assert!(!registered.authenticated);
4000
4001 Ok(())
4002 }
4003
4004 #[test]
4005 fn test_register_v3_hostname_p2p() -> anyhow::Result<()> {
4006 let val = TestValidator::random();
4007
4008 let mut state = StakeTableState::default();
4009 let val = val.with_p2p_addr("my-host:9000");
4012 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4013
4014 let registered = state.validators().get(&val.account).unwrap();
4015 let expected_p2p: NetAddr = "my-host:9000".parse().unwrap();
4016 assert_eq!(registered.p2p_addr, Some(expected_p2p));
4017
4018 Ok(())
4019 }
4020
4021 #[test]
4022 fn test_register_v3_invalid_p2p_addr_degrades_to_none() -> anyhow::Result<()> {
4023 let val = TestValidator::random();
4024
4025 let mut state = StakeTableState::default();
4026 let val = val.with_p2p_addr("host:notaport");
4027 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4028
4029 let registered = state.validators().get(&val.account).unwrap();
4030 assert_eq!(registered.p2p_addr, None);
4031
4032 Ok(())
4033 }
4034
4035 #[test]
4036 fn test_x25519_key_update_sets_value() -> anyhow::Result<()> {
4037 let val = TestValidator::random();
4038
4039 let mut state = StakeTableState::default();
4040 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
4041
4042 assert_eq!(
4043 state.validators().get(&val.account).unwrap().x25519_key,
4044 None
4045 );
4046
4047 let x25519_key = [99u8; 32];
4048 state.apply_event(val.x25519_update(x25519_key))??;
4049
4050 let registered = state.validators().get(&val.account).unwrap();
4051 let expected_x25519 =
4052 x25519::PublicKey::try_from(x25519_key.as_slice()).expect("valid x25519 key");
4053 assert_eq!(registered.x25519_key, Some(expected_x25519));
4054
4055 Ok(())
4056 }
4057
4058 #[test]
4059 fn test_p2p_addr_update_sets_value() -> anyhow::Result<()> {
4060 let val = TestValidator::random();
4061
4062 let mut state = StakeTableState::default();
4063 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
4064
4065 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
4066
4067 let p2p_addr = "10.0.0.1:8080";
4068 state.apply_event(val.p2p_update(p2p_addr))??;
4069
4070 let registered = state.validators().get(&val.account).unwrap();
4071 let expected_p2p: NetAddr = p2p_addr.parse().expect("valid p2p addr");
4072 assert_eq!(registered.p2p_addr, Some(expected_p2p));
4073
4074 Ok(())
4075 }
4076
4077 #[test]
4078 fn test_p2p_addr_unparsable_sets_none() -> anyhow::Result<()> {
4079 let val = TestValidator::random();
4080
4081 let mut state = StakeTableState::default();
4082 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
4083
4084 state.apply_event(val.p2p_update("10.0.0.1:8080"))??;
4086 assert!(
4087 state
4088 .validators()
4089 .get(&val.account)
4090 .unwrap()
4091 .p2p_addr
4092 .is_some()
4093 );
4094
4095 state.apply_event(val.p2p_update("host:notaport"))??;
4097 assert_eq!(state.validators().get(&val.account).unwrap().p2p_addr, None);
4098
4099 Ok(())
4100 }
4101
4102 #[test]
4103 fn test_x25519_key_update_unknown_validator() {
4104 let mut state = StakeTableState::default();
4105 let unknown = TestValidator::random();
4106
4107 let result = state.apply_event(unknown.x25519_update([1u8; 32]));
4108 assert_matches!(
4109 result,
4110 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
4111 );
4112 }
4113
4114 #[test]
4115 fn test_p2p_addr_update_unknown_validator() {
4116 let mut state = StakeTableState::default();
4117 let unknown = TestValidator::random();
4118
4119 let result = state.apply_event(unknown.p2p_update("127.0.0.1:9000"));
4120 assert_matches!(
4121 result,
4122 Err(StakeTableError::ValidatorNotFound(addr)) if addr == unknown.account
4123 );
4124 }
4125
4126 #[test]
4127 fn test_x25519_key_update_duplicate() -> anyhow::Result<()> {
4128 let shared_key = [55u8; 32];
4129 let val1 = TestValidator::random().with_x25519_key(shared_key);
4130 let val2 = TestValidator::random().with_x25519_key([2u8; 32]);
4131
4132 let mut state = StakeTableState::default();
4133 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
4135 state.apply_event(StakeTableEvent::RegisterV3((&val2).into()))??;
4136
4137 let result = state.apply_event(val2.x25519_update(shared_key));
4139 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
4140
4141 Ok(())
4142 }
4143
4144 #[test]
4145 fn test_p2p_addr_update_hostname() -> anyhow::Result<()> {
4146 let val = TestValidator::random();
4147
4148 let mut state = StakeTableState::default();
4149 state.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
4150
4151 state.apply_event(val.p2p_update("my-node.example.com:9000"))??;
4152
4153 let registered = state.validators().get(&val.account).unwrap();
4154 let expected_p2p: NetAddr = "my-node.example.com:9000".parse().unwrap();
4155 assert_eq!(registered.p2p_addr, Some(expected_p2p));
4156
4157 Ok(())
4158 }
4159
4160 #[test]
4161 fn test_register_v3_duplicate_bls_key() -> anyhow::Result<()> {
4162 let val1 = TestValidator::random();
4163 let mut val2 = TestValidator::random();
4164 val2.bls_vk = val1.bls_vk;
4165
4166 let mut state = StakeTableState::default();
4167 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
4168 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
4169 assert_matches!(result, Err(StakeTableError::BlsKeyAlreadyUsed(_)));
4170
4171 Ok(())
4172 }
4173
4174 #[test]
4175 fn test_register_v2_duplicate_schnorr_key_is_unauthenticated() -> anyhow::Result<()> {
4176 let val1 = TestValidator::random();
4177 let mut val2 = TestValidator::random();
4178 val2.schnorr_vk = val1.schnorr_vk;
4179
4180 let mut state = StakeTableState::default();
4181 state.apply_event(StakeTableEvent::RegisterV2((&val1).into()))??;
4182 state.apply_event(StakeTableEvent::RegisterV2((&val2).into()))??;
4183
4184 let registered = state.validators().get(&val2.account).expect("present");
4185 assert!(!registered.authenticated);
4186 assert!(registered.state_ver_key.is_none());
4187 assert_excluded_from_active_set(&state, val2.account);
4188
4189 Ok(())
4190 }
4191
4192 #[test]
4193 fn test_register_v3_duplicate_schnorr_key_is_unauthenticated() -> anyhow::Result<()> {
4194 let val1 = TestValidator::random();
4195 let mut val2 = TestValidator::random();
4196 val2.schnorr_vk = val1.schnorr_vk;
4197
4198 let mut state = StakeTableState::default();
4199 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
4200 state.apply_event(StakeTableEvent::RegisterV3((&val2).into()))??;
4201
4202 let registered = state.validators().get(&val2.account).expect("present");
4203 assert!(!registered.authenticated);
4204 assert!(registered.state_ver_key.is_none());
4205 assert_excluded_from_active_set(&state, val2.account);
4206
4207 Ok(())
4208 }
4209
4210 #[test]
4211 fn test_register_v3_reusing_v1_schnorr_key_keeps_both_validators() -> anyhow::Result<()> {
4212 let v1 = TestValidator::random();
4213 let mut v3 = TestValidator::random();
4214 v3.schnorr_vk = v1.schnorr_vk;
4215
4216 let events = Vec::from([
4217 StakeTableEvent::Register((&v1).into()),
4218 StakeTableEvent::RegisterV3((&v3).into()),
4219 ]);
4220 let (validators, _) = validators_from_l1_events(events.into_iter())?;
4221
4222 let first = validators.get(&v1.account).expect("present");
4223 assert!(first.authenticated);
4224 assert!(first.state_ver_key.is_some());
4225
4226 let second = validators.get(&v3.account).expect("present");
4227 assert!(!second.authenticated);
4228 assert!(second.state_ver_key.is_none());
4229
4230 Ok(())
4231 }
4232
4233 #[test]
4234 fn test_register_v3_duplicate_x25519_key() -> anyhow::Result<()> {
4235 let shared_x25519 = [7u8; 32];
4236 let val1 = TestValidator::random().with_x25519_key(shared_x25519);
4237 let val2 = TestValidator::random().with_x25519_key(shared_x25519);
4238
4239 let mut state = StakeTableState::default();
4240 state.apply_event(StakeTableEvent::RegisterV3((&val1).into()))??;
4241 let result = state.apply_event(StakeTableEvent::RegisterV3((&val2).into()));
4242 assert_matches!(result, Err(StakeTableError::X25519KeyAlreadyUsed(_)));
4243
4244 Ok(())
4245 }
4246
4247 #[test]
4248 fn test_register_v3_rejects_exited_validator() -> anyhow::Result<()> {
4249 let val = TestValidator::random();
4250
4251 let mut state = StakeTableState::default();
4252 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4253 state.apply_event(StakeTableEvent::DeregisterV2(ValidatorExitV2 {
4254 validator: val.account,
4255 unlocksAt: U256::from(1000u64),
4256 }))??;
4257
4258 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
4259 assert_matches!(
4260 result,
4261 Err(StakeTableError::ValidatorAlreadyExited(addr)) if addr == val.account
4262 );
4263
4264 Ok(())
4265 }
4266
4267 #[test]
4268 fn test_register_v3_zero_x25519_soft_fails() -> anyhow::Result<()> {
4269 let val = TestValidator::random().with_x25519_key([0u8; 32]);
4270
4271 let mut state = StakeTableState::default();
4272 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4273
4274 let registered = state.validators().get(&val.account).unwrap();
4275 assert!(registered.x25519_key.is_none());
4276 assert!(state.used_x25519_keys().is_empty());
4277
4278 Ok(())
4279 }
4280
4281 #[test]
4282 fn test_x25519_key_update_zero_soft_fails() -> anyhow::Result<()> {
4283 let val = TestValidator::random();
4284
4285 let mut state = StakeTableState::default();
4286 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4287
4288 state.apply_event(val.x25519_update([0u8; 32]))??;
4289 let registered = state.validators().get(&val.account).unwrap();
4290 assert!(registered.x25519_key.is_none());
4291 assert_eq!(state.used_x25519_keys().len(), 1);
4292
4293 Ok(())
4294 }
4295
4296 fn noncanonical_x25519_key() -> [u8; 32] {
4299 let mut key = [0xffu8; 32];
4300 key[0] = 0xed;
4301 key[31] = 0x7f;
4302 key
4303 }
4304
4305 #[test]
4308 fn test_register_v3_noncanonical_x25519_soft_fails() -> anyhow::Result<()> {
4309 let val = TestValidator::random().with_x25519_key(noncanonical_x25519_key());
4310
4311 let mut state = StakeTableState::default();
4312 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4313
4314 let registered = state.validators().get(&val.account).unwrap();
4315 assert!(registered.x25519_key.is_none());
4316 assert!(state.used_x25519_keys().is_empty());
4317
4318 Ok(())
4319 }
4320
4321 #[test]
4324 fn test_x25519_key_update_noncanonical_soft_fails() -> anyhow::Result<()> {
4325 let val = TestValidator::random();
4326
4327 let mut state = StakeTableState::default();
4328 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4329 assert!(
4330 state
4331 .validators()
4332 .get(&val.account)
4333 .unwrap()
4334 .x25519_key
4335 .is_some()
4336 );
4337
4338 state.apply_event(val.x25519_update(noncanonical_x25519_key()))??;
4339
4340 let registered = state.validators().get(&val.account).unwrap();
4341 assert!(registered.x25519_key.is_none());
4342 assert_eq!(state.used_x25519_keys().len(), 1);
4343
4344 state.apply_event(val.x25519_update([42u8; 32]))??;
4345 let registered = state.validators().get(&val.account).unwrap();
4346 assert!(registered.x25519_key.is_some());
4347 assert_eq!(state.used_x25519_keys().len(), 2);
4348
4349 Ok(())
4350 }
4351
4352 #[test]
4356 fn test_state_commit_includes_used_x25519_keys() -> anyhow::Result<()> {
4357 let val = TestValidator::random();
4358
4359 let mut state_without = StakeTableState::default();
4360 state_without.apply_event(StakeTableEvent::RegisterV2((&val).into()))??;
4361
4362 let mut state_with = state_without.clone();
4363 state_with
4364 .used_x25519_keys
4365 .insert(x25519::PublicKey::try_from([7u8; 32].as_slice()).unwrap());
4366
4367 assert_ne!(state_without.commit(), state_with.commit());
4368 Ok(())
4369 }
4370
4371 fn complete_mock_validator() -> RegisteredValidator<BLSPubKey> {
4375 let mut v = RegisteredValidator::<BLSPubKey>::mock();
4376 v.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
4377 v.p2p_addr = Some("127.0.0.1:9000".parse().unwrap());
4378 v
4379 }
4380
4381 #[test]
4383 fn test_select_pre_upgrade_includes_validators_missing_network_info() {
4384 let mut map = RegisteredValidatorMap::new();
4385
4386 let incomplete = RegisteredValidator::<BLSPubKey>::mock();
4387 assert!(incomplete.x25519_key.is_none());
4388 assert!(incomplete.p2p_addr.is_none());
4389 map.insert(incomplete.account, incomplete.clone());
4390
4391 let complete = complete_mock_validator();
4392 map.insert(complete.account, complete.clone());
4393
4394 let active = select_active_validator_set(&map, EPOCH_VERSION).unwrap();
4395 assert!(active.contains_key(&incomplete.account));
4396 assert!(active.contains_key(&complete.account));
4397 }
4398
4399 #[test]
4401 fn test_select_post_upgrade_excludes_missing_x25519() {
4402 let mut map = RegisteredValidatorMap::new();
4403
4404 let mut missing_x25519 = complete_mock_validator();
4405 missing_x25519.x25519_key = None;
4406 map.insert(missing_x25519.account, missing_x25519.clone());
4407
4408 let complete = complete_mock_validator();
4409 map.insert(complete.account, complete.clone());
4410
4411 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
4412 assert!(!active.contains_key(&missing_x25519.account));
4413 assert!(active.contains_key(&complete.account));
4414 }
4415
4416 #[test]
4418 fn test_select_post_upgrade_excludes_missing_p2p() {
4419 let mut map = RegisteredValidatorMap::new();
4420
4421 let mut missing_p2p = complete_mock_validator();
4422 missing_p2p.p2p_addr = None;
4423 map.insert(missing_p2p.account, missing_p2p.clone());
4424
4425 let complete = complete_mock_validator();
4426 map.insert(complete.account, complete.clone());
4427
4428 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
4429 assert!(!active.contains_key(&missing_p2p.account));
4430 assert!(active.contains_key(&complete.account));
4431 }
4432
4433 #[test]
4436 fn test_select_post_upgrade_excludes_invalid_p2p() {
4437 let mut map = RegisteredValidatorMap::new();
4438
4439 let mut invalid_p2p = complete_mock_validator();
4440 invalid_p2p.p2p_addr = Some("[[a]:b]:80".parse().unwrap());
4441 map.insert(invalid_p2p.account, invalid_p2p.clone());
4442
4443 let complete = complete_mock_validator();
4444 map.insert(complete.account, complete.clone());
4445
4446 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
4447 assert!(!active.contains_key(&invalid_p2p.account));
4448 assert!(active.contains_key(&complete.account));
4449
4450 let active = select_active_validator_set(&map, EPOCH_VERSION).unwrap();
4451 assert!(active.contains_key(&invalid_p2p.account));
4452 }
4453
4454 #[test]
4456 fn test_select_post_upgrade_keeps_complete() {
4457 let mut map = RegisteredValidatorMap::new();
4458 let complete = complete_mock_validator();
4459 map.insert(complete.account, complete.clone());
4460
4461 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
4462 assert!(active.contains_key(&complete.account));
4463 }
4464
4465 #[test]
4468 fn test_select_post_upgrade_all_incomplete_fails() {
4469 let mut map = RegisteredValidatorMap::new();
4470 for _ in 0..5 {
4471 let v = RegisteredValidator::<BLSPubKey>::mock();
4472 map.insert(v.account, v);
4473 }
4474
4475 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
4476 assert_matches!(result, Err(StakeTableError::NoValidValidators));
4477 }
4478
4479 #[test]
4483 fn test_select_post_upgrade_top_n_only_complete() {
4484 let mut map = RegisteredValidatorMap::new();
4485 let mut complete_accounts = HashSet::new();
4486 let mut incomplete_accounts = HashSet::new();
4487
4488 for i in 0..(MAX_VALIDATORS + 50) {
4492 if i % 2 == 0 {
4493 let mut v = complete_mock_validator();
4494 v.stake = U256::from(1_000_000_000_u64 + i as u64);
4495 complete_accounts.insert(v.account);
4496 map.insert(v.account, v);
4497 } else {
4498 let mut v = RegisteredValidator::<BLSPubKey>::mock();
4499 v.stake = U256::from(10_u64);
4500 incomplete_accounts.insert(v.account);
4501 map.insert(v.account, v);
4502 }
4503 }
4504
4505 let active = select_active_validator_set(&map, NEW_PROTOCOL_VERSION).unwrap();
4506
4507 assert!(
4508 active.len() <= MAX_VALIDATORS,
4509 "active has {} validators, expected at most {}",
4510 active.len(),
4511 MAX_VALIDATORS
4512 );
4513 for addr in active.keys() {
4514 assert!(
4515 complete_accounts.contains(addr),
4516 "incomplete validator {addr:?} ended up in active set"
4517 );
4518 assert!(!incomplete_accounts.contains(addr));
4519 }
4520 }
4521
4522 #[test]
4526 fn test_select_version_boundary() {
4527 let mut v = complete_mock_validator();
4528 v.x25519_key = None;
4529 let mut map = RegisteredValidatorMap::new();
4530 map.insert(v.account, v.clone());
4531
4532 for protocol_version in [
4533 EPOCH_VERSION,
4534 DRB_AND_HEADER_UPGRADE_VERSION,
4535 EPOCH_REWARD_VERSION,
4536 ] {
4537 let active = select_active_validator_set(&map, protocol_version).unwrap();
4538 assert!(
4539 active.contains_key(&v.account),
4540 "missing x25519 validator should be included at protocol version \
4541 {protocol_version}",
4542 );
4543 }
4544
4545 let result = select_active_validator_set(&map, NEW_PROTOCOL_VERSION);
4546 assert_matches!(result, Err(StakeTableError::NoValidValidators));
4547 }
4548
4549 #[test]
4552 fn test_p2p_parse_fail_pre_upgrade_included_post_upgrade_excluded() -> anyhow::Result<()> {
4553 let val = TestValidator::random().with_p2p_addr("host:notaport");
4554
4555 let mut state = StakeTableState::default();
4556 state.apply_event(StakeTableEvent::RegisterV3((&val).into()))??;
4557 state.apply_event(StakeTableEvent::Delegate(Delegated {
4559 delegator: Address::random(),
4560 validator: val.account,
4561 amount: U256::from(100),
4562 }))??;
4563
4564 let registered = state.validators().get(&val.account).unwrap();
4565 assert_eq!(registered.p2p_addr, None);
4566
4567 let pre = select_active_validator_set(state.validators(), EPOCH_VERSION).unwrap();
4569 assert!(pre.contains_key(&val.account));
4570
4571 let post = select_active_validator_set(state.validators(), NEW_PROTOCOL_VERSION);
4573 assert_matches!(post, Err(StakeTableError::NoValidValidators));
4574
4575 Ok(())
4576 }
4577
4578 #[test]
4579 fn test_register_v1_zero_g2_excluded_from_active_set() {
4580 let account = Address::random();
4581 let mut state = StakeTableState::default();
4582 state
4583 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4584 account,
4585 blsVk: zero_g2(),
4586 schnorrVk: StateKeyPair::generate().ver_key().into(),
4587 commission: 0,
4588 }))
4589 .unwrap()
4590 .unwrap();
4591
4592 let v = state.validators().get(&account).expect("present");
4593 assert!(v.stake_table_key.is_none());
4594 assert!(!v.authenticated);
4595
4596 state
4597 .apply_event(StakeTableEvent::Delegate(Delegated {
4598 delegator: Address::random(),
4599 validator: account,
4600 amount: U256::from(100),
4601 }))
4602 .unwrap()
4603 .unwrap();
4604
4605 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4606 Err(StakeTableError::NoValidValidators) => {},
4607 Ok(map) => assert!(map.get(&account).is_none()),
4608 Err(e) => panic!("unexpected error: {e}"),
4609 }
4610 }
4611
4612 #[test]
4613 fn test_register_v2_zero_g2_excluded_from_active_set() {
4614 let account = Address::random();
4615 let mut state = StakeTableState::default();
4616 state
4617 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4618 account,
4619 blsVK: zero_g2(),
4620 schnorrVK: StateKeyPair::generate().ver_key().into(),
4621 commission: 0,
4622 blsSig: zero_g1().into(),
4623 schnorrSig: Bytes::default(),
4624 metadataUri: String::new(),
4625 }))
4626 .unwrap()
4627 .unwrap();
4628
4629 let v = state.validators().get(&account).expect("present");
4630 assert!(v.stake_table_key.is_none());
4631 assert!(!v.authenticated);
4632
4633 state
4634 .apply_event(StakeTableEvent::Delegate(Delegated {
4635 delegator: Address::random(),
4636 validator: account,
4637 amount: U256::from(100),
4638 }))
4639 .unwrap()
4640 .unwrap();
4641
4642 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4643 Err(StakeTableError::NoValidValidators) => {},
4644 Ok(map) => assert!(map.get(&account).is_none()),
4645 Err(e) => panic!("unexpected error: {e}"),
4646 }
4647 }
4648
4649 #[test]
4650 fn test_register_v3_zero_g2_excluded_from_active_set() {
4651 let val = TestValidator::random();
4652 let account = val.account;
4653 let mut state = StakeTableState::default();
4654 state
4655 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
4656 &val,
4657 zero_g2(),
4658 )))
4659 .unwrap()
4660 .unwrap();
4661
4662 let v = state.validators().get(&account).expect("present");
4663 assert!(v.stake_table_key.is_none());
4664 assert!(!v.authenticated);
4665
4666 state
4667 .apply_event(StakeTableEvent::Delegate(Delegated {
4668 delegator: Address::random(),
4669 validator: account,
4670 amount: U256::from(100),
4671 }))
4672 .unwrap()
4673 .unwrap();
4674
4675 assert_excluded_from_active_set(&state, account);
4676 }
4677
4678 fn zero_schnorr() -> hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4679 hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4680 x: U256::ZERO,
4681 y: U256::ZERO,
4682 }
4683 }
4684
4685 fn identity_schnorr() -> hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4688 hotshot_contract_adapter::sol_types::EdOnBN254PointSol {
4689 x: U256::ZERO,
4690 y: U256::from(1),
4691 }
4692 }
4693
4694 fn register_v3_with_bls(val: &TestValidator, bls: G2PointSol) -> ValidatorRegisteredV3 {
4698 let mut reg = ValidatorRegisteredV3::from(val);
4699 reg.blsVK = bls;
4700 reg
4701 }
4702
4703 fn assert_excluded_from_active_set(state: &StakeTableState, account: Address) {
4705 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4706 Err(StakeTableError::NoValidValidators) => {},
4707 Ok(map) => assert!(map.get(&account).is_none()),
4708 Err(e) => panic!("unexpected error: {e}"),
4709 }
4710 }
4711
4712 #[test]
4713 fn test_register_v1_identity_schnorr_excluded_from_active_set() {
4714 let account = Address::random();
4715 let bls: G2PointSol = BLSPubKey::generated_from_seed_indexed([8u8; 32], 0)
4716 .0
4717 .into();
4718 let mut state = StakeTableState::default();
4719 state
4720 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4721 account,
4722 blsVk: bls,
4723 schnorrVk: identity_schnorr(),
4724 commission: 0,
4725 }))
4726 .unwrap()
4727 .unwrap();
4728
4729 let v = state.validators().get(&account).expect("present");
4730 assert!(
4731 !v.authenticated,
4732 "validator with identity schnorr key must be unauthenticated"
4733 );
4734 assert!(v.state_ver_key.is_none());
4735
4736 state
4737 .apply_event(StakeTableEvent::Delegate(Delegated {
4738 delegator: Address::random(),
4739 validator: account,
4740 amount: U256::from(100),
4741 }))
4742 .unwrap()
4743 .unwrap();
4744
4745 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4746 Err(StakeTableError::NoValidValidators) => {},
4747 Ok(map) => assert!(map.get(&account).is_none()),
4748 Err(e) => panic!("unexpected error: {e}"),
4749 }
4750 }
4751
4752 #[test]
4753 fn test_register_v1_invalid_schnorr_excluded_from_active_set() {
4754 let account = Address::random();
4755 let bls: G2PointSol = BLSPubKey::generated_from_seed_indexed([9u8; 32], 0)
4756 .0
4757 .into();
4758 let mut state = StakeTableState::default();
4759 state
4760 .apply_event(StakeTableEvent::Register(ValidatorRegistered {
4761 account,
4762 blsVk: bls,
4763 schnorrVk: zero_schnorr(),
4764 commission: 0,
4765 }))
4766 .unwrap()
4767 .unwrap();
4768
4769 let v = state.validators().get(&account).expect("present");
4770 assert!(
4771 !v.authenticated,
4772 "validator with zero schnorr key must be unauthenticated"
4773 );
4774
4775 state
4776 .apply_event(StakeTableEvent::Delegate(Delegated {
4777 delegator: Address::random(),
4778 validator: account,
4779 amount: U256::from(100),
4780 }))
4781 .unwrap()
4782 .unwrap();
4783
4784 match select_active_validator_set(state.validators(), EPOCH_VERSION) {
4785 Err(StakeTableError::NoValidValidators) => {},
4786 Ok(map) => assert!(map.get(&account).is_none()),
4787 Err(e) => panic!("unexpected error: {e}"),
4788 }
4789 }
4790
4791 #[test]
4792 fn test_register_v3_identity_schnorr_excluded_from_active_set() {
4793 let val = TestValidator::random();
4794 let account = val.account;
4795 let mut reg = ValidatorRegisteredV3::from(&val);
4796 reg.schnorrVK = identity_schnorr();
4797 let mut state = StakeTableState::default();
4798 state
4799 .apply_event(StakeTableEvent::RegisterV3(reg))
4800 .unwrap()
4801 .unwrap();
4802
4803 let v = state.validators().get(&account).expect("present");
4804 assert!(
4805 !v.authenticated,
4806 "validator with identity schnorr key must be unauthenticated"
4807 );
4808 assert!(v.state_ver_key.is_none());
4809
4810 state
4811 .apply_event(StakeTableEvent::Delegate(Delegated {
4812 delegator: Address::random(),
4813 validator: account,
4814 amount: U256::from(100),
4815 }))
4816 .unwrap()
4817 .unwrap();
4818
4819 assert_excluded_from_active_set(&state, account);
4820 }
4821
4822 #[test]
4823 fn test_register_v3_invalid_schnorr_excluded_from_active_set() {
4824 let val = TestValidator::random();
4825 let account = val.account;
4826 let mut reg = ValidatorRegisteredV3::from(&val);
4827 reg.schnorrVK = zero_schnorr();
4828 let mut state = StakeTableState::default();
4829 state
4830 .apply_event(StakeTableEvent::RegisterV3(reg))
4831 .unwrap()
4832 .unwrap();
4833
4834 let v = state.validators().get(&account).expect("present");
4835 assert!(
4836 !v.authenticated,
4837 "validator with zero schnorr key must be unauthenticated"
4838 );
4839 assert!(v.state_ver_key.is_none());
4840
4841 state
4842 .apply_event(StakeTableEvent::Delegate(Delegated {
4843 delegator: Address::random(),
4844 validator: account,
4845 amount: U256::from(100),
4846 }))
4847 .unwrap()
4848 .unwrap();
4849
4850 assert_excluded_from_active_set(&state, account);
4851 }
4852
4853 #[test]
4854 fn test_zero_bls_key_register_unauthenticated() {
4855 let account = Address::random();
4856 let schnorr_key_pair = StateKeyPair::generate();
4857 let mut state = StakeTableState::default();
4858
4859 let reg_v2 = ValidatorRegisteredV2 {
4860 account,
4861 blsVK: zero_g2(),
4862 schnorrVK: schnorr_key_pair.ver_key().into(),
4863 commission: 0,
4864 blsSig: zero_g1().into(),
4865 schnorrSig: Bytes::default(),
4866 metadataUri: String::new(),
4867 };
4868 state
4869 .apply_event(StakeTableEvent::RegisterV2(reg_v2))
4870 .expect("no fatal error")
4871 .expect("registered as unauthenticated");
4872 let registered = state.validators().get(&account).expect("present");
4873 assert!(!registered.authenticated);
4874 assert!(registered.stake_table_key.is_none());
4875 assert!(AuthenticatedValidator::try_from(registered).is_err());
4876
4877 let account_v1 = Address::random();
4879 let schnorr_v1 = StateKeyPair::generate();
4880 let mut state_v1 = StakeTableState::default();
4881 let reg_v1 = ValidatorRegistered {
4882 account: account_v1,
4883 blsVk: zero_g2(),
4884 schnorrVk: schnorr_v1.ver_key().into(),
4885 commission: 0,
4886 };
4887 state_v1
4888 .apply_event(StakeTableEvent::Register(reg_v1))
4889 .expect("no fatal error")
4890 .expect("registered as unauthenticated");
4891 let registered_v1 = state_v1.validators().get(&account_v1).expect("present");
4892 assert!(!registered_v1.authenticated);
4893 assert!(registered_v1.stake_table_key.is_none());
4894
4895 let valid_val = TestValidator::random();
4897 state
4898 .apply_event(StakeTableEvent::RegisterV2((&valid_val).into()))
4899 .unwrap()
4900 .unwrap();
4901 let prior_key_v2 = state
4902 .validators()
4903 .get(&valid_val.account)
4904 .unwrap()
4905 .stake_table_key;
4906 let update = ConsensusKeysUpdatedV2 {
4907 account: valid_val.account,
4908 blsVK: zero_g2(),
4909 schnorrVK: schnorr_key_pair.ver_key().into(),
4910 blsSig: zero_g1().into(),
4911 schnorrSig: Bytes::default(),
4912 };
4913 assert_matches!(
4914 state.apply_event(StakeTableEvent::KeyUpdateV2(update)),
4915 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4916 );
4917 assert_eq!(
4918 state
4919 .validators()
4920 .get(&valid_val.account)
4921 .unwrap()
4922 .stake_table_key,
4923 prior_key_v2
4924 );
4925
4926 let valid_v1 = TestValidator::random();
4928 state_v1
4929 .apply_event(StakeTableEvent::Register((&valid_v1).into()))
4930 .unwrap()
4931 .unwrap();
4932 let prior_key = state_v1
4933 .validators()
4934 .get(&valid_v1.account)
4935 .unwrap()
4936 .stake_table_key;
4937 let update_v1 = ConsensusKeysUpdated {
4938 account: valid_v1.account,
4939 blsVK: zero_g2(),
4940 schnorrVK: schnorr_key_pair.ver_key().into(),
4941 };
4942 assert_matches!(
4943 state_v1.apply_event(StakeTableEvent::KeyUpdate(update_v1)),
4944 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
4945 );
4946 assert_eq!(
4947 state_v1
4948 .validators()
4949 .get(&valid_v1.account)
4950 .unwrap()
4951 .stake_table_key,
4952 prior_key
4953 );
4954 }
4955
4956 #[test]
4960 fn test_zero_bls_key_registered_unauthenticated_in_from_l1_events() {
4961 let valid_val = TestValidator::random();
4962 let poison_account = Address::random();
4963 let schnorr_key_pair = StateKeyPair::generate();
4964
4965 let events = vec![
4966 StakeTableEvent::RegisterV2((&valid_val).into()),
4967 StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
4968 account: poison_account,
4969 blsVK: zero_g2(),
4970 schnorrVK: schnorr_key_pair.ver_key().into(),
4971 commission: 0,
4972 blsSig: zero_g1().into(),
4973 schnorrSig: Bytes::default(),
4974 metadataUri: String::new(),
4975 }),
4976 StakeTableEvent::Delegate(Delegated {
4977 delegator: Address::random(),
4978 validator: poison_account,
4979 amount: U256::from(10),
4980 }),
4981 ];
4982
4983 let (validators, _) = validators_from_l1_events(events.into_iter()).expect("must not fail");
4984 let valid = validators
4985 .get(&valid_val.account)
4986 .expect("valid validator present");
4987 assert!(valid.authenticated);
4988
4989 let poisoned = validators
4990 .get(&poison_account)
4991 .expect("poison validator present as unauthenticated");
4992 assert!(!poisoned.authenticated);
4993 assert!(poisoned.stake_table_key.is_none());
4994 assert_eq!(poisoned.stake, U256::from(10));
4995 assert!(AuthenticatedValidator::try_from(poisoned).is_err());
4996 }
4997
4998 #[test]
4999 fn test_register_v3_zero_bls_key_unauthenticated_in_from_l1_events() {
5000 let valid_val = TestValidator::random();
5001 let poison_val = TestValidator::random();
5002 let poison_account = poison_val.account;
5003
5004 let events = vec![
5005 StakeTableEvent::RegisterV3((&valid_val).into()),
5006 StakeTableEvent::RegisterV3(register_v3_with_bls(&poison_val, zero_g2())),
5007 StakeTableEvent::Delegate(Delegated {
5008 delegator: Address::random(),
5009 validator: poison_account,
5010 amount: U256::from(10),
5011 }),
5012 ];
5013
5014 let (validators, _) = validators_from_l1_events(events.into_iter()).expect("must not fail");
5015 let valid = validators
5016 .get(&valid_val.account)
5017 .expect("valid validator present");
5018 assert!(valid.authenticated);
5019
5020 let poisoned = validators
5021 .get(&poison_account)
5022 .expect("poison validator present as unauthenticated");
5023 assert!(!poisoned.authenticated);
5024 assert!(poisoned.stake_table_key.is_none());
5025 assert_eq!(poisoned.stake, U256::from(10));
5026 assert!(AuthenticatedValidator::try_from(poisoned).is_err());
5027 }
5028
5029 #[test]
5032 fn test_two_distinct_unparsable_g2_keys_both_register() {
5033 let zero = zero_g2();
5034 let nonzero = G2PointSol {
5036 x0: U256::from(1),
5037 x1: U256::ZERO,
5038 y0: U256::ZERO,
5039 y1: U256::ZERO,
5040 };
5041 assert!(BLSPubKey::try_from(zero).is_err());
5042 assert!(BLSPubKey::try_from(nonzero).is_err());
5043 assert_ne!(zero, nonzero);
5044
5045 let acc_a = Address::random();
5046 let acc_b = Address::random();
5047
5048 let mut state = StakeTableState::default();
5049 state
5050 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
5051 account: acc_a,
5052 blsVK: zero,
5053 schnorrVK: StateKeyPair::generate().ver_key().into(),
5054 commission: 0,
5055 blsSig: zero_g1().into(),
5056 schnorrSig: Bytes::default(),
5057 metadataUri: String::new(),
5058 }))
5059 .expect("first unparsable register must not fail")
5060 .expect("first unparsable register must apply");
5061 state
5062 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
5063 account: acc_b,
5064 blsVK: nonzero,
5065 schnorrVK: StateKeyPair::generate().ver_key().into(),
5066 commission: 0,
5067 blsSig: zero_g1().into(),
5068 schnorrSig: Bytes::default(),
5069 metadataUri: String::new(),
5070 }))
5071 .expect("second unparsable register must not fail")
5072 .expect("second unparsable register must apply");
5073
5074 for account in [acc_a, acc_b] {
5075 let v = state.validators().get(&account).expect("present");
5076 assert!(!v.authenticated);
5077 assert!(v.stake_table_key.is_none());
5078 }
5079 assert!(state.used_bls_keys().is_empty());
5080 }
5081
5082 #[test]
5086 fn test_register_v3_two_distinct_unparsable_g2_keys_both_register() {
5087 let zero = zero_g2();
5088 let nonzero = G2PointSol {
5090 x0: U256::from(1),
5091 x1: U256::ZERO,
5092 y0: U256::ZERO,
5093 y1: U256::ZERO,
5094 };
5095 assert!(BLSPubKey::try_from(zero).is_err());
5096 assert!(BLSPubKey::try_from(nonzero).is_err());
5097 assert_ne!(zero, nonzero);
5098
5099 let val_a = TestValidator::random();
5100 let val_b = TestValidator::random();
5101 let acc_a = val_a.account;
5102 let acc_b = val_b.account;
5103
5104 let mut state = StakeTableState::default();
5105 state
5106 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
5107 &val_a, zero,
5108 )))
5109 .expect("first unparsable register must not fail")
5110 .expect("first unparsable register must apply");
5111 state
5112 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
5113 &val_b, nonzero,
5114 )))
5115 .expect("second unparsable register must not fail")
5116 .expect("second unparsable register must apply");
5117
5118 for account in [acc_a, acc_b] {
5119 let v = state.validators().get(&account).expect("present");
5120 assert!(!v.authenticated);
5121 assert!(v.stake_table_key.is_none());
5122 }
5123 assert!(state.used_bls_keys().is_empty());
5124 }
5125
5126 #[test]
5129 fn test_zero_g2_then_valid_key_update() {
5130 let mut state = StakeTableState::default();
5131 let valid_val = TestValidator::random();
5132 let account = valid_val.account;
5133
5134 state
5135 .apply_event(StakeTableEvent::RegisterV2(ValidatorRegisteredV2 {
5136 account,
5137 blsVK: zero_g2(),
5138 schnorrVK: StateKeyPair::generate().ver_key().into(),
5139 commission: 0,
5140 blsSig: zero_g1().into(),
5141 schnorrSig: Bytes::default(),
5142 metadataUri: String::new(),
5143 }))
5144 .unwrap()
5145 .unwrap();
5146 assert!(!state.validators().get(&account).unwrap().authenticated);
5147
5148 let new_keys = TestValidator::random_update_keys(account, 0);
5149 let update: ConsensusKeysUpdatedV2 = (&new_keys).into();
5150 state
5151 .apply_event(StakeTableEvent::KeyUpdateV2(update))
5152 .unwrap()
5153 .unwrap();
5154
5155 let v = state.validators().get(&account).unwrap();
5156 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).unwrap();
5157 assert_eq!(v.stake_table_key.as_ref(), Some(&expected_bls));
5158 assert!(v.authenticated);
5161 }
5162
5163 #[test]
5164 fn test_register_v3_zero_g2_then_valid_key_update() {
5165 let val = TestValidator::random();
5166 let account = val.account;
5167 let mut state = StakeTableState::default();
5168
5169 state
5170 .apply_event(StakeTableEvent::RegisterV3(register_v3_with_bls(
5171 &val,
5172 zero_g2(),
5173 )))
5174 .unwrap()
5175 .unwrap();
5176 assert!(!state.validators().get(&account).unwrap().authenticated);
5177
5178 let new_keys = TestValidator::random_update_keys(account, 0);
5179 let update: ConsensusKeysUpdatedV2 = (&new_keys).into();
5180 state
5181 .apply_event(StakeTableEvent::KeyUpdateV2(update))
5182 .unwrap()
5183 .unwrap();
5184
5185 let v = state.validators().get(&account).unwrap();
5186 let expected_bls = BLSPubKey::try_from(new_keys.bls_vk).unwrap();
5187 assert_eq!(v.stake_table_key.as_ref(), Some(&expected_bls));
5188 assert!(v.authenticated);
5191 }
5192
5193 #[test]
5194 fn test_key_update_bad_key_no_corruption() {
5195 let mut state_v1 = StakeTableState::default();
5197 let val = TestValidator::random();
5198 state_v1
5199 .apply_event(StakeTableEvent::Register((&val).into()))
5200 .unwrap()
5201 .unwrap();
5202 let prior_v1 = state_v1
5203 .validators()
5204 .get(&val.account)
5205 .unwrap()
5206 .stake_table_key;
5207 assert_matches!(
5208 state_v1.apply_event(StakeTableEvent::KeyUpdate(ConsensusKeysUpdated {
5209 account: val.account,
5210 blsVK: zero_g2(),
5211 schnorrVK: StateKeyPair::generate().ver_key().into(),
5212 })),
5213 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
5214 );
5215 assert_eq!(
5216 state_v1
5217 .validators()
5218 .get(&val.account)
5219 .unwrap()
5220 .stake_table_key,
5221 prior_v1
5222 );
5223
5224 let mut state_v2 = StakeTableState::default();
5226 let val2 = TestValidator::random();
5227 state_v2
5228 .apply_event(StakeTableEvent::RegisterV2((&val2).into()))
5229 .unwrap()
5230 .unwrap();
5231 let prior_v2 = state_v2
5232 .validators()
5233 .get(&val2.account)
5234 .unwrap()
5235 .stake_table_key;
5236 assert_matches!(
5237 state_v2.apply_event(StakeTableEvent::KeyUpdateV2(ConsensusKeysUpdatedV2 {
5238 account: val2.account,
5239 blsVK: zero_g2(),
5240 schnorrVK: StateKeyPair::generate().ver_key().into(),
5241 blsSig: zero_g1().into(),
5242 schnorrSig: Bytes::default(),
5243 })),
5244 Ok(Err(ExpectedStakeTableError::InvalidBlsKey))
5245 );
5246 assert_eq!(
5247 state_v2
5248 .validators()
5249 .get(&val2.account)
5250 .unwrap()
5251 .stake_table_key,
5252 prior_v2
5253 );
5254 }
5255}
5256
5257#[cfg(test)]
5261mod proptest_x25519_key {
5262 use alloy::{
5263 primitives::FixedBytes,
5264 providers::{ProviderBuilder, WalletProvider},
5265 };
5266 use proptest::{
5267 prelude::*,
5268 test_runner::{Config as ProptestConfig, TestRunner},
5269 };
5270
5271 use super::{testing::TestValidator, *};
5272
5273 fn x25519_key_strategy() -> impl Strategy<Value = [u8; 32]> {
5274 let mut p = [0xffu8; 32];
5276 p[0] = 0xed;
5277 p[31] = 0x7f;
5278 prop_oneof![
5279 any::<[u8; 32]>(),
5281 Just([0u8; 32]),
5283 Just(p),
5285 (0..19u8).prop_map(move |k| {
5287 let mut key = p;
5288 key[0] += k;
5289 key
5290 }),
5291 Just({
5293 let mut key = p;
5294 key[0] = 0xec;
5295 key
5296 }),
5297 Just([0xffu8; 32]),
5300 Just({
5302 let mut key = [0u8; 32];
5303 key[31] = 0x80;
5304 key
5305 }),
5306 (0..=2u8).prop_map(|v| {
5308 let mut key = [0u8; 32];
5309 key[0] = v;
5310 key
5311 }),
5312 ]
5313 }
5314
5315 #[test]
5316 fn solidity_rust_x25519_validation_equivalence() {
5317 let rt = tokio::runtime::Runtime::new().unwrap();
5318
5319 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
5320 let sender = provider.default_signer_address();
5321 let contract_addr = rt.block_on(async {
5322 let contract = StakeTableV3::deploy(&provider).await.unwrap();
5323 *contract.address()
5324 });
5325 let contract = StakeTableV3::new(contract_addr, &provider);
5326
5327 let registered = TestValidator::random_update_keys(sender, 100).with_x25519_key([7u8; 32]);
5330 rt.block_on(async {
5331 let receipt = contract
5332 .registerValidatorV3(
5333 registered.bls_vk,
5334 registered.schnorr_vk,
5335 registered.bls_sig.into(),
5336 registered.schnorr_sig.clone(),
5337 registered.commission,
5338 String::new(),
5339 FixedBytes(registered.x25519_key),
5340 registered.p2p_addr.clone(),
5341 )
5342 .send()
5343 .await
5344 .unwrap()
5345 .get_receipt()
5346 .await
5347 .unwrap();
5348 assert!(receipt.status());
5349 });
5350
5351 let mut registered_state = StakeTableState::default();
5353 registered_state
5354 .apply_event(StakeTableEvent::RegisterV3((®istered).into()))
5355 .unwrap()
5356 .unwrap();
5357
5358 let base = TestValidator::random();
5359
5360 let cases = std::env::var("PROPTEST_CASES")
5361 .ok()
5362 .and_then(|v| v.parse().ok())
5363 .unwrap_or(512);
5364 let mut runner = TestRunner::new(ProptestConfig {
5365 cases,
5366 ..ProptestConfig::default()
5367 });
5368
5369 runner
5371 .run(&x25519_key_strategy(), |key| {
5372 let sol_valid = rt.block_on(async {
5373 contract
5374 .registerValidatorV3(
5375 base.bls_vk,
5376 base.schnorr_vk,
5377 base.bls_sig.into(),
5378 base.schnorr_sig.clone(),
5379 base.commission,
5380 String::new(),
5381 FixedBytes(key),
5382 base.p2p_addr.clone(),
5383 )
5384 .from(base.account)
5385 .call()
5386 .await
5387 .is_ok()
5388 });
5389
5390 let val = base.clone().with_x25519_key(key);
5391 let mut state = StakeTableState::default();
5392 let result = state.apply_event(StakeTableEvent::RegisterV3((&val).into()));
5393
5394 if sol_valid {
5395 prop_assert!(
5396 result.is_ok(),
5397 "Solidity accepted registration with x25519 key {key:?} but apply_event \
5398 failed: {result:?}"
5399 );
5400 let stored = state.validators().get(&val.account).unwrap().x25519_key;
5401 prop_assert!(stored.is_some());
5404 prop_assert_eq!(stored, parse_x25519_key(key).ok());
5405 }
5406 Ok(())
5407 })
5408 .unwrap();
5409
5410 runner
5412 .run(&x25519_key_strategy(), |key| {
5413 let sol_valid = rt.block_on(async {
5414 contract
5415 .updateX25519Key(FixedBytes(key))
5416 .from(sender)
5417 .call()
5418 .await
5419 .is_ok()
5420 });
5421
5422 let mut state = registered_state.clone();
5423 let result = state.apply_event(registered.x25519_update(key));
5424
5425 if sol_valid {
5426 prop_assert!(
5427 result.is_ok(),
5428 "Solidity accepted x25519 key update {key:?} but apply_event failed: \
5429 {result:?}"
5430 );
5431 let stored = state.validators().get(&sender).unwrap().x25519_key;
5432 prop_assert!(stored.is_some());
5433 prop_assert_eq!(stored, parse_x25519_key(key).ok());
5434 }
5435 Ok(())
5436 })
5437 .unwrap();
5438 }
5439}
5440
5441#[cfg(all(test, feature = "rlp"))]
5442mod rlp_tests {
5443 use alloy_rlp::{Decodable, Encodable};
5444 use hotshot_types::{signature_key::BLSPubKey, traits::signature_key::StateSignatureKey};
5445
5446 use super::*;
5447
5448 #[test]
5449 fn test_registered_validator_round_canonical_order() {
5450 let seed = [0; 32];
5451 let validator1 = RegisteredValidator {
5452 account: Address::random(),
5453 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed(seed, 0).0),
5454 state_ver_key: Some(SchnorrPubKey::generated_from_seed_indexed(seed, 0).0),
5455 stake: U256::MAX,
5456 commission: u16::MAX,
5457 delegators: [
5458 (Address::random(), U256::ZERO),
5459 (Address::random(), U256::MAX),
5460 ]
5461 .into_iter()
5462 .collect(),
5463 authenticated: true,
5464 x25519_key: Some(x25519::Keypair::generate().unwrap().public_key()),
5465 p2p_addr: Some(NetAddr::named("localhost", 8080)),
5466 };
5467 let validator2 = RegisteredValidator {
5468 account: Address::random(),
5469 ..validator1.clone()
5470 };
5471
5472 let mut validator_exits1 = HashSet::with_capacity(100);
5475 let mut validator_exits2 = HashSet::with_capacity(10_000);
5476 let mut used_bls_keys1 = HashSet::with_capacity(100);
5477 let mut used_bls_keys2 = HashSet::with_capacity(10_000);
5478 let mut used_schnorr_keys1 = HashSet::with_capacity(100);
5479 let mut used_schnorr_keys2 = HashSet::with_capacity(10_000);
5480 let mut used_x25519_keys1 = HashSet::with_capacity(100);
5481 let mut used_x25519_keys2 = HashSet::with_capacity(10_000);
5482
5483 for i in 0..100 {
5484 let exit = Address::random();
5485 let bls_key = BLSPubKey::generated_from_seed_indexed([0; 32], i).0;
5486 let schnorr_key = SchnorrPubKey::generated_from_seed_indexed([0; 32], i).0;
5487 let x25519_key = x25519::Keypair::generate().unwrap().public_key();
5488
5489 validator_exits1.insert(exit);
5490 validator_exits2.insert(exit);
5491 used_bls_keys1.insert(bls_key);
5492 used_bls_keys2.insert(bls_key);
5493 used_schnorr_keys1.insert(schnorr_key.clone());
5494 used_schnorr_keys2.insert(schnorr_key);
5495 used_x25519_keys1.insert(x25519_key);
5496 used_x25519_keys2.insert(x25519_key);
5497 }
5498 assert_eq!(validator_exits1, validator_exits2);
5499 assert_eq!(used_bls_keys1, used_bls_keys2);
5500 assert_eq!(used_schnorr_keys1, used_schnorr_keys2);
5501 assert_eq!(used_x25519_keys1, used_x25519_keys2);
5502 assert_ne!(
5503 validator_exits1.iter().collect::<Vec<_>>(),
5504 validator_exits2.iter().collect::<Vec<_>>()
5505 );
5506 assert_ne!(
5507 used_bls_keys1.iter().collect::<Vec<_>>(),
5508 used_bls_keys2.iter().collect::<Vec<_>>()
5509 );
5510 assert_ne!(
5511 used_schnorr_keys1.iter().collect::<Vec<_>>(),
5512 used_schnorr_keys2.iter().collect::<Vec<_>>()
5513 );
5514 assert_ne!(
5515 used_x25519_keys1.iter().collect::<Vec<_>>(),
5516 used_x25519_keys2.iter().collect::<Vec<_>>()
5517 );
5518
5519 let state1 = StakeTableState {
5520 validators: [
5521 (validator1.account, validator1.clone()),
5522 (validator2.account, validator2.clone()),
5523 ]
5524 .into_iter()
5525 .collect(),
5526 validator_exits: validator_exits1,
5527 used_bls_keys: used_bls_keys1,
5528 used_schnorr_keys: used_schnorr_keys1,
5529 used_x25519_keys: used_x25519_keys1,
5530 };
5531 let state2 = StakeTableState {
5532 validators: [
5533 (validator2.account, validator2.clone()),
5534 (validator1.account, validator1.clone()),
5535 ]
5536 .into_iter()
5537 .collect(),
5538 validator_exits: validator_exits2,
5539 used_bls_keys: used_bls_keys2,
5540 used_schnorr_keys: used_schnorr_keys2,
5541 used_x25519_keys: used_x25519_keys2,
5542 };
5543 assert_eq!(state1, state2);
5544 assert_eq!(state1.commit(), state2.commit());
5545
5546 let mut bytes1 = vec![];
5547 state1.encode(&mut bytes1);
5548 assert_eq!(bytes1.len(), state1.length());
5549
5550 let mut bytes2 = vec![];
5551 state2.encode(&mut bytes2);
5552 assert_eq!(bytes2.len(), state2.length());
5553
5554 assert_eq!(bytes1, bytes2);
5555 assert_eq!(
5556 state1,
5557 StakeTableState::decode(&mut bytes2.as_slice()).unwrap()
5558 );
5559 assert_eq!(
5560 state2,
5561 StakeTableState::decode(&mut bytes1.as_slice()).unwrap()
5562 );
5563 }
5564}