Skip to content

Commit

Permalink
Get block certificates (#113)
Browse files Browse the repository at this point in the history
* Add support for `GetBlockCertificates` and example.

* Formatting.

* Fix generated files.

* Improve documentation and rust doc.

* Doc.

* Fix conversions of bls signatures for qc and tc sigs.

* Split block_certificates into a new module file.

* Update CHANGELOG.md

Co-authored-by: Aleš Bizjak <[email protected]>

* Update src/types/block_certificates.rs

Co-authored-by: Aleš Bizjak <[email protected]>

* Address review comments.

* Revise successor proof.

* update concordium-base.

* Add optional block identifier argument to example.

---------

Co-authored-by: Aleš Bizjak <[email protected]>
  • Loading branch information
MilkywayPirate and abizjak authored Aug 29, 2023
1 parent 4a35d6f commit 0173cb0
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 9 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
## Unreleased changes
- Add a `commission_rates` field to `CurrentPaydayBakerPoolStatus` which yields the commission rates
- Add a `commission_rates` field to `CurrentPaydayBakerPoolStatus` which yields the commission rates
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.
- Add support for `GetBakersRewardPeriod` endpoint. Requires a node version at least 6.1.
- Add Support for `GetBakerEarliestWinTime` endpoint. Requires a node version at least 6.1.
- Add support for `GetBlockCertificates`. Requires a node version at least 6.1.

## 3.0.1

Expand Down
2 changes: 1 addition & 1 deletion concordium-base
36 changes: 36 additions & 0 deletions examples/v2_get_block_certificates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Test the `GetBlockCertificates` endpoint.
use anyhow::Context;
use clap::AppSettings;
use structopt::StructOpt;

use concordium_rust_sdk::v2;

#[derive(StructOpt)]
struct App {
#[structopt(
long = "node",
help = "GRPC interface of the node.",
default_value = "http://localhost:20000"
)]
endpoint: v2::Endpoint,
#[structopt(long = "block", help = "Block to query", default_value = "lastfinal")]
bi: v2::BlockIdentifier,
}

#[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 certificates = client.get_block_certificates(&app.bi).await?;
println!("{:#?}", certificates);

Ok(())
}
2 changes: 1 addition & 1 deletion examples/v2_get_first_block_epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ async fn main() -> anyhow::Result<()> {
.await
.context("Cannot connect.")?;
let fb = client.get_first_block_epoch(app.epoch).await?;
println!("{:?}", fb);
println!("{}", fb);
Ok(())
}
115 changes: 115 additions & 0 deletions src/types/block_certificates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Module exposing certificates for blocks.
//! The types are only relevant for nodes running at least protocol version
//! 6.

use concordium_base::{
base::{BakerId, Epoch, Round},
common::Serial,
hashes,
hashes::BlockHash,
};
use std::collections::BTreeSet;

/// An aggregate signature on a [`QuorumCertificate`] created
/// by members of the finalization committee.
#[derive(concordium_base::common::Serialize, Clone, Copy, Debug, PartialEq)]
pub struct QuorumSignature(
pub concordium_base::aggregate_sig::Signature<concordium_base::base::AggregateSigPairing>,
);

/// A quorum certificate on a block.
#[derive(Debug)]
pub struct QuorumCertificate {
/// [`BlockHash`] of the block that this [`QuorumCertificate`]
/// certifies.
pub block_hash: BlockHash,
/// [`Round`] of the block that this [`QuorumCertificate`]
/// certifies.
pub round: Round,
/// [`Epoch`] of the block that this [`QuorumCertificate`]
/// certifies.
pub epoch: Epoch,
/// The aggregate signature on the block identified by the
/// `block_hash` which serves as a proof that the block
/// was accepted on the chain by a quorum of the finalization
/// committee.
pub aggregate_signature: QuorumSignature,
/// The baker ids of the finalizers that formed
/// the `aggregate_signature`.
/// Note that the signatories are sorted in ascending order of [`BakerId`].
pub signatories: BTreeSet<BakerId>,
}

