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