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::{ConsensusKeysUpdatedLegacy, ConsensusKeysUpdatedV2Legacy, DelegatedLegacy, StakeTableV2::{
12    CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, Undelegated, ValidatorExit, ValidatorRegistered, ValidatorRegisteredV2
13}, UndelegatedLegacy, ValidatorExitLegacy, ValidatorRegisteredLegacy, ValidatorRegisteredV2Legacy};
14use hotshot_types::{
15    data::EpochNumber, light_client::StateVerKey, network::PeerConfigKeys, PeerConfig,
16};
17use itertools::Itertools;
18use jf_utils::to_bytes;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21use tokio::task::JoinHandle;
22
23use super::L1Client;
24use crate::{
25    traits::{MembershipPersistence, StateCatchup},
26    v0::{impls::StakeTableHash, ChainConfig},
27    v0_3::RewardAmount,
28    SeqTypes, ValidatorMap,
29};
30/// Stake table holding all staking information (DA and non-DA stakers)
31#[derive(Debug, Clone, Serialize, Deserialize, From)]
32pub struct CombinedStakeTable(Vec<PeerConfigKeys<SeqTypes>>);
33
34#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
35/// NewType to disambiguate DA Membership
36pub struct DAMembers(pub Vec<PeerConfig<SeqTypes>>);
37
38#[derive(Clone, Debug, From, Into, Serialize, Deserialize, PartialEq, Eq)]
39/// NewType to disambiguate StakeTable
40pub struct StakeTable(pub Vec<PeerConfig<SeqTypes>>);
41
42#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
43#[serde(bound(deserialize = ""))]
44pub struct Validator<KEY: SignatureKey> {
45    pub account: Address,
46    /// The peer's public key
47    pub stake_table_key: KEY,
48    /// the peer's state public key
49    pub state_ver_key: StateVerKey,
50    /// the peer's stake
51    pub stake: U256,
52    // commission
53    // TODO: MA commission is only valid from 0 to 10_000. Add newtype to enforce this.
54    pub commission: u16,
55    pub delegators: HashMap<Address, U256>,
56}
57
58pub(crate) fn to_fixed_bytes(value: U256) -> [u8; std::mem::size_of::<U256>()] {
59    let bytes: [u8; std::mem::size_of::<U256>()] = value.to_le_bytes();
60    bytes
61}
62
63impl<KEY: SignatureKey> Committable for Validator<KEY> {
64    fn commit(&self) -> Commitment<Self> {
65        let mut builder = RawCommitmentBuilder::new(&Self::tag())
66            .fixed_size_field("account", &self.account)
67            .var_size_field(
68                "stake_table_key",
69                self.stake_table_key.to_bytes().as_slice(),
70            )
71            .var_size_field("state_ver_key", &to_bytes!(&self.state_ver_key).unwrap())
72            .fixed_size_field("stake", &to_fixed_bytes(self.stake))
73            .constant_str("commission")
74            .u16(self.commission);
75
76        builder = builder.constant_str("delegators");
77        for (address, stake) in self.delegators.iter().sorted() {
78            builder = builder
79                .fixed_size_bytes(address)
80                .fixed_size_bytes(&to_fixed_bytes(*stake));
81        }
82
83        builder.finalize()
84    }
85
86    fn tag() -> String {
87        "VALIDATOR".to_string()
88    }
89}
90
91#[derive(serde::Serialize, serde::Deserialize, std::hash::Hash, Clone, Debug, PartialEq, Eq)]
92#[serde(bound(deserialize = ""))]
93pub struct Delegator {
94    pub address: Address,
95    pub validator: Address,
96    pub stake: U256,
97}
98
99/// Type for holding result sets matching epochs to stake tables.
100pub type IndexedStake = (
101    EpochNumber,
102    (ValidatorMap, Option<RewardAmount>),
103    Option<StakeTableHash>,
104);
105
106#[derive(Clone, derive_more::derive::Debug)]
107pub struct Fetcher {
108    /// Peers for catching up the stake table
109    #[debug(skip)]
110    pub(crate) peers: Arc<dyn StateCatchup>,
111    /// Methods for stake table persistence.
112    #[debug(skip)]
113    pub(crate) persistence: Arc<Mutex<dyn MembershipPersistence>>,
114    /// L1 provider
115    pub(crate) l1_client: L1Client,
116    /// Verifiable `ChainConfig` holding contract address
117    pub(crate) chain_config: Arc<Mutex<ChainConfig>>,
118    pub(crate) update_task: Arc<StakeTableUpdateTask>,
119    pub initial_supply: Arc<RwLock<Option<U256>>>,
120}
121
122#[derive(Debug, Default)]
123pub(crate) struct StakeTableUpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
124
125impl Drop for StakeTableUpdateTask {
126    fn drop(&mut self) {
127        if let Some(task) = self.0.get_mut().take() {
128            task.abort();
129        }
130    }
131}
132
133// (log block number, log index)
134pub type EventKey = (u64, u64);
135
136#[derive(Clone, derive_more::From, PartialEq, serde::Serialize, serde::Deserialize)]
137pub enum StakeTableEvent {
138    Register(ValidatorRegistered),
139    RegisterV2(ValidatorRegisteredV2),
140    Deregister(ValidatorExit),
141    Delegate(Delegated),
142    Undelegate(Undelegated),
143    KeyUpdate(ConsensusKeysUpdated),
144    KeyUpdateV2(ConsensusKeysUpdatedV2),
145    CommissionUpdate(CommissionUpdated),
146}
147
148
149#[derive(Clone,serde::Serialize, serde::Deserialize)]
150pub enum StakeTableEventLegacy {
151    Register(ValidatorRegisteredLegacy),
152    RegisterV2(ValidatorRegisteredV2Legacy),
153    Deregister(ValidatorExitLegacy),
154    Delegate(DelegatedLegacy),
155    Undelegate(UndelegatedLegacy),
156    KeyUpdate(ConsensusKeysUpdatedLegacy),
157    KeyUpdateV2(ConsensusKeysUpdatedV2Legacy),
158}
159
160
161impl From<StakeTableEventLegacy> for StakeTableEvent {
162    fn from(legacy: StakeTableEventLegacy) -> Self {
163        match legacy {
164            StakeTableEventLegacy::Register(v) => {
165                StakeTableEvent::Register(v.into())
166            }
167            StakeTableEventLegacy::RegisterV2(v) => {
168                StakeTableEvent::RegisterV2(v.into())
169            }
170            StakeTableEventLegacy::Deregister(v) => {
171                StakeTableEvent::Deregister(v.into())
172            }
173            StakeTableEventLegacy::Delegate(v) => {
174                StakeTableEvent::Delegate(v.into())
175            }
176            StakeTableEventLegacy::Undelegate(v) => {
177                StakeTableEvent::Undelegate(v.into())
178            }
179            StakeTableEventLegacy::KeyUpdate(v) => {
180                StakeTableEvent::KeyUpdate(v.into())
181            }
182            StakeTableEventLegacy::KeyUpdateV2(v) => {
183                StakeTableEvent::KeyUpdateV2(v.into())
184            }
185        }
186    }
187}
188
189#[derive(Debug, Error)]
190pub enum StakeTableError {
191    #[error("Validator {0:#x} already registered")]
192    AlreadyRegistered(Address),
193    #[error("Validator {0:#x} not found")]
194    ValidatorNotFound(Address),
195    #[error("Delegator {0:#x} not found")]
196    DelegatorNotFound(Address),
197    #[error("BLS key already used: {0}")]
198    BlsKeyAlreadyUsed(String),
199    #[error("Insufficient stake to undelegate")]
200    InsufficientStake,
201    #[error("Event authentication failed: {0}")]
202    AuthenticationFailed(String),
203    #[error("No validators met the minimum criteria (non-zero stake and at least one delegator)")]
204    NoValidValidators,
205    #[error("Could not compute maximum stake from filtered validators")]
206    MissingMaximumStake,
207    #[error("Overflow when calculating minimum stake threshold")]
208    MinimumStakeOverflow,
209    #[error("Delegator {0:#x} has 0 stake")]
210    ZeroDelegatorStake(Address),
211    #[error("Failed to hash stake table: {0}")]
212    HashError(#[from] bincode::Error),
213    #[error("Validator {0:#x} already exited and cannot be re-registered")]
214    ValidatorAlreadyExited(Address),
215    #[error("Validator {0:#x} has invalid commission {1}")]
216    InvalidCommission(Address, u16),
217    #[error("Schnorr key already used: {0}")]
218    SchnorrKeyAlreadyUsed(String),
219    #[error("Stake table event decode error {0}")]
220    StakeTableEventDecodeError(#[from] alloy::sol_types::Error),
221    #[error("Stake table events sorting error: {0}")]
222    EventSortingError(#[from] EventSortingError)
223}
224
225#[derive(Debug, Error)]
226pub enum ExpectedStakeTableError {
227    #[error("Schnorr key already used: {0}")]
228    SchnorrKeyAlreadyUsed(String),
229}
230
231#[derive(Debug, Error)]
232pub enum FetchRewardError {
233    #[error("No stake table contract address found in chain config")]
234    MissingStakeTableContract,
235
236    #[error("Token address fetch failed: {0}")]
237    TokenAddressFetch(#[source] alloy::contract::Error),
238
239    #[error("Token Initialized event logs are empty")]
240    MissingInitializedEvent,
241
242    #[error("Transaction hash not found in Initialized event log: {init_log:?}")]
243    MissingTransactionHash { init_log: Log },
244
245    #[error("Missing transaction receipt for Initialized event. tx_hash={tx_hash}")]
246    MissingTransactionReceipt { tx_hash: String },
247
248    #[error("Failed to get transaction for Initialized event: {0}")]
249    MissingTransaction(#[source] alloy::contract::Error),
250
251    #[error("Failed to decode Transfer log. tx_hash={tx_hash}")]
252    DecodeTransferLog { tx_hash: String },
253
254    #[error("First transfer should be a mint from the zero address")]
255    InvalidMintFromAddress,
256
257    #[error("Division by zero {0}")]
258    DivisionByZero(&'static str),
259
260    #[error("Overflow {0}")]
261    Overflow(&'static str),
262
263    #[error("Contract call failed: {0}")]
264    ContractCall(#[source] alloy::contract::Error),
265
266    #[error("Rpc call failed: {0}")]
267    Rpc(#[source] RpcError<TransportErrorKind>),
268
269    #[error("Exceeded max block range scan ({0} blocks) while searching for Initialized event")]
270    ExceededMaxScanRange(u64),
271
272    #[error("Scanning for Initialized event failed: {0}")]
273    ScanQueryFailed(#[source] alloy::contract::Error),
274}
275
276#[derive(Debug, thiserror::Error)]
277pub enum EventSortingError {
278    #[error("Missing block number in log")]
279    MissingBlockNumber,
280
281    #[error("Missing log index in log")]
282    MissingLogIndex,
283
284    #[error("Invalid stake table V2 event")]
285    InvalidStakeTableV2Event
286}