1use std::{collections::HashMap, sync::Arc};
2
3use alloy::{
4 primitives::{Address, Log, U256},
5 transports::{RpcError, TransportErrorKind},
6};
7use async_lock::{Mutex, RwLock};
8use committable::{Commitment, Committable, RawCommitmentBuilder};
9use derive_more::derive::{From, Into};
10use hotshot_contract_adapter::sol_types::StakeTableV3::{
11 CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, P2pAddrUpdated,
12 Undelegated, UndelegatedV2, ValidatorExit, ValidatorExitV2, ValidatorRegistered,
13 ValidatorRegisteredV2, ValidatorRegisteredV3, X25519KeyUpdated,
14};
15use hotshot_types::{
16 PeerConfig,
17 addr::{InvalidNetAddr, NetAddr},
18 data::EpochNumber,
19 light_client::StateVerKey,
20 network::PeerConfigKeys,
21 traits::signature_key::SignatureKey,
22 x25519,
23};
24use itertools::Itertools;
25use jf_utils::to_bytes;
26use serde::{Deserialize, Serialize};
27use thiserror::Error;
28use tokio::task::JoinHandle;
29use vbs::version::Version;
30use versions::NEW_PROTOCOL_VERSION;
31
32#[cfg(feature = "node")]
33use super::L1Client;
34use crate::{
35 AuthenticatedValidatorMap, SeqTypes,
36 traits::{MembershipPersistence, StateCatchup},
37 v0::{ChainConfig, impls::StakeTableHash},
38 v0_3::RewardAmount,
39};
40#[derive(Debug, Clone, Serialize, Deserialize, From)]
42pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
43
44#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
45pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
47
48#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
49pub struct StakeTable(pub Vec<PeerConfig<SeqTypes>>);
51
52pub(crate) fn to_fixed_bytes(value: U256) -> [u8; std::mem::size_of::<U256>()] {
53 let bytes: [u8; std::mem::size_of::<U256>()] = value.to_le_bytes();
54 bytes
55}
56
57#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
66#[serde(bound(deserialize = ""))]
67pub struct RegisteredValidator<KEY: SignatureKey> {
68 pub account: Address,
69 pub stake_table_key: Option<KEY>,
71 pub state_ver_key: Option<StateVerKey>,
73 pub stake: U256,
75 pub commission: u16,
78 pub delegators: HashMap<Address, U256>,
79 pub authenticated: bool,
82 pub x25519_key: Option<x25519::PublicKey>,
84 pub p2p_addr: Option<NetAddr>,
86}
87
88#[cfg(feature = "rlp")]
89mod registered_validator_rlp {
90 use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable};
91 use anyhow::Context;
92
93 use super::*;
94
95 #[derive(RlpEncodable, RlpDecodable, PartialEq, Eq, PartialOrd, Ord)]
96 struct DelegatorRlp {
97 address: Address,
98 stake: U256,
99 }
100
101 #[derive(RlpEncodable, RlpDecodable)]
102 #[rlp(trailing(canonical))]
103 struct RegisteredValidatorRlp {
104 account: Address,
105 stake: U256,
106 commission: u16,
107 delegators: Vec<DelegatorRlp>,
108 authenticated: bool,
109 stake_table_key: Option<Vec<u8>>,
110 state_ver_key: Option<Vec<u8>>,
111 x25519_key: Option<Vec<u8>>,
112 p2p_addr: Option<Vec<u8>>,
113 }
114
115 impl<KEY: SignatureKey> TryFrom<&RegisteredValidator<KEY>> for RegisteredValidatorRlp {
116 type Error = anyhow::Error;
117
118 fn try_from(v: &RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
119 let mut delegators = v
120 .delegators
121 .iter()
122 .map(|(&address, &stake)| DelegatorRlp { address, stake })
123 .collect::<Vec<_>>();
124 delegators.sort();
127
128 Ok(Self {
129 account: v.account,
130 stake: v.stake,
131 commission: v.commission,
132 delegators,
133 authenticated: v.authenticated,
134 stake_table_key: match &v.stake_table_key {
135 Some(key) => {
136 Some(bincode::serialize(key).context("serializing stake_table_key")?)
137 },
138 None => None,
139 },
140 state_ver_key: match &v.state_ver_key {
141 Some(key) => {
142 Some(bincode::serialize(key).context("serializing state_ver_key")?)
143 },
144 None => None,
145 },
146 x25519_key: match &v.x25519_key {
147 Some(key) => Some(bincode::serialize(key).context("serializing x25519_key")?),
148 None => None,
149 },
150 p2p_addr: match &v.p2p_addr {
151 Some(addr) => Some(bincode::serialize(addr).context("serializing p2p2_addr")?),
152 None => None,
153 },
154 })
155 }
156 }
157
158 impl<KEY: SignatureKey> TryFrom<RegisteredValidatorRlp> for RegisteredValidator<KEY> {
159 type Error = anyhow::Error;
160
161 fn try_from(v: RegisteredValidatorRlp) -> Result<Self, Self::Error> {
162 Ok(Self {
163 account: v.account,
164 stake: v.stake,
165 commission: v.commission,
166 delegators: v
167 .delegators
168 .into_iter()
169 .map(|d| (d.address, d.stake))
170 .collect(),
171 authenticated: v.authenticated,
172 stake_table_key: if let Some(bytes) = &v.stake_table_key {
173 Some(bincode::deserialize(bytes).context("deserializing stake_table_key")?)
174 } else {
175 None
176 },
177 state_ver_key: if let Some(bytes) = &v.state_ver_key {
178 Some(bincode::deserialize(bytes).context("deserializing state_ver_key")?)
179 } else {
180 None
181 },
182 x25519_key: if let Some(bytes) = &v.x25519_key {
183 Some(bincode::deserialize(bytes).context("deserializing x25519_key")?)
184 } else {
185 None
186 },
187 p2p_addr: if let Some(bytes) = &v.p2p_addr {
188 Some(bincode::deserialize(bytes).context("deserializing p2p_addr")?)
189 } else {
190 None
191 },
192 })
193 }
194 }
195
196 impl<KEY: SignatureKey> Encodable for RegisteredValidator<KEY> {
197 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
198 RegisteredValidatorRlp::try_from(self)
199 .expect("failed to canonicalize RegisteredValidator")
200 .encode(out);
201 }
202
203 fn length(&self) -> usize {
204 RegisteredValidatorRlp::try_from(self)
205 .expect("failed to canonicalize RegisteredValidator")
206 .length()
207 }
208 }
209
210 impl<KEY: SignatureKey> Decodable for RegisteredValidator<KEY> {
211 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
212 let rlp = RegisteredValidatorRlp::decode(buf)?;
213 rlp.try_into().map_err(|err| {
214 tracing::error!("failed to parse canonicalized RegisteredValidator: {err:#}");
215 alloy_rlp::Error::Custom("failed to parse canonicalized RegisteredValidator")
216 })
217 }
218 }
219}
220
221#[derive(serde::Serialize, Clone, Debug, PartialEq, Eq)]
225pub struct AuthenticatedValidator<KEY: SignatureKey>(RegisteredValidator<KEY>);
226
227impl<'de, KEY: SignatureKey> Deserialize<'de> for AuthenticatedValidator<KEY> {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: serde::Deserializer<'de>,
231 {
232 let inner = RegisteredValidator::deserialize(deserializer)?;
233 if !inner.authenticated {
234 return Err(serde::de::Error::custom(
235 "cannot deserialize unauthenticated validator as AuthenticatedValidator",
236 ));
237 }
238 if inner.stake_table_key.is_none() {
239 return Err(serde::de::Error::custom(
240 "cannot deserialize validator without BLS key as AuthenticatedValidator",
241 ));
242 }
243 if inner.state_ver_key.is_none() {
244 return Err(serde::de::Error::custom(
245 "cannot deserialize validator without Schnorr key as AuthenticatedValidator",
246 ));
247 }
248 Ok(AuthenticatedValidator(inner))
249 }
250}
251
252impl<KEY: SignatureKey> AuthenticatedValidator<KEY> {
253 pub fn into_inner(self) -> RegisteredValidator<KEY> {
254 self.0
255 }
256
257 pub fn ensure_eligible(&self, protocol_version: Version) -> Result<(), IneligibleValidator> {
282 if protocol_version < NEW_PROTOCOL_VERSION {
283 return Ok(());
284 }
285 if self.x25519_key.is_none() {
286 return Err(IneligibleValidator::MissingX25519Key);
287 }
288 let addr = self
289 .p2p_addr
290 .as_ref()
291 .ok_or(IneligibleValidator::MissingP2pAddr)?;
292 addr.validate()
293 .map_err(|source| IneligibleValidator::InvalidP2pAddr {
294 addr: addr.clone(),
295 source,
296 })
297 }
298
299 pub fn stake_table_key(&self) -> &KEY {
300 self.0
301 .stake_table_key
302 .as_ref()
303 .expect("AuthenticatedValidator invariant: key is Some")
304 }
305
306 pub fn state_ver_key(&self) -> &StateVerKey {
307 self.0
308 .state_ver_key
309 .as_ref()
310 .expect("AuthenticatedValidator invariant: state_ver_key is Some")
311 }
312}
313
314impl<KEY: SignatureKey> std::ops::Deref for AuthenticatedValidator<KEY> {
315 type Target = RegisteredValidator<KEY>;
316
317 fn deref(&self) -> &Self::Target {
318 &self.0
319 }
320}
321
322#[derive(Debug, Error)]
323#[error("Validator {0:#x} not authenticated (invalid registration signature)")]
324pub struct UnauthenticatedValidatorError(pub Address);
325
326#[derive(Debug, Error)]
328pub enum IneligibleValidator {
329 #[error("missing x25519 key")]
330 MissingX25519Key,
331 #[error("missing p2p address")]
332 MissingP2pAddr,
333 #[error("invalid p2p address {addr:?}: {source}")]
334 InvalidP2pAddr {
335 addr: NetAddr,
336 source: InvalidNetAddr,
337 },
338}
339
340impl<KEY: SignatureKey + Clone> TryFrom<&RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
341 type Error = UnauthenticatedValidatorError;
342
343 fn try_from(v: &RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
344 if !v.authenticated || v.stake_table_key.is_none() || v.state_ver_key.is_none() {
345 return Err(UnauthenticatedValidatorError(v.account));
346 }
347 Ok(AuthenticatedValidator(v.clone()))
348 }
349}
350
351impl<KEY: SignatureKey> TryFrom<RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
352 type Error = UnauthenticatedValidatorError;
353
354 fn try_from(v: RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
355 if !v.authenticated || v.stake_table_key.is_none() || v.state_ver_key.is_none() {
356 return Err(UnauthenticatedValidatorError(v.account));
357 }
358 Ok(AuthenticatedValidator(v))
359 }
360}
361
362impl<KEY: SignatureKey> From<AuthenticatedValidator<KEY>> for RegisteredValidator<KEY> {
363 fn from(v: AuthenticatedValidator<KEY>) -> Self {
364 v.into_inner()
365 }
366}
367
368impl<KEY: SignatureKey> Committable for RegisteredValidator<KEY> {
369 fn commit(&self) -> Commitment<Self> {
370 let mut builder =
371 RawCommitmentBuilder::new(&Self::tag()).fixed_size_field("account", &self.account);
372
373 builder = match &self.stake_table_key {
376 Some(key) => builder.var_size_field("stake_table_key", key.to_bytes().as_slice()),
377 None => builder.constant_str("no_bls_key"),
378 };
379
380 builder = match &self.state_ver_key {
383 Some(key) => builder.var_size_field("state_ver_key", &to_bytes!(key).unwrap()),
384 None => builder.constant_str("no_schnorr_key"),
385 };
386
387 builder = builder
388 .fixed_size_field("stake", &to_fixed_bytes(self.stake))
389 .constant_str("commission")
390 .u16(self.commission);
391
392 if let Some(key) = &self.x25519_key {
396 builder = builder.var_size_field("x25519_key", key.as_slice());
397 }
398 if let Some(addr) = &self.p2p_addr {
399 builder = builder.var_size_field("p2p_addr", addr.unbracketed_string().as_bytes());
400 }
401
402 builder = builder.constant_str("delegators");
403 for (address, stake) in self.delegators.iter().sorted() {
404 builder = builder
405 .fixed_size_bytes(address)
406 .fixed_size_bytes(&to_fixed_bytes(*stake));
407 }
408
409 if !self.authenticated {
411 builder = builder.constant_str("unauthenticated");
412 }
413
414 builder.finalize()
415 }
416
417 fn tag() -> String {
418 "VALIDATOR".to_string()
419 }
420}
421
422#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
423#[serde(bound(deserialize = ""))]
424pub struct Delegator {
425 pub address: Address,
426 pub validator: Address,
427 pub stake: U256,
428}
429
430pub type IndexedStake = (
432 EpochNumber,
433 (AuthenticatedValidatorMap, Option<RewardAmount>),
434 Option<StakeTableHash>,
435);
436
437#[derive(Clone, derive_more::derive::Debug)]
438pub struct Fetcher {
439 #[debug(skip)]
441 pub(crate) peers: Arc<dyn StateCatchup>,
442 #[debug(skip)]
444 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
445 #[cfg(feature = "node")]
447 pub(crate) l1_client: L1Client,
448 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
450 #[cfg_attr(not(feature = "node"), allow(dead_code))]
451 pub(crate) update_task: Arc<StakeTableUpdateTask>,
452 pub(crate) initial_supply: Arc<RwLock<Option<U256>>>,
453}
454
455#[derive(Debug, Default)]
456pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
457
458impl Drop for StakeTableUpdateTask {
459 fn drop(&mut self) {
460 if let Some(task) = self.0.get_mut().take() {
461 task.abort();
462 }
463 }
464}
465
466pub type EventKey = (u64, u64);
468
469#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
470pub enum StakeTableEvent {
471 Register(ValidatorRegistered),
472 RegisterV2(ValidatorRegisteredV2),
473 Deregister(ValidatorExit),
474 DeregisterV2(ValidatorExitV2),
475 Delegate(Delegated),
476 Undelegate(Undelegated),
477 UndelegateV2(UndelegatedV2),
478 KeyUpdate(ConsensusKeysUpdated),
479 KeyUpdateV2(ConsensusKeysUpdatedV2),
480 CommissionUpdate(CommissionUpdated),
481 RegisterV3(ValidatorRegisteredV3),
482 X25519KeyUpdate(X25519KeyUpdated),
483 P2pAddrUpdate(P2pAddrUpdated),
484}
485
486#[derive(Debug, Error)]
487pub enum StakeTableError {
488 #[error("Validator {0:#x} already registered")]
489 AlreadyRegistered(Address),
490 #[error("Validator {0:#x} not found")]
491 ValidatorNotFound(Address),
492 #[error("Delegator {0:#x} not found")]
493 DelegatorNotFound(Address),
494 #[error("BLS key already used: {0}")]
495 BlsKeyAlreadyUsed(String),
496 #[error("Insufficient stake to undelegate")]
497 InsufficientStake,
498 #[error("Event authentication failed: {0}")]
499 AuthenticationFailed(String),
500 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
501 NoValidValidators,
502 #[error("Could not compute maximum stake from filtered validators")]
503 MissingMaximumStake,
504 #[error("Overflow when calculating minimum stake threshold")]
505 MinimumStakeOverflow,
506 #[error("Delegator {0:#x} has 0 stake")]
507 ZeroDelegatorStake(Address),
508 #[error("Failed to hash stake table: {0}")]
509 HashError(#[from] bincode::Error),
510 #[error("Validator {0:#x} already exited and cannot be re-registered")]
511 ValidatorAlreadyExited(Address),
512 #[error("Validator {0:#x} has invalid commission {1}")]
513 InvalidCommission(Address, u16),
514 #[error("x25519 key already used: {0}")]
515 X25519KeyAlreadyUsed(String),
516 #[error("Invalid x25519 key: {0}")]
517 InvalidX25519Key(String),
518 #[error("Stake table event decode error {0}")]
519 StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
520 #[error("Stake table events sorting error: {0}")]
521 EventSortingError(#[from] EventSortingError),
522}
523
524#[derive(Debug, Error)]
525pub enum ExpectedStakeTableError {
526 #[error("Schnorr key already used: {0}")]
527 SchnorrKeyAlreadyUsed(String),
528 #[error("Invalid BLS key")]
529 InvalidBlsKey,
530 #[error("Invalid Schnorr key")]
531 InvalidSchnorrKey,
532}
533
534#[derive(Debug, Error)]
535pub enum FetchRewardError {
536 #[error("No stake table contract address found in chain config")]
537 MissingStakeTableContract,
538
539 #[error("Token address fetch failed: {0}")]
540 TokenAddressFetch(#[source] alloy::contract::Error),
541
542 #[error("Token Initialized event logs are empty")]
543 MissingInitializedEvent,
544
545 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
546 MissingTransactionHash { init_log: Log },
547
548 #[error("Block number not found in Initialized event log")]
549 MissingBlockNumber,
550
551 #[error("Transfer event query failed: {0}")]
552 TransferEventQuery(#[source] alloy::contract::Error),
553
554 #[error("No Transfer event found in the Initialized event block")]
555 MissingTransferEvent,
556
557 #[error("Division by zero {0}")]
558 DivisionByZero(&'static str),
559
560 #[error("Overflow {0}")]
561 Overflow(&'static str),
562
563 #[error("Contract call failed: {0}")]
564 ContractCall(#[source] alloy::contract::Error),
565
566 #[error("Rpc call failed: {0}")]
567 Rpc(#[source] RpcError<TransportErrorKind>),
568
569 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
570 ExceededMaxScanRange(u64),
571
572 #[error("Scanning for Initialized event failed: {0}")]
573 ScanQueryFailed(#[source] alloy::contract::Error),
574}
575
576#[derive(Debug, thiserror::Error)]
577pub enum EventSortingError {
578 #[error("Missing block number in log")]
579 MissingBlockNumber,
580
581 #[error("Missing log index in log")]
582 MissingLogIndex,
583
584 #[error("Invalid stake table event")]
585 InvalidStakeTableEvent,
586}
587
588#[cfg(test)]
589mod tests {
590 use std::collections::HashMap;
591
592 use alloy::primitives::{Address, U256};
593 use committable::Committable;
594 use hotshot_types::{
595 addr::NetAddr, light_client::StateVerKey, signature_key::BLSPubKey,
596 traits::signature_key::SignatureKey, x25519,
597 };
598
599 use super::{AuthenticatedValidator, RegisteredValidator};
600
601 #[test]
603 fn test_commitment_changes_with_x25519_and_p2p_fields() {
604 let base = RegisteredValidator::<BLSPubKey>::mock();
605 assert!(base.x25519_key.is_none());
606 assert!(base.p2p_addr.is_none());
607 let commit_base = base.commit();
608
609 let mut with_x25519 = base.clone();
610 with_x25519.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
611 let commit_x25519 = with_x25519.commit();
612
613 let mut with_p2p = base.clone();
614 with_p2p.p2p_addr = Some("127.0.0.1:8080".parse::<NetAddr>().unwrap());
615 let commit_p2p = with_p2p.commit();
616
617 assert_ne!(commit_base, commit_x25519);
618 assert_ne!(commit_base, commit_p2p);
619 assert_ne!(commit_x25519, commit_p2p);
620 }
621
622 #[test]
623 fn test_commitment_of_ipv6_addr_is_frozen() {
624 let p2p_addr: NetAddr = "[2001:db8::1]:9977".parse().unwrap();
625 assert_eq!(p2p_addr.unbracketed_string(), "2001:db8::1:9977");
626
627 let validator = RegisteredValidator::<BLSPubKey> {
628 account: Address::repeat_byte(7),
629 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0),
630 state_ver_key: Some(StateVerKey::default()),
631 stake: U256::from(1000),
632 commission: 500,
633 delegators: HashMap::new(),
634 authenticated: true,
635 x25519_key: None,
636 p2p_addr: Some(p2p_addr),
637 };
638 assert_eq!(
639 validator.commit().to_string(),
640 "VALIDATOR~7K4UdfSAqHRUGHW6HvabPzbi7dnJxEbHfO66O3eO3Sbw"
641 );
642 }
643
644 #[test]
647 fn test_unauthenticated_validator_commitment_differs() {
648 let account = Address::random();
649 let stake_table_key = Some(BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0);
650 let state_ver_key = Some(StateVerKey::default());
651 let stake = U256::from(1000);
652 let commission = 500u16;
653 let delegators = HashMap::new();
654
655 let authenticated = RegisteredValidator {
656 account,
657 stake_table_key,
658 state_ver_key: state_ver_key.clone(),
659 stake,
660 commission,
661 delegators: delegators.clone(),
662 authenticated: true,
663 x25519_key: None,
664 p2p_addr: None,
665 };
666
667 let unauthenticated = RegisteredValidator {
668 account,
669 stake_table_key,
670 state_ver_key,
671 stake,
672 commission,
673 delegators,
674 authenticated: false,
675 x25519_key: None,
676 p2p_addr: None,
677 };
678
679 let auth_commitment = authenticated.commit();
680 let unauth_commitment = unauthenticated.commit();
681 assert_ne!(
682 auth_commitment.as_ref() as &[u8],
683 unauth_commitment.as_ref() as &[u8]
684 );
685 }
686
687 #[test]
688 fn test_registered_validator_serde_roundtrip_with_none_key() {
689 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
690 account: Address::random(),
691 stake_table_key: None,
692 state_ver_key: Some(StateVerKey::default()),
693 stake: U256::from(42u64),
694 commission: 1000,
695 delegators: HashMap::new(),
696 authenticated: false,
697 x25519_key: None,
698 p2p_addr: None,
699 };
700 let json = serde_json::to_string(&v).unwrap();
701 let v2: RegisteredValidator<BLSPubKey> = serde_json::from_str(&json).unwrap();
702 assert_eq!(v, v2);
703 assert!(v2.stake_table_key.is_none());
704 }
705
706 #[test]
707 fn test_authenticated_try_from_rejects_none_key() {
708 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
709 account: Address::random(),
710 stake_table_key: None,
711 state_ver_key: Some(StateVerKey::default()),
712 stake: U256::from(1u64),
713 commission: 0,
714 delegators: HashMap::new(),
715 authenticated: true,
716 x25519_key: None,
717 p2p_addr: None,
718 };
719 assert!(AuthenticatedValidator::try_from(&v).is_err());
720 assert!(AuthenticatedValidator::try_from(v).is_err());
721 }
722
723 #[test]
724 fn test_authenticated_try_from_rejects_none_schnorr_key() {
725 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
726 account: Address::random(),
727 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0),
728 state_ver_key: None,
729 stake: U256::from(1u64),
730 commission: 0,
731 delegators: HashMap::new(),
732 authenticated: true,
733 x25519_key: None,
734 p2p_addr: None,
735 };
736 assert!(AuthenticatedValidator::try_from(&v).is_err());
737 assert!(AuthenticatedValidator::try_from(v).is_err());
738 }
739
740 #[test]
741 fn test_commit_none_vs_some_differs() {
742 let account = Address::random();
743 let state_ver_key = Some(StateVerKey::default());
744 let stake = U256::from(7u64);
745
746 let with_key: RegisteredValidator<BLSPubKey> = RegisteredValidator {
747 account,
748 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([2u8; 32], 0).0),
749 state_ver_key: state_ver_key.clone(),
750 stake,
751 commission: 100,
752 delegators: HashMap::new(),
753 authenticated: false,
754 x25519_key: None,
755 p2p_addr: None,
756 };
757 let without_key: RegisteredValidator<BLSPubKey> = RegisteredValidator {
758 account,
759 stake_table_key: None,
760 state_ver_key,
761 stake,
762 commission: 100,
763 delegators: HashMap::new(),
764 authenticated: false,
765 x25519_key: None,
766 p2p_addr: None,
767 };
768 assert_ne!(
769 with_key.commit().as_ref() as &[u8],
770 without_key.commit().as_ref() as &[u8]
771 );
772 }
773
774 #[test]
775 fn test_commit_none_vs_some_schnorr_differs() {
776 let account = Address::random();
777 let stake_table_key = Some(BLSPubKey::generated_from_seed_indexed([3u8; 32], 0).0);
778 let stake = U256::from(11u64);
779
780 let with_schnorr: RegisteredValidator<BLSPubKey> = RegisteredValidator {
781 account,
782 stake_table_key,
783 state_ver_key: Some(StateVerKey::default()),
784 stake,
785 commission: 100,
786 delegators: HashMap::new(),
787 authenticated: false,
788 x25519_key: None,
789 p2p_addr: None,
790 };
791 let without_schnorr: RegisteredValidator<BLSPubKey> = RegisteredValidator {
792 account,
793 stake_table_key,
794 state_ver_key: None,
795 stake,
796 commission: 100,
797 delegators: HashMap::new(),
798 authenticated: false,
799 x25519_key: None,
800 p2p_addr: None,
801 };
802 assert_ne!(
803 with_schnorr.commit().as_ref() as &[u8],
804 without_schnorr.commit().as_ref() as &[u8]
805 );
806 }
807}
808
809#[cfg(all(test, feature = "rlp"))]
810mod rlp_tests {
811 use alloy_rlp::{Decodable, Encodable};
812 use hotshot_types::{signature_key::BLSPubKey, traits::signature_key::StateSignatureKey};
813
814 use super::*;
815
816 #[test]
817 fn test_registered_validator_round_trip() {
818 let seed = [0; 32];
819 let validator = RegisteredValidator {
820 account: Address::random(),
821 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed(seed, 0).0),
822 state_ver_key: Some(StateVerKey::generated_from_seed_indexed(seed, 0).0),
823 stake: U256::MAX,
824 commission: u16::MAX,
825 delegators: [
826 (Address::random(), U256::ZERO),
827 (Address::random(), U256::MAX),
828 ]
829 .into_iter()
830 .collect(),
831 authenticated: true,
832 x25519_key: Some(x25519::Keypair::generate().unwrap().public_key()),
833 p2p_addr: Some(NetAddr::named("localhost", 8080)),
834 };
835
836 let mut bytes = vec![];
837 validator.encode(&mut bytes);
838 assert_eq!(bytes.len(), validator.length());
839 assert_eq!(
840 validator,
841 RegisteredValidator::decode(&mut bytes.as_slice()).unwrap()
842 );
843 }
844
845 #[test]
846 fn test_registered_validator_round_trip_empty() {
847 let validator = RegisteredValidator::<BLSPubKey> {
848 account: Address::random(),
849 stake_table_key: None,
850 state_ver_key: None,
851 stake: U256::MAX,
852 commission: u16::MAX,
853 delegators: Default::default(),
854 authenticated: true,
855 x25519_key: None,
856 p2p_addr: None,
857 };
858
859 let mut bytes = vec![];
860 validator.encode(&mut bytes);
861 assert_eq!(bytes.len(), validator.length());
862 assert_eq!(
863 validator,
864 RegisteredValidator::decode(&mut bytes.as_slice()).unwrap()
865 );
866 }
867
868 #[test]
869 fn test_registered_validator_round_canonical_order() {
870 let mut delegators1 = HashMap::with_capacity(100);
873 let mut delegators2 = HashMap::with_capacity(10_000);
874 for i in 0..100 {
875 let delegator = Address::random();
876 let amount = U256::from(i);
877 delegators1.insert(delegator, amount);
878 delegators2.insert(delegator, amount);
879 }
880 assert_eq!(delegators1, delegators2);
881 assert_ne!(
882 delegators1.iter().collect::<Vec<_>>(),
883 delegators2.iter().collect::<Vec<_>>()
884 );
885
886 let validator1 = RegisteredValidator::<BLSPubKey> {
887 account: Address::random(),
888 stake_table_key: None,
889 state_ver_key: None,
890 stake: U256::MAX,
891 commission: u16::MAX,
892 delegators: delegators1,
893 authenticated: true,
894 x25519_key: None,
895 p2p_addr: None,
896 };
897 let validator2 = RegisteredValidator::<BLSPubKey> {
898 delegators: delegators2,
899 ..validator1.clone()
900 };
901 assert_eq!(validator1, validator2);
902
903 let mut bytes1 = vec![];
904 validator1.encode(&mut bytes1);
905 assert_eq!(bytes1.len(), validator1.length());
906
907 let mut bytes2 = vec![];
908 validator2.encode(&mut bytes2);
909 assert_eq!(bytes2.len(), validator2.length());
910
911 assert_eq!(bytes1, bytes2);
912 assert_eq!(
913 validator1,
914 RegisteredValidator::decode(&mut bytes2.as_slice()).unwrap()
915 );
916 assert_eq!(
917 validator2,
918 RegisteredValidator::decode(&mut bytes1.as_slice()).unwrap()
919 );
920 }
921}