espresso_types/v0/v0_3/
stake_table.rs

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/// Stake table holding all staking information (DA and non-DA stakers)
26#[derive(Debug, Clone, Serialize, Deserialize, From)]
27pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
28
29#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
30/// NewType to disambiguate DA Membership
31pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
32
33#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
34/// NewType to disambiguate StakeTable
35pub 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    /// The peer's public key
42    pub stake_table_key: KEY,
43    /// the peer's state public key
44    pub state_ver_key: StateVerKey,
45    /// the peer's stake
46    pub stake: U256,
47    // commission
48    // TODO: MA commission is only valid from 0 to 10_000. Add newtype to enforce this.
49    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
61/// Type for holding result sets matching epochs to stake tables.
62pub type IndexedStake = (
63    EpochNumber,
64    ValidatorMap,
65);
66
67#[derive(Clone, derive_more::derive::Debug)]
68pub struct Fetcher {
69    /// Peers for catching up the stake table
70    #[debug(skip)]
71    pub(crate) peers: Arc<dyn StateCatchup>,
72    /// Methods for stake table persistence.
73    #[debug(skip)]
74    pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
75    /// L1 provider
76    pub(crate) l1_client: L1Client,
77    /// Verifiable `ChainConfig` holding contract address
78    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
93// (log block number, log index)
94pub 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}