espresso_types/v0/v0_3/
stake_table.rs

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/// Stake table holding all staking information (DA and non-DA stakers)
32#[derive(Debug, Clone, Serialize, Deserialize, From)]
33pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
34
35#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
36/// NewType to disambiguate DA Membership
37pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
38
39#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
40/// NewType to disambiguate StakeTable
41pub 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    /// The peer's public key
48    pub stake_table_key: KEY,
49    /// the peer's state public key
50    pub state_ver_key: StateVerKey,
51    /// the peer's stake
52    pub stake: U256,
53    // commission
54    // TODO: MA commission is only valid from 0 to 10_000. Add newtype to enforce this.
55    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
100/// Type for holding result sets matching epochs to stake tables.
101pub type IndexedStake = (
102    EpochNumber,
103    (ValidatorMap, Option<RewardAmount>),
104    Option<StakeTableHash>,
105);
106
107#[derive(Clone, derive_more::derive::Debug)]
108pub struct Fetcher {
109    /// Peers for catching up the stake table
110    #[debug(skip)]
111    pub(crate) peers: Arc<dyn StateCatchup>,
112    /// Methods for stake table persistence.
113    #[debug(skip)]
114    pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
115    /// L1 provider
116    pub(crate) l1_client: L1Client,
117    /// Verifiable `ChainConfig` holding contract address
118    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
134// (log block number, log index)
135pub 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}