Skip to content

Commit

Permalink
Support for GetWinningBakersEpoch (#117)
Browse files Browse the repository at this point in the history
* GetWinningBakersEpoch

* Example for getting winning bakers.

* Formatting.

* Changelog.

* Address review comments.

* Doc.

* Address review comments..

* Implement `GetFirstBlockEpoch` and example. (#119)

* Fix.

* Fix a fix.

* Doc.

* Better documentation for `get_winning_bakers_epoch`.

* Fix links
  • Loading branch information
MilkywayPirate authored Aug 29, 2023
1 parent 7dbaf45 commit 4a35d6f
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## Unreleased changes
- Add a `commission_rates` field to `CurrentPaydayBakerPoolStatus` which yields the commission rates
of the baker for the reward period (requires a node with version at least 6.1).
of the baker for the reward period. Requires a node version at least 6.1.
- Add support for `GetWinningBakersEpoch`. Requires a node version at least 6.1.
- Add Support for `GetFirstBlockEpoch`. Requires a node version at least 6.1.
- Add support for `GetBakersRewardPeriod` endpoint. Requires a node with version at least 6.1.
- Add Support for `GetBakerEarliestWinTime` endpoint. Requires a node with version at least 6.1.

Expand Down
34 changes: 34 additions & 0 deletions examples/v2_get_first_block_epoch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Test the `GetFirstBlockEpoch` endpoint.
use anyhow::Context;
use clap::AppSettings;
use concordium_rust_sdk::v2;
use structopt::StructOpt;

#[derive(StructOpt)]
struct App {
#[structopt(
long = "node",
help = "GRPC interface of the node.",
default_value = "http://localhost:20000"
)]
endpoint: v2::Endpoint,

#[structopt(long = "epoch", help = "Epoch identifier", default_value = "%5,0")]
epoch: v2::EpochIdentifier,
}

#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
let app = {
let app = App::clap().global_setting(AppSettings::ColoredHelp);
let matches = app.get_matches();
App::from_clap(&matches)
};

let mut client = v2::Client::new(app.endpoint)
.await
.context("Cannot connect.")?;
let fb = client.get_first_block_epoch(app.epoch).await?;
println!("{:?}", fb);
Ok(())
}
37 changes: 37 additions & 0 deletions examples/v2_get_winning_bakers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Test the `GetWinningBakersEpoch` endpoint.
use anyhow::Context;
use clap::AppSettings;
use concordium_rust_sdk::v2;
use futures::StreamExt;
use structopt::StructOpt;

#[derive(StructOpt)]
struct App {
#[structopt(
long = "node",
help = "GRPC interface of the node.",
default_value = "http://localhost:20000"
)]
endpoint: v2::Endpoint,

#[structopt(long = "epoch", help = "Epoch identifier", default_value = "%5,1")]
epoch: v2::EpochIdentifier,
}

#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
let app = {
let app = App::clap().global_setting(AppSettings::ColoredHelp);
let matches = app.get_matches();
App::from_clap(&matches)
};

let mut client = v2::Client::new(app.endpoint)
.await
.context("Cannot connect.")?;
let mut wbs = client.get_winning_bakers_epoch(app.epoch).await?;
while let Some(wb) = wbs.next().await {
println!("{:?}", wb?);
}
Ok(())
}
12 changes: 12 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,18 @@ pub struct NodeInfo {
pub details: NodeDetails,
}

/// A baker that has won a round in consensus version 1.
#[derive(Debug)]
pub struct WinningBaker {
/// The round that was won.
pub round: Round,
/// The id of the baker that won the round.
pub winner: BakerId,
/// Whether the block that was made (if any) is
/// part of the finalized chain.
pub present: bool,
}

/// Information of a baker for a certain reward period.
#[derive(Debug)]
pub struct BakerRewardPeriodInfo {
Expand Down
14 changes: 14 additions & 0 deletions src/v2/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3135,6 +3135,20 @@ impl TryFrom<NextUpdateSequenceNumbers> for super::types::queries::NextUpdateSeq
}
}

impl TryFrom<WinningBaker> for super::types::WinningBaker {
type Error = tonic::Status;

fn try_from(wb: WinningBaker) -> Result<Self, Self::Error> {
Ok(Self {
round: wb.round.require()?.value.into(),
winner: super::types::BakerId {
id: wb.winner.require()?.value.into(),
},
present: wb.present,
})
}
}