/// A map from a [`Round`] to the set of finalizers
/// (identified by their [`BakerId`]) which signed off
/// in the round.
#[derive(Debug)]
pub struct FinalizerRound {
/// The round which was signed off.
pub round: Round,
/// The set of finalizers who signed off.
/// (identified by their [`BakerId`])
/// Note that the baker ids are sorted in ascending order and are
/// distinct.
pub finalizers: Vec<BakerId>,
}

/// An aggregate signature on a [`TimeoutCertificate`] created
/// by members of the finalization committee.
#[derive(concordium_base::common::Serialize, Clone, Copy, Debug, PartialEq)]
pub struct TimeoutSignature(
pub concordium_base::aggregate_sig::Signature<concordium_base::base::AggregateSigPairing>,
);

/// The timeout certificate serves as a proof that no block
/// was created and/or distributed to the network in time.
/// The [`TimeoutCertificate`] makes it possible for the consensus protocol
/// to advance to the following round, thus giving (possibly) another baker
/// the chance to bake a block.
#[derive(Debug)]
pub struct TimeoutCertificate {
/// The round that timed out.
pub round: Round,
/// The minimum epoch of which signatures are included in
/// the signature for the certificate.
pub min_epoch: Epoch,
/// The rounds of which finalizers have their best quorum
/// certificates in the [`Epoch`] `min_epoch`.
pub qc_rounds_first_epoch: Vec<FinalizerRound>,
/// The rounds of which finalizers have their best quorum
/// certificates in the [`Epoch`] `min_epoch` + 1.
pub qc_rounds_second_epoch: Vec<FinalizerRound>,
/// The aggregate signature by the finalization committee which
/// serves as a proof that the [`Round`] timed out, hence
/// no block was added to the chain.
pub aggregate_signature: TimeoutSignature,
}

/// The epoch finalization entry serves as a proof that
/// a quorum of the finalization committee has progressed
/// to a new [`Epoch`].
#[derive(Debug)]
pub struct EpochFinalizationEntry {
/// The [`QuorumCertificate`] of the finalized block.
pub finalized_qc: QuorumCertificate,
/// The [`QuorumCertificate`] of the immediate successor
/// of the block indicated by `finalized_qc`.
pub successor_qc: QuorumCertificate,
/// The witness that proves that the block of the `successor_qc`
/// is an immediate decendant of the block of the `finalized_qc`.
pub successor_proof: hashes::SuccessorProof,
}

#[derive(Debug)]
pub struct BlockCertificates {
/// The [`QuorumCertificate`] of a block.
/// Note that this will be [`None`] in the case
/// where the block is a genesis block.
pub quorum_certificate: Option<QuorumCertificate>,
/// The [`TimeoutCertificate`] is present if and only if
/// the previous round of the block timed out.
pub timeout_certificate: Option<TimeoutCertificate>,
/// The [`EpochFinalizationEntry`] is present if and only if
/// the block is the first block of a new [`Epoch`].
pub epoch_finalization_entry: Option<EpochFinalizationEntry>,
}
1 change: 1 addition & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Context;
pub use concordium_base::hashes;
// re-export to maintain backwards compatibility.
pub use concordium_base::id::types::CredentialType;
pub mod block_certificates;
pub mod network;
pub mod queries;
pub mod smart_contracts;
Expand Down
131 changes: 128 additions & 3 deletions src/v2/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

use super::generated::*;

use crate::types::{queries::ConcordiumBFTDetails, UpdateKeysCollectionSkeleton};

use super::Require;
use crate::{
types::{queries::ConcordiumBFTDetails, UpdateKeysCollectionSkeleton},
v2::generated::BlockCertificates,
};
use chrono::TimeZone;
use concordium_base::{
base,
Expand All @@ -19,7 +21,7 @@ use concordium_base::{
},
updates,
};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};

fn consume<A: Deserial>(bytes: &[u8]) -> Result<A, tonic::Status> {
let mut cursor = std::io::Cursor::new(bytes);
Expand Down Expand Up @@ -3135,6 +3137,129 @@ impl TryFrom<NextUpdateSequenceNumbers> for super::types::queries::NextUpdateSeq
}
}

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

fn try_from(message: QuorumSignature) -> Result<Self, Self::Error> { consume(&message.value) }
}

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

