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 ValidatorExit, ValidatorRegistered, ValidatorRegisteredV2,
14};
15use hotshot_types::{
16 data::EpochNumber, light_client::StateVerKey, network::PeerConfigKeys, PeerConfig,
17};
18use itertools::Itertools;
19use jf_utils::to_bytes;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22use tokio::task::JoinHandle;
23
24use super::L1Client;
25use crate::{
26 traits::{MembershipPersistence, StateCatchup},
27 v0::{impls::StakeTableHash, ChainConfig},
28 v0_3::RewardAmount,
29 SeqTypes, ValidatorMap,
30};
31#[derive(Debug, Clone, Serialize, Deserialize, From)]
33pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
34
35#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
36pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
38
39#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
40pub struct StakeTable(pub Vec<PeerConfig<SeqTypes>>);
42
43#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
44#[serde(bound(deserialize = ""))]
45pub struct Validator<KEY: SignatureKey> {
46 pub account: Address,
47 pub stake_table_key: KEY,
49 pub state_ver_key: StateVerKey,
51 pub stake: U256,
53 pub commission: u16,
56 pub delegators: HashMap<Address, U256>,
57}
58
59pub(crate) fn to_fixed_bytes(value: U256) -> [u8; std::mem::size_of::<U256>()] {
60 let bytes: [u8; std::mem::size_of::<U256>()] = value.to_le_bytes();
61 bytes
62}
63
64impl<KEY: SignatureKey> Committable for Validator<KEY> {
65 fn commit(&self) -> Commitment<Self> {
66 let mut builder = RawCommitmentBuilder::new(&Self::tag())
67 .fixed_size_field("account", &self.account)
68 .var_size_field(
69 "stake_table_key",
70 self.stake_table_key.to_bytes().as_slice(),
71 )
72 .var_size_field("state_ver_key", &to_bytes!(&self.state_ver_key).unwrap())
73 .fixed_size_field("stake", &to_fixed_bytes(self.stake))
74 .constant_str("commission")
75 .u16(self.commission);
76
77 builder = builder.constant_str("delegators");
78 for (address, stake) in self.delegators.iter().sorted() {
79 builder = builder
80 .fixed_size_bytes(address)
81 .fixed_size_bytes(&to_fixed_bytes(*stake));
82 }
83
84 builder.finalize()
85 }
86
87 fn tag() -> String {
88 "VALIDATOR".to_string()
89 }
90}
91
92#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
93#[serde(bound(deserialize = ""))]
94pub struct Delegator {
95 pub address: Address,
96 pub validator: Address,
97 pub stake: U256,
98}
99
100pub type IndexedStake = (
102 EpochNumber,
103 (ValidatorMap, Option<RewardAmount>),
104 Option<StakeTableHash>,
105);
106
107#[derive(Clone, derive_more::derive::Debug)]
108pub struct Fetcher {
109 #[debug(skip)]
111 pub(crate) peers: Arc<dyn StateCatchup>,
112 #[debug(skip)]
114 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
115 pub(crate) l1_client: L1Client,
117 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
119 pub(crate) update_task: Arc<StakeTableUpdateTask>,
120 pub initial_supply: Arc<RwLock<Option<U256>>>,
121}
122
123#[derive(Debug, Default)]
124pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
125
126impl Drop for StakeTableUpdateTask {
127 fn drop(&mut self) {
128 if let Some(task) = self.0.get_mut().take() {
129 task.abort();
130 }
131 }
132}
133
134pub type EventKey = (u64, u64);
136
137#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
138pub enum StakeTableEvent {
139 Register(ValidatorRegistered),
140 RegisterV2(ValidatorRegisteredV2),
141 Deregister(ValidatorExit),
142 Delegate(Delegated),
143 Undelegate(Undelegated),
144 KeyUpdate(ConsensusKeysUpdated),
145 KeyUpdateV2(ConsensusKeysUpdatedV2),
146 CommissionUpdate(CommissionUpdated),
147}
148
149#[derive(Debug, Error)]
150pub enum StakeTableError {
151 #[error("Validator {0:#x} already registered")]
152 AlreadyRegistered(Address),
153 #[error("Validator {0:#x} not found")]
154 ValidatorNotFound(Address),
155 #[error("Delegator {0:#x} not found")]
156 DelegatorNotFound(Address),
157 #[error("BLS key already used: {0}")]
158 BlsKeyAlreadyUsed(String),
159 #[error("Insufficient stake to undelegate")]
160 InsufficientStake,
161 #[error("Event authentication failed: {0}")]
162 AuthenticationFailed(String),
163 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
164 NoValidValidators,
165 #[error("Could not compute maximum stake from filtered validators")]
166 MissingMaximumStake,
167 #[error("Overflow when calculating minimum stake threshold")]
168 MinimumStakeOverflow,
169 #[error("Delegator {0:#x} has 0 stake")]
170 ZeroDelegatorStake(Address),
171 #[error("Failed to hash stake table: {0}")]
172 HashError(#[from] bincode::Error),
173 #[error("Validator {0:#x} already exited and cannot be re-registered")]
174 ValidatorAlreadyExited(Address),
175 #[error("Validator {0:#x} has invalid commission {1}")]
176 InvalidCommission(Address, u16),
177 #[error("Schnorr key already used: {0}")]
178 SchnorrKeyAlreadyUsed(String),
179 #[error("Stake table event decode error {0}")]
180 StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
181 #[error("Stake table events sorting error: {0}")]
182 EventSortingError(#[from] EventSortingError)
183}
184
185#[derive(Debug, Error)]
186pub enum ExpectedStakeTableError {
187 #[error("Schnorr key already used: {0}")]
188 SchnorrKeyAlreadyUsed(String),
189}
190
191#[derive(Debug, Error)]
192pub enum FetchRewardError {
193 #[error("No stake table contract address found in chain config")]
194 MissingStakeTableContract,
195
196 #[error("Token address fetch failed: {0}")]
197 TokenAddressFetch(#[source] alloy::contract::Error),
198
199 #[error("Token Initialized event logs are empty")]
200 MissingInitializedEvent,
201
202 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
203 MissingTransactionHash { init_log: Log },
204
205 #[error("Missing transaction receipt for Initialized event. tx_hash={tx_hash}")]
206 MissingTransactionReceipt { tx_hash: String },
207
208 #[error("Failed to get transaction for Initialized event: {0}")]
209 MissingTransaction(#[source] alloy::contract::Error),
210
211 #[error("Failed to decode Transfer log. tx_hash={tx_hash}")]
212 DecodeTransferLog { tx_hash: String },
213
214 #[error("First transfer should be a mint from the zero address")]
215 InvalidMintFromAddress,
216
217 #[error("Division by zero {0}")]
218 DivisionByZero(&'static str),
219
220 #[error("Overflow {0}")]
221 Overflow(&'static str),
222
223 #[error("Contract call failed: {0}")]
224 ContractCall(#[source] alloy::contract::Error),
225
226 #[error("Rpc call failed: {0}")]
227 Rpc(#[source] RpcError<TransportErrorKind>),
228
229 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
230 ExceededMaxScanRange(u64),
231
232 #[error("Scanning for Initialized event failed: {0}")]
233 ScanQueryFailed(#[source] alloy::contract::Error),
234}
235
236#[derive(Debug, thiserror::Error)]
237pub enum EventSortingError {
238 #[error("Missing block number in log")]
239 MissingBlockNumber,
240
241 #[error("Missing log index in log")]
242 MissingLogIndex,
243
244 #[error("Invalid stake table V2 event")]
245 InvalidStakeTableV2Event
246}