impl TryFrom<BakerRewardPeriodInfo> for super::types::BakerRewardPeriodInfo {
type Error = tonic::Status;

Expand Down
129 changes: 129 additions & 0 deletions src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,103 @@ pub enum AccountIdentifier {
Index(crate::types::AccountIndex),
}

/// Identifier for an [`Epoch`] relative to the specified genesis index.
#[derive(Debug, Copy, Clone)]
pub struct SpecifiedEpoch {
/// Genesis index to query in.
pub genesis_index: types::GenesisIndex,
/// The epoch of the genesis to query.
pub epoch: types::Epoch,
}

/// An identifier of an epoch used in queries.
#[derive(Copy, Clone, Debug, derive_more::From)]
pub enum EpochIdentifier {
/// A specified epoch to query.
Specified(SpecifiedEpoch),
/// Query the epoch of the block.
Block(BlockIdentifier),
}

/// Errors that may occur as a result of
/// parsing a [`EpochIdentifier`] from a string via
/// [from_str(&str)][std::str::FromStr].
#[derive(Debug, thiserror::Error)]
pub enum EpochIdentifierFromStrError {
#[error("The input is not recognized.")]
InvalidFormat,
#[error("The genesis index is not a valid unsigned integer")]
InvalidGenesis,
#[error("The epoch index is not a valid unsigned integer")]
InvalidEpoch,
#[error("The input is not a valid block identifier: {0}.")]
InvalidBlockIdentifier(#[from] BlockIdentifierFromStrError),
}

/// Parse a string as an [`EpochIdentifier`]. The format is one of the
/// following:
///
/// - a string starting with `%` followed by two integers separated by `,` for
/// [`Specified`](EpochIdentifier::Specified). First component is treated as
/// the genesis index and the second component as the epoch.
/// - a string starting with `@` followed by a [`BlockIdentifier`] for
/// [`Block`](EpochIdentifier::Block).
impl std::str::FromStr for EpochIdentifier {
type Err = EpochIdentifierFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(rest) = s.strip_prefix('%') {
if let Some((gen_idx_str, epoch_str)) = rest.split_once(',') {
let genesis_index = GenesisIndex::from_str(gen_idx_str)
.map_err(|_| EpochIdentifierFromStrError::InvalidGenesis)?;
let epoch = Epoch::from_str(epoch_str)
.map_err(|_| EpochIdentifierFromStrError::InvalidEpoch)?;
Ok(Self::Specified(SpecifiedEpoch {
genesis_index,
epoch,
}))
} else {
Err(EpochIdentifierFromStrError::InvalidFormat)
}
} else {
Ok(Self::Block(BlockIdentifier::from_str(s)?))
}
}
}

impl IntoRequest<generated::EpochRequest> for &EpochIdentifier {
fn into_request(self) -> tonic::Request<generated::EpochRequest> {
tonic::Request::new((*self).into())
}
}

impl From<EpochIdentifier> for generated::EpochRequest {
fn from(ei: EpochIdentifier) -> Self {
match ei {
EpochIdentifier::Specified(SpecifiedEpoch {
genesis_index,
epoch,
}) => generated::EpochRequest {
epoch_request_input: Some(
generated::epoch_request::EpochRequestInput::RelativeEpoch(
generated::epoch_request::RelativeEpoch {
genesis_index: Some(generated::GenesisIndex {
value: genesis_index.height,
}),
epoch: Some(generated::Epoch { value: epoch.epoch }),
},
),
),
},
EpochIdentifier::Block(bi) => generated::EpochRequest {
epoch_request_input: Some(generated::epoch_request::EpochRequestInput::BlockHash(
(&bi).into(),
)),
},
}
}
}

/// Information of a finalized block.
#[derive(Copy, Clone, Debug)]
pub struct FinalizedBlockInfo {
Expand Down Expand Up @@ -2171,6 +2268,38 @@ impl Client {
})
}

/// Get the winning bakers of an historical `Epoch`.
/// Hence, when this function is invoked using [`EpochIdentifier::Block`]
/// and the [`BlockIdentifier`] is either [`BlockIdentifier::Best`] or
/// [`BlockIdentifier::LastFinal`], then [`tonic::Code::Unavailable`] is
/// returned, as these identifiers are not historical by definition.
///
/// The stream ends when there
/// are no more rounds for the epoch specified. This only works for
/// epochs in at least protocol version 6. Note that the endpoint is
/// only available on a node running at least protocol version 6.
pub async fn get_winning_bakers_epoch(
&mut self,
ei: impl Into<EpochIdentifier>,
) -> endpoints::QueryResult<impl Stream<Item = Result<types::WinningBaker, tonic::Status>>>
{
let response = self.client.get_winning_bakers_epoch(&ei.into()).await?;
let stream = response.into_inner().map(|result| match result {
Ok(wb) => wb.try_into(),
Err(err) => Err(err),
});
Ok(stream)
}

/// Get the first block of the epoch.
pub async fn get_first_block_epoch(
&mut self,
ei: impl Into<EpochIdentifier>,
) -> endpoints::QueryResult<BlockHash> {
let response = self.client.get_first_block_epoch(&ei.into()).await?;
Ok(response.into_inner().try_into()?)
}

/// Get next available sequence numbers for updating chain parameters after
/// a given block. If the block does not exist then [`QueryError::NotFound`]
/// is returned.
Expand Down

0 comments on commit 4a35d6f

Please sign in to comment.