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, 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/// 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
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
90/// Type for holding result sets matching epochs to stake tables.
91pub type IndexedStake = (
92    EpochNumber,
93    (ValidatorMap, Option<RewardAmount>),
94    Option<StakeTableHash>,
95);
96
97#[derive(Clone, derive_more::derive::Debug)]
98pub struct Fetcher {
99    /// Peers for catching up the stake table
100    #[debug(skip)]
101    pub(crate) peers: Arc<dyn StateCatchup>,
102    /// Methods for stake table persistence.
103    #[debug(skip)]
104    pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
105    /// L1 provider
106    pub(crate) l1_client: L1Client,
107    /// Verifiable `ChainConfig` holding contract address
108    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
126// (log block number, log index)
127pub 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}