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::StakeTableV2::{
12 CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, Undelegated,
13 UndelegatedV2, ValidatorExit, ValidatorExitV2, ValidatorRegistered, ValidatorRegisteredV2,
14};
15use hotshot_types::{
16 PeerConfig, data::EpochNumber, light_client::StateVerKey, network::PeerConfigKeys, x25519,
17 addr::NetAddr
18};
19use itertools::Itertools;
20use jf_utils::to_bytes;
21use serde::{Deserialize, Serialize};
22use thiserror::Error;
23use tokio::task::JoinHandle;
24
25use super::L1Client;
26use crate::{
27 traits::{MembershipPersistence, StateCatchup},
28 v0::{impls::StakeTableHash, ChainConfig},
29 v0_3::RewardAmount,
30 AuthenticatedValidatorMap, SeqTypes,
31};
32#[derive(Debug, Clone, Serialize, Deserialize, From)]
34pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
35
36#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
37pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
39
40#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
41pub struct StakeTable(pub Vec<PeerConfig<SeqTypes>>);
43
44pub(crate) fn to_fixed_bytes(value: U256) -> [u8; std::mem::size_of::<U256>()] {
45 let bytes: [u8; std::mem::size_of::<U256>()] = value.to_le_bytes();
46 bytes
47}
48
49#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
54#[serde(bound(deserialize = ""))]
55pub struct RegisteredValidator<KEY: SignatureKey> {
56 pub account: Address,
57 pub stake_table_key: KEY,
59 pub state_ver_key: StateVerKey,
61 pub stake: U256,
63 pub commission: u16,
66 pub delegators: HashMap<Address, U256>,
67 pub authenticated: bool,
70 pub x25519_key: Option<x25519::PublicKey>,
72 pub p2p_addr: Option<NetAddr>
74}
75
76#[derive(serde::Serialize, Clone, Debug, PartialEq, Eq)]
80pub struct AuthenticatedValidator<KEY: SignatureKey>(RegisteredValidator<KEY>);
81
82impl<'de, KEY: SignatureKey> Deserialize<'de> for AuthenticatedValidator<KEY> {
83 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84 where
85 D: serde::Deserializer<'de>,
86 {
87 let inner = RegisteredValidator::deserialize(deserializer)?;
88 if !inner.authenticated {
89 return Err(serde::de::Error::custom(
90 "cannot deserialize unauthenticated validator as AuthenticatedValidator",
91 ));
92 }
93 Ok(AuthenticatedValidator(inner))
94 }
95}
96
97impl<KEY: SignatureKey> AuthenticatedValidator<KEY> {
98 pub fn into_inner(self) -> RegisteredValidator<KEY> {
99 self.0
100 }
101}
102
103impl<KEY: SignatureKey> std::ops::Deref for AuthenticatedValidator<KEY> {
104 type Target = RegisteredValidator<KEY>;
105
106 fn deref(&self) -> &Self::Target {
107 &self.0
108 }
109}
110
111#[derive(Debug, Error)]
112#[error("Validator {0:#x} not authenticated (invalid registration signature)")]
113pub struct UnauthenticatedValidatorError(pub Address);
114
115impl<KEY: SignatureKey + Clone> TryFrom<&RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
116 type Error = UnauthenticatedValidatorError;
117
118 fn try_from(v: &RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
119 if !v.authenticated {
120 return Err(UnauthenticatedValidatorError(v.account));
121 }
122 Ok(AuthenticatedValidator(v.clone()))
123 }
124}
125
126impl<KEY: SignatureKey> TryFrom<RegisteredValidator<KEY>> for AuthenticatedValidator<KEY> {
127 type Error = UnauthenticatedValidatorError;
128
129 fn try_from(v: RegisteredValidator<KEY>) -> Result<Self, Self::Error> {
130 if !v.authenticated {
131 return Err(UnauthenticatedValidatorError(v.account));
132 }
133 Ok(AuthenticatedValidator(v))
134 }
135}
136
137impl<KEY: SignatureKey> From<AuthenticatedValidator<KEY>> for RegisteredValidator<KEY> {
138 fn from(v: AuthenticatedValidator<KEY>) -> Self {
139 v.into_inner()
140 }
141}
142
143impl<KEY: SignatureKey> Committable for RegisteredValidator<KEY> {
144 fn commit(&self) -> Commitment<Self> {
145 let mut builder = RawCommitmentBuilder::new(&Self::tag())
146 .fixed_size_field("account", &self.account)
147 .var_size_field(
148 "stake_table_key",
149 self.stake_table_key.to_bytes().as_slice(),
150 )
151 .var_size_field("state_ver_key", &to_bytes!(&self.state_ver_key).unwrap())
152 .fixed_size_field("stake", &to_fixed_bytes(self.stake))
153 .constant_str("commission")
154 .u16(self.commission);
155 builder = builder.constant_str("delegators");
159 for (address, stake) in self.delegators.iter().sorted() {
160 builder = builder
161 .fixed_size_bytes(address)
162 .fixed_size_bytes(&to_fixed_bytes(*stake));
163 }
164
165 if !self.authenticated {
167 builder = builder.constant_str("unauthenticated");
168 }
169
170 builder.finalize()
171 }
172
173 fn tag() -> String {
174 "VALIDATOR".to_string()
175 }
176}
177
178#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
179#[serde(bound(deserialize = ""))]
180pub struct Delegator {
181 pub address: Address,
182 pub validator: Address,
183 pub stake: U256,
184}
185
186pub type IndexedStake = (
188 EpochNumber,
189 (AuthenticatedValidatorMap, Option<RewardAmount>),
190 Option<StakeTableHash>,
191);
192
193#[derive(Clone, derive_more::derive::Debug)]
194pub struct Fetcher {
195 #[debug(skip)]
197 pub(crate) peers: Arc<dyn StateCatchup>,
198 #[debug(skip)]
200 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
201 pub(crate) l1_client: L1Client,
203 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
205 pub(crate) update_task: Arc<StakeTableUpdateTask>,
206 pub initial_supply: Arc<RwLock<Option<U256>>>,
207}
208
209#[derive(Debug, Default)]
210pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
211
212impl Drop for StakeTableUpdateTask {
213 fn drop(&mut self) {
214 if let Some(task) = self.0.get_mut().take() {
215 task.abort();
216 }
217 }
218}
219
220pub type EventKey = (u64, u64);
222
223#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
224pub enum StakeTableEvent {
225 Register(ValidatorRegistered),
226 RegisterV2(ValidatorRegisteredV2),
227 Deregister(ValidatorExit),
228 DeregisterV2(ValidatorExitV2),
229 Delegate(Delegated),
230 Undelegate(Undelegated),
231 UndelegateV2(UndelegatedV2),
232 KeyUpdate(ConsensusKeysUpdated),
233 KeyUpdateV2(ConsensusKeysUpdatedV2),
234 CommissionUpdate(CommissionUpdated),
235}
236
237#[derive(Debug, Error)]
238pub enum StakeTableError {
239 #[error("Validator {0:#x} already registered")]
240 AlreadyRegistered(Address),
241 #[error("Validator {0:#x} not found")]
242 ValidatorNotFound(Address),
243 #[error("Delegator {0:#x} not found")]
244 DelegatorNotFound(Address),
245 #[error("BLS key already used: {0}")]
246 BlsKeyAlreadyUsed(String),
247 #[error("Insufficient stake to undelegate")]
248 InsufficientStake,
249 #[error("Event authentication failed: {0}")]
250 AuthenticationFailed(String),
251 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
252 NoValidValidators,
253 #[error("Could not compute maximum stake from filtered validators")]
254 MissingMaximumStake,
255 #[error("Overflow when calculating minimum stake threshold")]
256 MinimumStakeOverflow,
257 #[error("Delegator {0:#x} has 0 stake")]
258 ZeroDelegatorStake(Address),
259 #[error("Failed to hash stake table: {0}")]
260 HashError(#[from] bincode::Error),
261 #[error("Validator {0:#x} already exited and cannot be re-registered")]
262 ValidatorAlreadyExited(Address),
263 #[error("Validator {0:#x} has invalid commission {1}")]
264 InvalidCommission(Address, u16),
265 #[error("Schnorr key already used: {0}")]
266 SchnorrKeyAlreadyUsed(String),
267 #[error("Stake table event decode error {0}")]
268 StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
269 #[error("Stake table events sorting error: {0}")]
270 EventSortingError(#[from] EventSortingError),
271}
272
273#[derive(Debug, Error)]
274pub enum ExpectedStakeTableError {
275 #[error("Schnorr key already used: {0}")]
276 SchnorrKeyAlreadyUsed(String),
277}
278
279#[derive(Debug, Error)]
280pub enum FetchRewardError {
281 #[error("No stake table contract address found in chain config")]
282 MissingStakeTableContract,
283
284 #[error("Token address fetch failed: {0}")]
285 TokenAddressFetch(#[source] alloy::contract::Error),
286
287 #[error("Token Initialized event logs are empty")]
288 MissingInitializedEvent,
289
290 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
291 MissingTransactionHash { init_log: Log },
292
293 #[error("Block number not found in Initialized event log")]
294 MissingBlockNumber,
295
296 #[error("Transfer event query failed: {0}")]
297 TransferEventQuery(#[source] alloy::contract::Error),
298
299 #[error("No Transfer event found in the Initialized event block")]
300 MissingTransferEvent,
301
302 #[error("Division by zero {0}")]
303 DivisionByZero(&'static str),
304
305 #[error("Overflow {0}")]
306 Overflow(&'static str),
307
308 #[error("Contract call failed: {0}")]
309 ContractCall(#[source] alloy::contract::Error),
310
311 #[error("Rpc call failed: {0}")]
312 Rpc(#[source] RpcError<TransportErrorKind>),
313
314 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
315 ExceededMaxScanRange(u64),
316
317 #[error("Scanning for Initialized event failed: {0}")]
318 ScanQueryFailed(#[source] alloy::contract::Error),
319}
320
321#[derive(Debug, thiserror::Error)]
322pub enum EventSortingError {
323 #[error("Missing block number in log")]
324 MissingBlockNumber,
325
326 #[error("Missing log index in log")]
327 MissingLogIndex,
328
329 #[error("Invalid stake table V2 event")]
330 InvalidStakeTableV2Event,
331}
332
333#[cfg(test)]
334mod tests {
335 use std::collections::HashMap;
336
337 use alloy::primitives::{Address, U256};
338 use committable::Committable;
339 use hotshot::types::{BLSPubKey, SignatureKey};
340 use hotshot_types::light_client::StateVerKey;
341
342 use super::RegisteredValidator;
343
344 #[test]
347 fn test_unauthenticated_validator_commitment_differs() {
348 let account = Address::random();
349 let stake_table_key = BLSPubKey::generated_from_seed_indexed([1u8; 32], 0).0;
350 let state_ver_key = StateVerKey::default();
351 let stake = U256::from(1000);
352 let commission = 500u16;
353 let delegators = HashMap::new();
354
355 let authenticated = RegisteredValidator {
356 account,
357 stake_table_key,
358 state_ver_key: state_ver_key.clone(),
359 stake,
360 commission,
361 delegators: delegators.clone(),
362 authenticated: true,
363 x25519_key: None,
364 p2p_addr: None,
365 };
366
367 let unauthenticated = RegisteredValidator {
368 account,
369 stake_table_key,
370 state_ver_key,
371 stake,
372 commission,
373 delegators,
374 authenticated: false,
375 x25519_key: None,
376 p2p_addr: None,
377 };
378
379 let auth_commitment = authenticated.commit();
380 let unauth_commitment = unauthenticated.commit();
381 assert_ne!(
382 auth_commitment.as_ref() as &[u8],
383 unauth_commitment.as_ref() as &[u8]
384 );
385 }
386}