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::types::SignatureKey;
11use hotshot_contract_adapter::sol_types::StakeTableV3::{
12 CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, P2pAddrUpdated,
13 Undelegated, UndelegatedV2, ValidatorExit, ValidatorExitV2, ValidatorRegistered,
14 ValidatorRegisteredV2, ValidatorRegisteredV3, X25519KeyUpdated,
15};
16use hotshot_types::{
17 PeerConfig, addr::NetAddr, data::EpochNumber, light_client::StateVerKey,
18 network::PeerConfigKeys, x25519,
19};
20use itertools::Itertools;
21use jf_utils::to_bytes;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24use tokio::task::JoinHandle;
25use vbs::version::Version;
26use versions::NEW_PROTOCOL_VERSION;
27
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)]
57#[serde(bound(deserialize = ""))]
58pub struct RegisteredValidator<KEY: SignatureKey> {
59 pub account: Address,
60 pub stake_table_key: KEY,
62 pub state_ver_key: StateVerKey,
64 pub stake: U256,
66 pub commission: u16,
69 pub delegators: HashMap<Address, U256>,
70 pub authenticated: bool,
73 pub x25519_key: Option<x25519::PublicKey>,
75 pub p2p_addr: Option<NetAddr>,
77}
78
79#[derive(serde::Serialize, Clone, Debug, PartialEq, Eq)]
83pub struct AuthenticatedValidator<KEY: SignatureKey>(RegisteredValidator<KEY>);
84
85impl<'de, KEY: SignatureKey> Deserialize<'de> for AuthenticatedValidator<KEY> {
86 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87 where
88 D: serde::Deserializer<'de>,
89 {
90 let inner = RegisteredValidator::deserialize(deserializer)?;
91 if !inner.authenticated {
92 return Err(serde::de::Error::custom(
93 "cannot deserialize unauthenticated validator as AuthenticatedValidator",
94 ));
95 }
96 Ok(AuthenticatedValidator(inner))
97 }
98}
99
100impl<KEY: SignatureKey> AuthenticatedValidator<KEY> {
101 pub fn into_inner(self) -> RegisteredValidator<KEY> {
102 self.0
103 }
104
105 pub fn is_eligible(&self, protocol_version: Version) -> bool {
127 if protocol_version >= NEW_PROTOCOL_VERSION
128 && (self.x25519_key.is_none() || self.p2p_addr.is_none())
129 {
130 return false;
131 }
132 true
133 }
134}
135
136impl<KEY: SignatureKey> std::ops::Deref for AuthenticatedValidator<KEY> {
137 type Target = RegisteredValidator<KEY>;
138
139 fn deref(&self) -> &Self::Target {
140 &self.0
141 }
142}
143
144#[derive(Debug, Error)]
145#[error("Validator {0:#x} not authenticated (invalid registration signature)")]
146pub struct UnauthenticatedValidatorError(pub Address);
147
148impl<KEY: SignatureKey + Clone> TryFrom<&RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
149 type Error = UnauthenticatedValidatorError;
150
151 fn try_from(v: &RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
152 if !v.authenticated {
153 return Err(UnauthenticatedValidatorError(v.account));
154 }
155 Ok(AuthenticatedValidator(v.clone()))
156 }
157}
158
159impl<KEY: SignatureKey> TryFrom<RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
160 type Error = UnauthenticatedValidatorError;
161
162 fn try_from(v: RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
163 if !v.authenticated {
164 return Err(UnauthenticatedValidatorError(v.account));
165 }
166 Ok(AuthenticatedValidator(v))
167 }
168}
169
170impl<KEY: SignatureKey> From<AuthenticatedValidator<KEY>> for RegisteredValidator<KEY> {
171 fn from(v: AuthenticatedValidator<KEY>) -> Self {
172 v.into_inner()
173 }
174}
175
176impl<KEY: SignatureKey> Committable for RegisteredValidator<KEY> {
177 fn commit(&self) -> Commitment<Self> {
178 let mut builder = RawCommitmentBuilder::new(&Self::tag())
179 .fixed_size_field("account", &self.account)
180 .var_size_field(
181 "stake_table_key",
182 self.stake_table_key.to_bytes().as_slice(),
183 )
184 .var_size_field("state_ver_key", &to_bytes!(&self.state_ver_key).unwrap())
185 .fixed_size_field("stake", &to_fixed_bytes(self.stake))
186 .constant_str("commission")
187 .u16(self.commission);
188
189 if let Some(key) = &self.x25519_key {
193 builder = builder.var_size_field("x25519_key", key.as_slice());
194 }
195 if let Some(addr) = &self.p2p_addr {
196 builder = builder.var_size_field("p2p_addr", addr.to_string().as_bytes());
197 }
198
199 builder = builder.constant_str("delegators");
200 for (address, stake) in self.delegators.iter().sorted() {
201 builder = builder
202 .fixed_size_bytes(address)
203 .fixed_size_bytes(&to_fixed_bytes(*stake));
204 }
205
206 if !self.authenticated {
208 builder = builder.constant_str("unauthenticated");
209 }
210
211 builder.finalize()
212 }
213
214 fn tag() -> String {
215 "VALIDATOR".to_string()
216 }
217}
218
219#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
220#[serde(bound(deserialize = ""))]
221pub struct Delegator {
222 pub address: Address,
223 pub validator: Address,
224 pub stake: U256,
225}
226
227pub type IndexedStake = (
229 EpochNumber,
230 (AuthenticatedValidatorMap, Option<RewardAmount>),
231 Option<StakeTableHash>,
232);
233
234#[derive(Clone, derive_more::derive::Debug)]
235pub struct Fetcher {
236 #[debug(skip)]
238 pub(crate) peers: Arc<dyn StateCatchup>,
239 #[debug(skip)]
241 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
242 pub(crate) l1_client: L1Client,
244 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
246 pub(crate) update_task: Arc<StakeTableUpdateTask>,
247 pub initial_supply: Arc<RwLock<Option<U256>>>,
248}
249
250#[derive(Debug, Default)]
251pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
252
253impl Drop for StakeTableUpdateTask {
254 fn drop(&mut self) {
255 if let Some(task) = self.0.get_mut().take() {
256 task.abort();
257 }
258 }
259}
260
261pub type EventKey = (u64, u64);
263
264#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
265pub enum StakeTableEvent {
266 Register(ValidatorRegistered),
267 RegisterV2(ValidatorRegisteredV2),
268 Deregister(ValidatorExit),
269 DeregisterV2(ValidatorExitV2),
270 Delegate(Delegated),
271 Undelegate(Undelegated),
272 UndelegateV2(UndelegatedV2),
273 KeyUpdate(ConsensusKeysUpdated),
274 KeyUpdateV2(ConsensusKeysUpdatedV2),
275 CommissionUpdate(CommissionUpdated),
276 RegisterV3(ValidatorRegisteredV3),
277 X25519KeyUpdate(X25519KeyUpdated),
278 P2pAddrUpdate(P2pAddrUpdated),
279}
280
281#[derive(Debug, Error)]
282pub enum StakeTableError {
283 #[error("Validator {0:#x} already registered")]
284 AlreadyRegistered(Address),
285 #[error("Validator {0:#x} not found")]
286 ValidatorNotFound(Address),
287 #[error("Delegator {0:#x} not found")]
288 DelegatorNotFound(Address),
289 #[error("BLS key already used: {0}")]
290 BlsKeyAlreadyUsed(String),
291 #[error("Insufficient stake to undelegate")]
292 InsufficientStake,
293 #[error("Event authentication failed: {0}")]
294 AuthenticationFailed(String),
295 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
296 NoValidValidators,
297 #[error("Could not compute maximum stake from filtered validators")]
298 MissingMaximumStake,
299 #[error("Overflow when calculating minimum stake threshold")]
300 MinimumStakeOverflow,
301 #[error("Delegator {0:#x} has 0 stake")]
302 ZeroDelegatorStake(Address),
303 #[error("Failed to hash stake table: {0}")]
304 HashError(#[from] bincode::Error),
305 #[error("Validator {0:#x} already exited and cannot be re-registered")]
306 ValidatorAlreadyExited(Address),
307 #[error("Validator {0:#x} has invalid commission {1}")]
308 InvalidCommission(Address, u16),
309 #[error("Schnorr key already used: {0}")]
310 SchnorrKeyAlreadyUsed(String),
311 #[error("x25519 key already used: {0}")]
312 X25519KeyAlreadyUsed(String),
313 #[error("Invalid x25519 key: {0}")]
314 InvalidX25519Key(String),
315 #[error("Stake table event decode error {0}")]
316 StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
317 #[error("Stake table events sorting error: {0}")]
318 EventSortingError(#[from] EventSortingError),
319}
320
321#[derive(Debug, Error)]
322pub enum ExpectedStakeTableError {
323 #[error("Schnorr key already used: {0}")]
324 SchnorrKeyAlreadyUsed(String),
325}
326
327#[derive(Debug, Error)]
328pub enum FetchRewardError {
329 #[error("No stake table contract address found in chain config")]
330 MissingStakeTableContract,
331
332 #[error("Token address fetch failed: {0}")]
333 TokenAddressFetch(#[source] alloy::contract::Error),
334
335 #[error("Token Initialized event logs are empty")]
336 MissingInitializedEvent,
337
338 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
339 MissingTransactionHash { init_log: Log },
340
341 #[error("Block number not found in Initialized event log")]
342 MissingBlockNumber,
343
344 #[error("Transfer event query failed: {0}")]
345 TransferEventQuery(#[source] alloy::contract::Error),
346
347 #[error("No Transfer event found in the Initialized event block")]
348 MissingTransferEvent,
349
350 #[error("Division by zero {0}")]
351 DivisionByZero(&'static str),
352
353 #[error("Overflow {0}")]
354 Overflow(&'static str),
355
356 #[error("Contract call failed: {0}")]
357 ContractCall(#[source] alloy::contract::Error),
358
359 #[error("Rpc call failed: {0}")]
360 Rpc(#[source] RpcError<TransportErrorKind>),
361
362 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
363 ExceededMaxScanRange(u64),
364
365 #[error("Scanning for Initialized event failed: {0}")]
366 ScanQueryFailed(#[source] alloy::contract::Error),
367}
368
369#[derive(Debug, thiserror::Error)]
370pub enum EventSortingError {
371 #[error("Missing block number in log")]
372 MissingBlockNumber,
373
374 #[error("Missing log index in log")]
375 MissingLogIndex,
376
377 #[error("Invalid stake table event")]
378 InvalidStakeTableEvent,
379}
380
381#[cfg(test)]
382mod tests {
383 use std::collections::HashMap;
384
385 use alloy::primitives::{Address, U256};
386 use committable::Committable;
387 use hotshot::types::{BLSPubKey, SignatureKey};
388 use hotshot_types::{addr::NetAddr, light_client::StateVerKey, x25519};
389
390 use super::RegisteredValidator;
391
392 #[test]
394 fn test_commitment_changes_with_x25519_and_p2p_fields() {
395 let base = RegisteredValidator::<BLSPubKey>::mock();
396 assert!(base.x25519_key.is_none());
397 assert!(base.p2p_addr.is_none());
398 let commit_base = base.commit();
399
400 let mut with_x25519 = base.clone();
401 with_x25519.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
402 let commit_x25519 = with_x25519.commit();
403
404 let mut with_p2p = base.clone();
405 with_p2p.p2p_addr = Some("127.0.0.1:8080".parse::<NetAddr>().unwrap());
406 let commit_p2p = with_p2p.commit();
407
408 assert_ne!(commit_base, commit_x25519);
409 assert_ne!(commit_base, commit_p2p);
410 assert_ne!(commit_x25519, commit_p2p);
411 }
412
413 #[test]
416 fn test_unauthenticated_validator_commitment_differs() {
417 let account = Address::random();
418 let stake_table_key = BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0;
419 let state_ver_key = StateVerKey::default();
420 let stake = U256::from(1000);
421 let commission = 500u16;
422 let delegators = HashMap::new();
423
424 let authenticated = RegisteredValidator {
425 account,
426 stake_table_key,
427 state_ver_key: state_ver_key.clone(),
428 stake,
429 commission,
430 delegators: delegators.clone(),
431 authenticated: true,
432 x25519_key: None,
433 p2p_addr: None,
434 };
435
436 let unauthenticated = RegisteredValidator {
437 account,
438 stake_table_key,
439 state_ver_key,
440 stake,
441 commission,
442 delegators,
443 authenticated: false,
444 x25519_key: None,
445 p2p_addr: None,
446 };
447
448 let auth_commitment = authenticated.commit();
449 let unauth_commitment = unauthenticated.commit();
450 assert_ne!(
451 auth_commitment.as_ref() as &[u8],
452 unauth_commitment.as_ref() as &[u8]
453 );
454 }
455}