diff --git a/sequencer/api/node.toml b/sequencer/api/node.toml new file mode 100644 index 000000000..ddcdaa7db --- /dev/null +++ b/sequencer/api/node.toml @@ -0,0 +1,8 @@ +[route.stake_table_current] +PATH = ["stake-table/current"] +DOC = "Get the stake table for the current epoch" + +[route.stake_table] +PATH = ["stake-table/:epoch_number"] +":epoch_number" = "Integer" +DOC = "Get the stake table for the given epoch" diff --git a/sequencer/src/api.rs b/sequencer/src/api.rs index e89c63550..4ca7046cd 100644 --- a/sequencer/src/api.rs +++ b/sequencer/src/api.rs @@ -5,7 +5,7 @@ use async_lock::RwLock; use async_once_cell::Lazy; use async_trait::async_trait; use committable::{Commitment, Committable}; -use data_source::{CatchupDataSource, SubmitDataSource}; +use data_source::{CatchupDataSource, StakeTableDataSource, SubmitDataSource}; use derivative::Derivative; use espresso_types::{ retain_accounts, v0::traits::SequencerPersistence, v0_3::ChainConfig, AccountQueryData, @@ -26,9 +26,14 @@ use hotshot_types::{ event::Event, light_client::StateSignatureRequestBody, network::NetworkConfig, - traits::{network::ConnectedNetwork, node_implementation::Versions, ValidatedState as _}, + traits::{ + network::ConnectedNetwork, + node_implementation::{NodeType, Versions}, + ValidatedState as _, + }, utils::{View, ViewInner}, }; +use hotshot_types::{stake_table::StakeTableEntry, traits::election::Membership}; use jf_merkle_tree::MerkleTreeScheme; use std::sync::Arc; @@ -157,6 +162,43 @@ impl, D: Send + Sync, V: Versions, P: SequencerPersi } } +impl, D: Sync, V: Versions, P: SequencerPersistence> + StakeTableDataSource for StorageState +{ + /// Get the stake table for a given epoch or the current epoch if not provided + async fn get_stake_table( + &self, + epoch: Option<::Epoch>, + ) -> Vec::SignatureKey>> { + self.as_ref().get_stake_table(epoch).await + } +} + +impl, V: Versions, P: SequencerPersistence> + StakeTableDataSource for ApiState +{ + /// Get the stake table for a given epoch or the current epoch if not provided + async fn get_stake_table( + &self, + epoch: Option<::Epoch>, + ) -> Vec::SignatureKey>> { + // Get the epoch from the argument or the current epoch if not provided + let epoch = if let Some(epoch) = epoch { + epoch + } else { + self.consensus().await.read().await.cur_epoch().await + }; + + self.consensus() + .await + .read() + .await + .memberships + .quorum_membership + .stake_table(epoch) + } +} + impl, V: Versions, P: SequencerPersistence> SubmitDataSource for ApiState { diff --git a/sequencer/src/api/data_source.rs b/sequencer/src/api/data_source.rs index dee7a7b07..34d50af1b 100644 --- a/sequencer/src/api/data_source.rs +++ b/sequencer/src/api/data_source.rs @@ -16,16 +16,18 @@ use hotshot_query_service::{ node::NodeDataSource, status::StatusDataSource, }; -use hotshot_types::network::{ - BuilderType, CombinedNetworkConfig, Libp2pConfig, RandomBuilderConfig, -}; use hotshot_types::{ data::ViewNumber, light_client::StateSignatureRequestBody, network::NetworkConfig, + stake_table::StakeTableEntry, traits::{network::ConnectedNetwork, node_implementation::Versions}, HotShotConfig, PeerConfig, ValidatorConfig, }; +use hotshot_types::{ + network::{BuilderType, CombinedNetworkConfig, Libp2pConfig, RandomBuilderConfig}, + traits::node_implementation::NodeType, +}; use serde::{Deserialize, Serialize}; use tide_disco::Url; use vec1::Vec1; @@ -114,6 +116,14 @@ pub(crate) trait NodeStateDataSource { fn node_state(&self) -> impl Send + Future; } +pub(crate) trait StakeTableDataSource { + /// Get the stake table for a given epoch or the current epoch if not provided + fn get_stake_table( + &self, + epoch: Option<::Epoch>, + ) -> impl Send + Future>>; +} + pub(crate) trait CatchupDataSource: Sync { /// Get the state of the requested `account`. /// diff --git a/sequencer/src/api/endpoints.rs b/sequencer/src/api/endpoints.rs index 9d9d362cb..40f4e752c 100644 --- a/sequencer/src/api/endpoints.rs +++ b/sequencer/src/api/endpoints.rs @@ -9,7 +9,6 @@ use anyhow::Result; use committable::Committable; use espresso_types::{FeeAccount, FeeMerkleTree, NamespaceId, NsProof, PubKey, Transaction}; use futures::{try_join, FutureExt}; -use hotshot_query_service::merklized_state::Snapshot; use hotshot_query_service::{ availability::{self, AvailabilityDataSource, CustomSnafu, FetchBlockSnafu}, explorer::{self, ExplorerDataSource}, @@ -18,8 +17,9 @@ use hotshot_query_service::{ }, node, ApiState, Error, }; +use hotshot_query_service::{merklized_state::Snapshot, node::NodeDataSource}; use hotshot_types::{ - data::ViewNumber, + data::{EpochNumber, ViewNumber}, traits::{ network::ConnectedNetwork, node_implementation::{ConsensusTime, Versions}, @@ -30,12 +30,12 @@ use serde::{de::Error as _, Deserialize, Serialize}; use snafu::OptionExt; use tagged_base64::TaggedBase64; use tide_disco::{method::ReadState, Api, Error as _, StatusCode}; -use vbs::version::StaticVersionType; +use vbs::version::{StaticVersion, StaticVersionType}; use super::{ data_source::{ CatchupDataSource, HotShotConfigDataSource, NodeStateDataSource, SequencerDataSource, - StateSignatureDataSource, SubmitDataSource, + StakeTableDataSource, StateSignatureDataSource, SubmitDataSource, }, StorageState, }; @@ -174,18 +174,47 @@ where Ok(api) } -type NodeApi = Api, node::Error, ApiVer>; - -pub(super) fn node() -> Result> +pub(super) fn node() -> Result>> where - N: ConnectedNetwork, - D: SequencerDataSource + Send + Sync + 'static, - P: SequencerPersistence, + S: 'static + Send + Sync + ReadState, + ::State: + Send + Sync + StakeTableDataSource + NodeDataSource, { - let api = node::define_api::, SeqTypes, _>( - &Default::default(), - SequencerApiVersion::instance(), - )?; + // Extend the base API + let mut options = node::Options::default(); + let extension = toml::from_str(include_str!("../../api/node.toml"))?; + options.extensions.push(extension); + + // Create the base API with our extensions + let mut api = node::define_api::(&options, SequencerApiVersion::instance())?; + + // Tack on the application logic + api.at("stake_table", |req, state| { + async move { + // Try to get the epoch from the request. If this fails, error + // as it was probably a mistake + let epoch = EpochNumber::new(req.integer_param("epoch_number").map_err(|_| { + hotshot_query_service::node::Error::Custom { + message: "Epoch number is required".to_string(), + status: StatusCode::BAD_REQUEST, + } + })?); + + Ok(state + .read(|state| state.get_stake_table(Some(epoch)).boxed()) + .await) + } + .boxed() + })? + .at("stake_table_current", |_, state| { + async move { + Ok(state + .read(|state| state.get_stake_table(None).boxed()) + .await) + } + .boxed() + })?; + Ok(api) } pub(super) fn submit() -> Result>