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