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, addr::NetAddr, data::EpochNumber, light_client::StateVerKey,
17 network::PeerConfigKeys, traits::signature_key::SignatureKey, x25519,
18};
19use itertools::Itertools;
20use jf_utils::to_bytes;
21use serde::{Deserialize, Serialize};
22use thiserror::Error;
23use tokio::task::JoinHandle;
24use vbs::version::Version;
25use versions::NEW_PROTOCOL_VERSION;
26
27#[cfg(feature = "node")]
28use super::L1Client;
29use crate::{
30 AuthenticatedValidatorMap, SeqTypes,
31 traits::{MembershipPersistence, StateCatchup},
32 v0::{ChainConfig, impls::StakeTableHash},
33 v0_3::RewardAmount,
34};
35#[derive(Debug, Clone, Serialize, Deserialize, From)]
37pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
38
39#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
40pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
42
43#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
44pub struct StakeTable(pub Vec<PeerConfig<SeqTypes>>);
46
47pub(crate) fn to_fixed_bytes(value: U256) -> [u8; std::mem::size_of::<U256>()] {
48 let bytes: [u8; std::mem::size_of::<U256>()] = value.to_le_bytes();
49 bytes
50}
51
52#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
61#[serde(bound(deserialize = ""))]
62pub struct RegisteredValidator<KEY: SignatureKey> {
63 pub account: Address,
64 pub stake_table_key: Option<KEY>,
66 pub state_ver_key: Option<StateVerKey>,
68 pub stake: U256,
70 pub commission: u16,
73 pub delegators: HashMap<Address, U256>,
74 pub authenticated: bool,
77 pub x25519_key: Option<x25519::PublicKey>,
79 pub p2p_addr: Option<NetAddr>,
81}
82
83#[derive(serde::Serialize, Clone, Debug, PartialEq, Eq)]
87pub struct AuthenticatedValidator<KEY: SignatureKey>(RegisteredValidator<KEY>);
88
89impl<'de, KEY: SignatureKey> Deserialize<'de> for AuthenticatedValidator<KEY> {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 let inner = RegisteredValidator::deserialize(deserializer)?;
95 if !inner.authenticated {
96 return Err(serde::de::Error::custom(
97 "cannot deserialize unauthenticated validator as AuthenticatedValidator",
98 ));
99 }
100 if inner.stake_table_key.is_none() {
101 return Err(serde::de::Error::custom(
102 "cannot deserialize validator without BLS key as AuthenticatedValidator",
103 ));
104 }
105 if inner.state_ver_key.is_none() {
106 return Err(serde::de::Error::custom(
107 "cannot deserialize validator without Schnorr key as AuthenticatedValidator",
108 ));
109 }
110 Ok(AuthenticatedValidator(inner))
111 }
112}
113
114impl<KEY: SignatureKey> AuthenticatedValidator<KEY> {
115 pub fn into_inner(self) -> RegisteredValidator<KEY> {
116 self.0
117 }
118
119 pub fn is_eligible(&self, protocol_version: Version) -> bool {
141 if protocol_version >= NEW_PROTOCOL_VERSION
142 && (self.x25519_key.is_none() || self.p2p_addr.is_none())
143 {
144 return false;
145 }
146 true
147 }
148
149 pub fn stake_table_key(&self) -> &KEY {
150 self.0
151 .stake_table_key
152 .as_ref()
153 .expect("AuthenticatedValidator invariant: key is Some")
154 }
155
156 pub fn state_ver_key(&self) -> &StateVerKey {
157 self.0
158 .state_ver_key
159 .as_ref()
160 .expect("AuthenticatedValidator invariant: state_ver_key is Some")
161 }
162}
163
164impl<KEY: SignatureKey> std::ops::Deref for AuthenticatedValidator<KEY> {
165 type Target = RegisteredValidator<KEY>;
166
167 fn deref(&self) -> &Self::Target {
168 &self.0
169 }
170}
171
172#[derive(Debug, Error)]
173#[error("Validator {0:#x} not authenticated (invalid registration signature)")]
174pub struct UnauthenticatedValidatorError(pub Address);
175
176impl<KEY: SignatureKey + Clone> TryFrom<&RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
177 type Error = UnauthenticatedValidatorError;
178
179 fn try_from(v: &RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
180 if !v.authenticated || v.stake_table_key.is_none() || v.state_ver_key.is_none() {
181 return Err(UnauthenticatedValidatorError(v.account));
182 }
183 Ok(AuthenticatedValidator(v.clone()))
184 }
185}
186
187impl<KEY: SignatureKey> TryFrom<RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
188 type Error = UnauthenticatedValidatorError;
189
190 fn try_from(v: RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
191 if !v.authenticated || v.stake_table_key.is_none() || v.state_ver_key.is_none() {
192 return Err(UnauthenticatedValidatorError(v.account));
193 }
194 Ok(AuthenticatedValidator(v))
195 }
196}
197
198impl<KEY: SignatureKey> From<AuthenticatedValidator<KEY>> for RegisteredValidator<KEY> {
199 fn from(v: AuthenticatedValidator<KEY>) -> Self {
200 v.into_inner()
201 }
202}
203
204impl<KEY: SignatureKey> Committable for RegisteredValidator<KEY> {
205 fn commit(&self) -> Commitment<Self> {
206 let mut builder =
207 RawCommitmentBuilder::new(&Self::tag()).fixed_size_field("account", &self.account);
208
209 builder = match &self.stake_table_key {
212 Some(key) => builder.var_size_field("stake_table_key", key.to_bytes().as_slice()),
213 None => builder.constant_str("no_bls_key"),
214 };
215
216 builder = match &self.state_ver_key {
219 Some(key) => builder.var_size_field("state_ver_key", &to_bytes!(key).unwrap()),
220 None => builder.constant_str("no_schnorr_key"),
221 };
222
223 builder = builder
224 .fixed_size_field("stake", &to_fixed_bytes(self.stake))
225 .constant_str("commission")
226 .u16(self.commission);
227
228 if let Some(key) = &self.x25519_key {
232 builder = builder.var_size_field("x25519_key", key.as_slice());
233 }
234 if let Some(addr) = &self.p2p_addr {
235 builder = builder.var_size_field("p2p_addr", addr.to_string().as_bytes());
236 }
237
238 builder = builder.constant_str("delegators");
239 for (address, stake) in self.delegators.iter().sorted() {
240 builder = builder
241 .fixed_size_bytes(address)
242 .fixed_size_bytes(&to_fixed_bytes(*stake));
243 }
244
245 if !self.authenticated {
247 builder = builder.constant_str("unauthenticated");
248 }
249
250 builder.finalize()
251 }
252
253 fn tag() -> String {
254 "VALIDATOR".to_string()
255 }
256}
257
258#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
259#[serde(bound(deserialize = ""))]
260pub struct Delegator {
261 pub address: Address,
262 pub validator: Address,
263 pub stake: U256,
264}
265
266pub type IndexedStake = (
268 EpochNumber,
269 (AuthenticatedValidatorMap, Option<RewardAmount>),
270 Option<StakeTableHash>,
271);
272
273#[derive(Clone, derive_more::derive::Debug)]
274pub struct Fetcher {
275 #[debug(skip)]
277 pub(crate) peers: Arc<dyn StateCatchup>,
278 #[debug(skip)]
280 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
281 #[cfg(feature = "node")]
283 pub(crate) l1_client: L1Client,
284 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
286 #[cfg_attr(not(feature = "node"), allow(dead_code))]
287 pub(crate) update_task: Arc<StakeTableUpdateTask>,
288 pub initial_supply: Arc<RwLock<Option<U256>>>,
289}
290
291#[derive(Debug, Default)]
292pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
293
294impl Drop for StakeTableUpdateTask {
295 fn drop(&mut self) {
296 if let Some(task) = self.0.get_mut().take() {
297 task.abort();
298 }
299 }
300}
301
302pub type EventKey = (u64, u64);
304
305#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
306pub enum StakeTableEvent {
307 Register(ValidatorRegistered),
308 RegisterV2(ValidatorRegisteredV2),
309 Deregister(ValidatorExit),
310 DeregisterV2(ValidatorExitV2),
311 Delegate(Delegated),
312 Undelegate(Undelegated),
313 UndelegateV2(UndelegatedV2),
314 KeyUpdate(ConsensusKeysUpdated),
315 KeyUpdateV2(ConsensusKeysUpdatedV2),
316 CommissionUpdate(CommissionUpdated),
317 RegisterV3(ValidatorRegisteredV3),
318 X25519KeyUpdate(X25519KeyUpdated),
319 P2pAddrUpdate(P2pAddrUpdated),
320}
321
322#[derive(Debug, Error)]
323pub enum StakeTableError {
324 #[error("Validator {0:#x} already registered")]
325 AlreadyRegistered(Address),
326 #[error("Validator {0:#x} not found")]
327 ValidatorNotFound(Address),
328 #[error("Delegator {0:#x} not found")]
329 DelegatorNotFound(Address),
330 #[error("BLS key already used: {0}")]
331 BlsKeyAlreadyUsed(String),
332 #[error("Insufficient stake to undelegate")]
333 InsufficientStake,
334 #[error("Event authentication failed: {0}")]
335 AuthenticationFailed(String),
336 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
337 NoValidValidators,
338 #[error("Could not compute maximum stake from filtered validators")]
339 MissingMaximumStake,
340 #[error("Overflow when calculating minimum stake threshold")]
341 MinimumStakeOverflow,
342 #[error("Delegator {0:#x} has 0 stake")]
343 ZeroDelegatorStake(Address),
344 #[error("Failed to hash stake table: {0}")]
345 HashError(#[from] bincode::Error),
346 #[error("Validator {0:#x} already exited and cannot be re-registered")]
347 ValidatorAlreadyExited(Address),
348 #[error("Validator {0:#x} has invalid commission {1}")]
349 InvalidCommission(Address, u16),
350 #[error("Schnorr key already used: {0}")]
351 SchnorrKeyAlreadyUsed(String),
352 #[error("x25519 key already used: {0}")]
353 X25519KeyAlreadyUsed(String),
354 #[error("Invalid x25519 key: {0}")]
355 InvalidX25519Key(String),
356 #[error("Stake table event decode error {0}")]
357 StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
358 #[error("Stake table events sorting error: {0}")]
359 EventSortingError(#[from] EventSortingError),
360}
361
362#[derive(Debug, Error)]
363pub enum ExpectedStakeTableError {
364 #[error("Schnorr key already used: {0}")]
365 SchnorrKeyAlreadyUsed(String),
366 #[error("Invalid BLS key")]
367 InvalidBlsKey,
368 #[error("Invalid Schnorr key")]
369 InvalidSchnorrKey,
370}
371
372#[derive(Debug, Error)]
373pub enum FetchRewardError {
374 #[error("No stake table contract address found in chain config")]
375 MissingStakeTableContract,
376
377 #[error("Token address fetch failed: {0}")]
378 TokenAddressFetch(#[source] alloy::contract::Error),
379
380 #[error("Token Initialized event logs are empty")]
381 MissingInitializedEvent,
382
383 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
384 MissingTransactionHash { init_log: Log },
385
386 #[error("Block number not found in Initialized event log")]
387 MissingBlockNumber,
388
389 #[error("Transfer event query failed: {0}")]
390 TransferEventQuery(#[source] alloy::contract::Error),
391
392 #[error("No Transfer event found in the Initialized event block")]
393 MissingTransferEvent,
394
395 #[error("Division by zero {0}")]
396 DivisionByZero(&'static str),
397
398 #[error("Overflow {0}")]
399 Overflow(&'static str),
400
401 #[error("Contract call failed: {0}")]
402 ContractCall(#[source] alloy::contract::Error),
403
404 #[error("Rpc call failed: {0}")]
405 Rpc(#[source] RpcError<TransportErrorKind>),
406
407 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
408 ExceededMaxScanRange(u64),
409
410 #[error("Scanning for Initialized event failed: {0}")]
411 ScanQueryFailed(#[source] alloy::contract::Error),
412}
413
414#[derive(Debug, thiserror::Error)]
415pub enum EventSortingError {
416 #[error("Missing block number in log")]
417 MissingBlockNumber,
418
419 #[error("Missing log index in log")]
420 MissingLogIndex,
421
422 #[error("Invalid stake table event")]
423 InvalidStakeTableEvent,
424}
425
426#[cfg(test)]
427mod tests {
428 use std::collections::HashMap;
429
430 use alloy::primitives::{Address, U256};
431 use committable::Committable;
432 use hotshot::types::{BLSPubKey, SignatureKey};
433 use hotshot_types::{addr::NetAddr, light_client::StateVerKey, x25519};
434
435 use super::{AuthenticatedValidator, RegisteredValidator};
436
437 #[test]
439 fn test_commitment_changes_with_x25519_and_p2p_fields() {
440 let base = RegisteredValidator::<BLSPubKey>::mock();
441 assert!(base.x25519_key.is_none());
442 assert!(base.p2p_addr.is_none());
443 let commit_base = base.commit();
444
445 let mut with_x25519 = base.clone();
446 with_x25519.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
447 let commit_x25519 = with_x25519.commit();
448
449 let mut with_p2p = base.clone();
450 with_p2p.p2p_addr = Some("127.0.0.1:8080".parse::<NetAddr>().unwrap());
451 let commit_p2p = with_p2p.commit();
452
453 assert_ne!(commit_base, commit_x25519);
454 assert_ne!(commit_base, commit_p2p);
455 assert_ne!(commit_x25519, commit_p2p);
456 }
457
458 #[test]
461 fn test_unauthenticated_validator_commitment_differs() {
462 let account = Address::random();
463 let stake_table_key = Some(BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0);
464 let state_ver_key = Some(StateVerKey::default());
465 let stake = U256::from(1000);
466 let commission = 500u16;
467 let delegators = HashMap::new();
468
469 let authenticated = RegisteredValidator {
470 account,
471 stake_table_key,
472 state_ver_key: state_ver_key.clone(),
473 stake,
474 commission,
475 delegators: delegators.clone(),
476 authenticated: true,
477 x25519_key: None,
478 p2p_addr: None,
479 };
480
481 let unauthenticated = RegisteredValidator {
482 account,
483 stake_table_key,
484 state_ver_key,
485 stake,
486 commission,
487 delegators,
488 authenticated: false,
489 x25519_key: None,
490 p2p_addr: None,
491 };
492
493 let auth_commitment = authenticated.commit();
494 let unauth_commitment = unauthenticated.commit();
495 assert_ne!(
496 auth_commitment.as_ref() as &[u8],
497 unauth_commitment.as_ref() as &[u8]
498 );
499 }
500
501 #[test]
502 fn test_registered_validator_serde_roundtrip_with_none_key() {
503 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
504 account: Address::random(),
505 stake_table_key: None,
506 state_ver_key: Some(StateVerKey::default()),
507 stake: U256::from(42u64),
508 commission: 1000,
509 delegators: HashMap::new(),
510 authenticated: false,
511 x25519_key: None,
512 p2p_addr: None,
513 };
514 let json = serde_json::to_string(&v).unwrap();
515 let v2: RegisteredValidator<BLSPubKey> = serde_json::from_str(&json).unwrap();
516 assert_eq!(v, v2);
517 assert!(v2.stake_table_key.is_none());
518 }
519
520 #[test]
521 fn test_authenticated_try_from_rejects_none_key() {
522 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
523 account: Address::random(),
524 stake_table_key: None,
525 state_ver_key: Some(StateVerKey::default()),
526 stake: U256::from(1u64),
527 commission: 0,
528 delegators: HashMap::new(),
529 authenticated: true,
530 x25519_key: None,
531 p2p_addr: None,
532 };
533 assert!(AuthenticatedValidator::try_from(&v).is_err());
534 assert!(AuthenticatedValidator::try_from(v).is_err());
535 }
536
537 #[test]
538 fn test_authenticated_try_from_rejects_none_schnorr_key() {
539 let v: RegisteredValidator<BLSPubKey> = RegisteredValidator {
540 account: Address::random(),
541 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0),
542 state_ver_key: None,
543 stake: U256::from(1u64),
544 commission: 0,
545 delegators: HashMap::new(),
546 authenticated: true,
547 x25519_key: None,
548 p2p_addr: None,
549 };
550 assert!(AuthenticatedValidator::try_from(&v).is_err());
551 assert!(AuthenticatedValidator::try_from(v).is_err());
552 }
553
554 #[test]
555 fn test_commit_none_vs_some_differs() {
556 let account = Address::random();
557 let state_ver_key = Some(StateVerKey::default());
558 let stake = U256::from(7u64);
559
560 let with_key: RegisteredValidator<BLSPubKey> = RegisteredValidator {
561 account,
562 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([2u8; 32], 0).0),
563 state_ver_key: state_ver_key.clone(),
564 stake,
565 commission: 100,
566 delegators: HashMap::new(),
567 authenticated: false,
568 x25519_key: None,
569 p2p_addr: None,
570 };
571 let without_key: RegisteredValidator<BLSPubKey> = RegisteredValidator {
572 account,
573 stake_table_key: None,
574 state_ver_key,
575 stake,
576 commission: 100,
577 delegators: HashMap::new(),
578 authenticated: false,
579 x25519_key: None,
580 p2p_addr: None,
581 };
582 assert_ne!(
583 with_key.commit().as_ref() as &[u8],
584 without_key.commit().as_ref() as &[u8]
585 );
586 }
587
588 #[test]
589 fn test_commit_none_vs_some_schnorr_differs() {
590 let account = Address::random();
591 let stake_table_key = Some(BLSPubKey::generated_from_seed_indexed([3u8; 32], 0).0);
592 let stake = U256::from(11u64);
593
594 let with_schnorr: RegisteredValidator<BLSPubKey> = RegisteredValidator {
595 account,
596 stake_table_key,
597 state_ver_key: Some(StateVerKey::default()),
598 stake,
599 commission: 100,
600 delegators: HashMap::new(),
601 authenticated: false,
602 x25519_key: None,
603 p2p_addr: None,
604 };
605 let without_schnorr: RegisteredValidator<BLSPubKey> = RegisteredValidator {
606 account,
607 stake_table_key,
608 state_ver_key: None,
609 stake,
610 commission: 100,
611 delegators: HashMap::new(),
612 authenticated: false,
613 x25519_key: None,
614 p2p_addr: None,
615 };
616 assert_ne!(
617 with_schnorr.commit().as_ref() as &[u8],
618 without_schnorr.commit().as_ref() as &[u8]
619 );
620 }
621}