fn try_from(message: QuorumCertificate) -> Result<Self, Self::Error> {
Ok(Self {
block_hash: message.block_hash.require()?.try_into()?,
round: message.round.require()?.into(),
epoch: message.epoch.require()?.into(),
aggregate_signature: message.aggregate_signature.require()?.try_into()?,
signatories: message
.signatories
.into_iter()
.map(From::from)
.collect::<BTreeSet<super::types::BakerId>>(),
})
}
}

impl TryFrom<SuccessorProof> for super::hashes::SuccessorProof {
type Error = tonic::Status;

fn try_from(message: SuccessorProof) -> Result<Self, Self::Error> {
match message.value.try_into() {
Ok(hash) => Ok(Self::new(hash)),
Err(_) => Err(tonic::Status::internal(
"Unexpected successor proof format.",
)),
}
}
}

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

fn try_from(message: EpochFinalizationEntry) -> Result<Self, Self::Error> {
Ok(Self {
finalized_qc: message.finalized_qc.require()?.try_into()?,
successor_qc: message.successor_qc.require()?.try_into()?,
successor_proof: message.successor_proof.require()?.try_into()?,
})
}
}

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

fn try_from(message: FinalizerRound) -> Result<Self, Self::Error> {
Ok(Self {
round: message.round.require()?.into(),
finalizers: message
.finalizers
.into_iter()
.map(From::from)
.collect::<Vec<super::types::BakerId>>(),
})
}
}

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

fn try_from(message: TimeoutSignature) -> Result<Self, Self::Error> { consume(&message.value) }
}

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

fn try_from(message: TimeoutCertificate) -> Result<Self, Self::Error> {
Ok(
Self {
round: message.round.require()?.into(),
min_epoch: message.min_epoch.require()?.into(),
qc_rounds_first_epoch:
message
.qc_rounds_first_epoch
.into_iter()
.map(TryFrom::try_from)
.collect::<Result<
Vec<super::types::block_certificates::FinalizerRound>,
tonic::Status,
>>()?,
qc_rounds_second_epoch:
message
.qc_rounds_second_epoch
.into_iter()
.map(TryFrom::try_from)
.collect::<Result<
Vec<super::types::block_certificates::FinalizerRound>,
tonic::Status,
>>()?,
aggregate_signature: message.aggregate_signature.require()?.try_into()?,
},
)
}
}

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

fn try_from(message: BlockCertificates) -> Result<Self, Self::Error> {
Ok(Self {
quorum_certificate: message
.quorum_certificate
.map(TryFrom::try_from)
.transpose()?,
timeout_certificate: message
.timeout_certificate
.map(TryFrom::try_from)
.transpose()?,
epoch_finalization_entry: message
.epoch_finalization_entry
.map(TryFrom::try_from)
.transpose()?,
})
}
}

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

Expand Down
24 changes: 23 additions & 1 deletion src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
endpoints, id,
id::types::AccountCredentialMessage,
types::{
self, hashes,
self, block_certificates, hashes,
hashes::{BlockHash, TransactionHash, TransactionSignHash},
smart_contracts::{
ContractContext, InstanceInfo, InvokeContractResult, ModuleReference, WasmModule,
Expand Down Expand Up @@ -2337,6 +2337,28 @@ impl Client {
})
}

/// For a non-genesis block, this returns the [`QuorumCertificate`], a
/// [`TimeoutCertificate`] (if present) and [`EpochFinalizationEntry`] (if
/// present).
/// If the block being pointed to is *not* from protocol version 6 or
/// above, then [`InvalidArgument`](`tonic::Code::InvalidArgument`)
/// is returned.
pub async fn get_block_certificates(
&mut self,
bi: impl IntoBlockIdentifier,
) -> endpoints::QueryResult<QueryResponse<block_certificates::BlockCertificates>> {
let response = self
.client
.get_block_certificates(&bi.into_block_identifier())
.await?;
let block_hash = extract_metadata(&response)?;
let response = block_certificates::BlockCertificates::try_from(response.into_inner())?;
Ok(QueryResponse {
block_hash,
response,
})
}

/// Get the information about a finalization record in a block.
/// A block can contain zero or one finalization record. If a record is
/// contained then this query will return information about the finalization
Expand Down

0 comments on commit 0173cb0

Please sign in to comment.