1use std::{collections::HashMap, sync::Arc};
2
3use alloy::{primitives::{Address, Log, U256}, transports::{RpcError, TransportErrorKind}};
4use async_lock::Mutex;
5use derive_more::derive::{From, Into};
6use hotshot::types::{SignatureKey};
7use hotshot_contract_adapter::sol_types::StakeTableV2::{
8 ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, Undelegated, ValidatorExit,
9 ValidatorRegistered, ValidatorRegisteredV2,
10};
11use hotshot_types::{
12 data::EpochNumber, light_client::StateVerKey, network::PeerConfigKeys, PeerConfig,
13};
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16use tokio::task::JoinHandle;
17
18use super::L1Client;
19use crate::{
20 traits::{MembershipPersistence, StateCatchup},
21 v0::ChainConfig,
22 SeqTypes, ValidatorMap,
23};
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
53#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
54#[serde(bound(deserialize = ""))]
55pub struct Delegator {
56 pub address: Address,
57 pub validator: Address,
58 pub stake: U256,
59}
60
61pub type IndexedStake = (
63 EpochNumber,
64 ValidatorMap,
65);
66
67#[derive(Clone, derive_more::derive::Debug)]
68pub struct Fetcher {
69 #[debug(skip)]
71 pub(crate) peers: Arc<dyn StateCatchup>,
72 #[debug(skip)]
74 pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
75 pub(crate) l1_client: L1Client,
77 pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
79 pub(crate) update_task: Arc<StakeTableUpdateTask>,
80}
81
82#[derive(Debug, Default)]
83pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
84
85impl Drop for StakeTableUpdateTask {
86 fn drop(&mut self) {
87 if let Some(task) = self.0.get_mut().take() {
88 task.abort();
89 }
90 }
91}
92
93pub type EventKey = (u64, u64);
95
96#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
97pub enum StakeTableEvent {
98 Register(ValidatorRegistered),
99 RegisterV2(ValidatorRegisteredV2),
100 Deregister(ValidatorExit),
101 Delegate(Delegated),
102 Undelegate(Undelegated),
103 KeyUpdate(ConsensusKeysUpdated),
104 KeyUpdateV2(ConsensusKeysUpdatedV2),
105}
106
107
108#[derive(Debug, Error)]
109pub enum StakeTableError {
110 #[error("Validator {0:#x} already registered")]
111 AlreadyRegistered(Address),
112 #[error("Validator {0:#x} not found")]
113 ValidatorNotFound(Address),
114 #[error("Delegator {0:#x} not found")]
115 DelegatorNotFound(Address),
116 #[error("BLS key already used: {0}")]
117 BlsKeyAlreadyUsed(String),
118 #[error("Insufficient stake to undelegate")]
119 InsufficientStake,
120 #[error("Event authentication failed: {0}")]
121 AuthenticationFailed(String),
122 #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
123 NoValidValidators,
124 #[error("Could not compute maximum stake from filtered validators")]
125 MissingMaximumStake,
126 #[error("Overflow when calculating minimum stake threshold")]
127 MinimumStakeOverflow,
128 #[error("Delegator {0:#x} has 0 stake")]
129 ZeroDelegatorStake(Address),
130}
131
132#[derive(Debug, Error)]
133pub enum ExpectedStakeTableError {
134 #[error("Schnorr key already used: {0}")]
135 SchnorrKeyAlreadyUsed(String),
136}
137
138#[derive(Debug, Error)]
139pub enum FetchRewardError {
140 #[error("No stake table contract address found in chain config")]
141 MissingStakeTableContract,
142
143 #[error("Token address fetch failed: {0}")]
144 TokenAddressFetch(#[source] alloy::contract::Error),
145
146 #[error("Token Initialized event logs are empty")]
147 MissingInitializedEvent,
148
149 #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
150 MissingTransactionHash { init_log: Log },
151
152 #[error("Missing transaction receipt for Initialized event. tx_hash={tx_hash}")]
153 MissingTransactionReceipt { tx_hash: String },
154
155 #[error("Failed to get transaction for Initialized event: {0}")]
156 MissingTransaction(#[source] alloy::contract::Error),
157
158 #[error("Failed to decode Transfer log. tx_hash={tx_hash}")]
159 DecodeTransferLog { tx_hash: String},
160
161 #[error("First transfer should be a mint from the zero address")]
162 InvalidMintFromAddress,
163
164 #[error("Division by zero in commission basis points")]
165 DivisionByZero,
166
167 #[error("Contract call failed: {0}")]
168 ContractCall(#[source] alloy::contract::Error),
169
170 #[error("Rpc call failed: {0}")]
171 Rpc(#[source] RpcError<TransportErrorKind>),
172
173 #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
174 ExceededMaxScanRange(u64),
175
176 #[error("Scanning for Initialized event failed: {0}")]
177 ScanQueryFailed(#[source] alloy::contract::Error),
178}
179
180#[derive(Debug, thiserror::Error)]
181pub enum EventSortingError {
182 #[error("Missing block number in log")]
183 MissingBlockNumber,
184
185 #[error("Missing log index in log")]
186 MissingLogIndex,
187}