diff --git a/ipc/cli/src/commands/checkpoint/bottomup_bundles.rs b/ipc/cli/src/commands/checkpoint/bottomup_bundles.rs new file mode 100644 index 000000000..0c3e4d47e --- /dev/null +++ b/ipc/cli/src/commands/checkpoint/bottomup_bundles.rs @@ -0,0 +1,51 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: MIT +//! List bottom up bundles + +use std::fmt::Debug; +use std::str::FromStr; + +use async_trait::async_trait; +use clap::Args; +use fvm_shared::clock::ChainEpoch; +use ipc_sdk::subnet_id::SubnetID; + +use crate::commands::get_ipc_provider; +use crate::{CommandLineHandler, GlobalArguments}; + +/// The command to get bottom up bundles at height. +pub(crate) struct GetBottomUpBundles; + +#[async_trait] +impl CommandLineHandler for GetBottomUpBundles { + type Arguments = GetBottomUpBundlesArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!("get bottom up bundles with args: {:?}", arguments); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + + for h in arguments.from_epoch..=arguments.to_epoch { + let bundle = provider.get_bottom_up_bundle(&subnet, h).await?; + println!( + "checkpoint: {:?}, signatures: {:?}, signatories: {:?}, cross_msgs: {:?}", + bundle.checkpoint, bundle.signatures, bundle.signatories, bundle.cross_msgs, + ); + println!("{bundle:?}"); + } + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command(about = "List bottom up checkpoint signature bundle for a child subnet")] +pub(crate) struct GetBottomUpBundlesArgs { + #[arg(long, short, help = "The target subnet to perform query")] + pub subnet: String, + #[arg(long, short, help = "Include checkpoints from this epoch")] + pub from_epoch: ChainEpoch, + #[arg(long, short, help = "Include checkpoints up to this epoch")] + pub to_epoch: ChainEpoch, +} diff --git a/ipc/cli/src/commands/checkpoint/bottomup_height.rs b/ipc/cli/src/commands/checkpoint/bottomup_height.rs new file mode 100644 index 000000000..8e5ffaa07 --- /dev/null +++ b/ipc/cli/src/commands/checkpoint/bottomup_height.rs @@ -0,0 +1,42 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: MIT + +use std::fmt::Debug; +use std::str::FromStr; + +use async_trait::async_trait; +use clap::Args; +use ipc_sdk::subnet_id::SubnetID; + +use crate::commands::get_ipc_provider; +use crate::{CommandLineHandler, GlobalArguments}; + +/// The command to get the last bottom up checkpoint height in a subnet. +pub(crate) struct LastBottomUpCheckpointHeight; + +#[async_trait] +impl CommandLineHandler for LastBottomUpCheckpointHeight { + type Arguments = LastBottomUpCheckpointHeightArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!( + "list bottom up checkpoint height with args: {:?}", + arguments + ); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + + let height = provider.last_bottom_up_checkpoint_height(&subnet).await?; + println!("height: {height}"); + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command(about = "Last bottom up checkpoint height committed in a child subnet")] +pub(crate) struct LastBottomUpCheckpointHeightArgs { + #[arg(long, short, help = "The target subnet to perform query")] + pub subnet: String, +} diff --git a/ipc/cli/src/commands/checkpoint/bottomup_submitted.rs b/ipc/cli/src/commands/checkpoint/bottomup_submitted.rs new file mode 100644 index 000000000..1bc377d39 --- /dev/null +++ b/ipc/cli/src/commands/checkpoint/bottomup_submitted.rs @@ -0,0 +1,50 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: MIT + +use std::fmt::Debug; +use std::str::FromStr; + +use async_trait::async_trait; +use clap::Args; +use fvm_shared::address::Address; +use ipc_sdk::subnet_id::SubnetID; + +use crate::commands::get_ipc_provider; +use crate::{CommandLineHandler, GlobalArguments}; + +/// The command to check if the address has submitted in the last bottom up checkpoint height. +pub(crate) struct SubmittedInBottomUpHeight; + +#[async_trait] +impl CommandLineHandler for SubmittedInBottomUpHeight { + type Arguments = SubmittedInBottomUpHeightArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!( + "check submitted bottom up checkpoint with args: {:?}", + arguments + ); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + let address = Address::from_str(&arguments.submitter)?; + + let submitted = provider + .has_submitted_in_last_checkpoint_height(&subnet, &address) + .await?; + println!("has submitted: {submitted}"); + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command( + about = "Check if the address has submitted a signature in the last bottom up checkpoint of a child subnet" +)] +pub(crate) struct SubmittedInBottomUpHeightArgs { + #[arg(long, short, help = "The target subnet to perform query")] + pub subnet: String, + #[arg(long, short, help = "The hex encoded address of the submitter")] + pub submitter: String, +} diff --git a/ipc/cli/src/commands/checkpoint/mod.rs b/ipc/cli/src/commands/checkpoint/mod.rs index ecf0fa89f..cf7a0c445 100644 --- a/ipc/cli/src/commands/checkpoint/mod.rs +++ b/ipc/cli/src/commands/checkpoint/mod.rs @@ -1,17 +1,31 @@ // Copyright 2022-2023 Protocol Labs // SPDX-License-Identifier: MIT +use crate::commands::checkpoint::bottomup_bundles::{GetBottomUpBundles, GetBottomUpBundlesArgs}; +use crate::commands::checkpoint::bottomup_height::{ + LastBottomUpCheckpointHeight, LastBottomUpCheckpointHeightArgs, +}; +use crate::commands::checkpoint::bottomup_submitted::{ + SubmittedInBottomUpHeight, SubmittedInBottomUpHeightArgs, +}; use crate::commands::checkpoint::list_checkpoints::{ ListBottomUpCheckpoints, ListBottomUpCheckpointsArgs, }; use crate::commands::checkpoint::list_validator_changes::{ ListValidatorChanges, ListValidatorChangesArgs, }; +use crate::commands::checkpoint::quorum_reached::{ + GetQuorumReacehdEvents, GetQuorumReachedEventsArgs, +}; use crate::commands::checkpoint::relayer::{BottomUpRelayer, BottomUpRelayerArgs}; use crate::{CommandLineHandler, GlobalArguments}; use clap::{Args, Subcommand}; +mod bottomup_bundles; +mod bottomup_height; +mod bottomup_submitted; mod list_checkpoints; mod list_validator_changes; +mod quorum_reached; mod relayer; #[derive(Debug, Args)] @@ -30,6 +44,16 @@ impl CheckpointCommandsArgs { Commands::ListValidatorChanges(args) => { ListValidatorChanges::handle(global, args).await } + Commands::ListBottomupBundle(args) => GetBottomUpBundles::handle(global, args).await, + Commands::QuorumReachedEvents(args) => { + GetQuorumReacehdEvents::handle(global, args).await + } + Commands::LastBottomUpCheckpointHeight(args) => { + LastBottomUpCheckpointHeight::handle(global, args).await + } + Commands::HasSubmittedBottomUpHeight(args) => { + SubmittedInBottomUpHeight::handle(global, args).await + } } } } @@ -39,4 +63,8 @@ pub(crate) enum Commands { ListBottomup(ListBottomUpCheckpointsArgs), Relayer(BottomUpRelayerArgs), ListValidatorChanges(ListValidatorChangesArgs), + ListBottomupBundle(GetBottomUpBundlesArgs), + QuorumReachedEvents(GetQuorumReachedEventsArgs), + LastBottomUpCheckpointHeight(LastBottomUpCheckpointHeightArgs), + HasSubmittedBottomUpHeight(SubmittedInBottomUpHeightArgs), } diff --git a/ipc/cli/src/commands/checkpoint/quorum_reached.rs b/ipc/cli/src/commands/checkpoint/quorum_reached.rs new file mode 100644 index 000000000..89f8549f6 --- /dev/null +++ b/ipc/cli/src/commands/checkpoint/quorum_reached.rs @@ -0,0 +1,49 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: MIT +//! List quorum reached events + +use std::fmt::Debug; +use std::str::FromStr; + +use async_trait::async_trait; +use clap::Args; +use fvm_shared::clock::ChainEpoch; +use ipc_sdk::subnet_id::SubnetID; + +use crate::commands::get_ipc_provider; +use crate::{CommandLineHandler, GlobalArguments}; + +/// The command to list quorum reached at height. +pub(crate) struct GetQuorumReacehdEvents; + +#[async_trait] +impl CommandLineHandler for GetQuorumReacehdEvents { + type Arguments = GetQuorumReachedEventsArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!("get quorum reached events with args: {:?}", arguments); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + + for h in arguments.from_epoch..=arguments.to_epoch { + let events = provider.quorum_reached_events(&subnet, h).await?; + for e in events { + println!("{e}"); + } + } + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command(about = "List quorum reached events for a child subnet")] +pub(crate) struct GetQuorumReachedEventsArgs { + #[arg(long, short, help = "The target subnet to perform query")] + pub subnet: String, + #[arg(long, short, help = "Include events from this epoch")] + pub from_epoch: ChainEpoch, + #[arg(long, short, help = "Include events up to this epoch")] + pub to_epoch: ChainEpoch, +} diff --git a/ipc/cli/src/commands/checkpoint/relayer.rs b/ipc/cli/src/commands/checkpoint/relayer.rs index 33449c8a8..42d7b7c6c 100644 --- a/ipc/cli/src/commands/checkpoint/relayer.rs +++ b/ipc/cli/src/commands/checkpoint/relayer.rs @@ -7,9 +7,11 @@ use anyhow::anyhow; use async_trait::async_trait; use clap::Args; use fvm_shared::address::Address; +use fvm_shared::clock::ChainEpoch; use ipc_identity::EvmKeyStore; use ipc_provider::checkpoint::BottomUpCheckpointManager; -use ipc_provider::new_evm_keystore_from_path; +use ipc_provider::config::Config; +use ipc_provider::new_evm_keystore_from_config; use ipc_sdk::subnet_id::SubnetID; use std::str::FromStr; use std::sync::{Arc, RwLock}; @@ -28,8 +30,8 @@ impl CommandLineHandler for BottomUpRelayer { log::debug!("start bottom up relayer with args: {:?}", arguments); let config_path = global.config_path(); - - let mut keystore = new_evm_keystore_from_path(&config_path)?; + let config = Arc::new(Config::from_file(&config_path)?); + let mut keystore = new_evm_keystore_from_config(config)?; let submitter = match (arguments.submitter.as_ref(), keystore.get_default()?) { (Some(submitter), _) => Address::from_str(submitter)?, (None, Some(addr)) => { @@ -49,13 +51,17 @@ impl CommandLineHandler for BottomUpRelayer { let child = get_subnet_config(&config_path, &subnet)?; let parent = get_subnet_config(&config_path, &parent)?; - let manager = BottomUpCheckpointManager::new_evm_manager( + let mut manager = BottomUpCheckpointManager::new_evm_manager( parent.clone(), child.clone(), Arc::new(RwLock::new(keystore)), ) .await?; + if let Some(v) = arguments.finalization_blocks { + manager = manager.with_finalization_blocks(v as ChainEpoch); + } + let interval = Duration::from_secs( arguments .checkpoint_interval_sec @@ -74,6 +80,13 @@ pub(crate) struct BottomUpRelayerArgs { pub subnet: String, #[arg(long, short, help = "The number of seconds to submit checkpoint")] pub checkpoint_interval_sec: Option, + #[arg( + long, + short, + default_value = "0", + help = "The number of blocks away from chain head that is considered final" + )] + pub finalization_blocks: Option, #[arg(long, short, help = "The hex encoded address of the submitter")] pub submitter: Option, } diff --git a/ipc/provider/src/checkpoint.rs b/ipc/provider/src/checkpoint.rs index db2f218ea..ed6e83e58 100644 --- a/ipc/provider/src/checkpoint.rs +++ b/ipc/provider/src/checkpoint.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Result}; use fvm_shared::address::Address; use fvm_shared::clock::ChainEpoch; use ipc_identity::{EthKeyAddress, PersistentKeyStore}; +use std::cmp::max; use std::fmt::{Display, Formatter}; use std::sync::{Arc, RwLock}; use std::time::Duration; @@ -27,6 +28,8 @@ pub struct BottomUpCheckpointManager { metadata: CheckpointConfig, parent_handler: T, child_handler: T, + /// The number of blocks away from the chain head that is considered final + finalization_blocks: ChainEpoch, } impl BottomUpCheckpointManager { @@ -48,8 +51,14 @@ impl BottomUpCheckpointManager { }, parent_handler, child_handler, + finalization_blocks: 0, }) } + + pub fn with_finalization_blocks(mut self, finalization_blocks: ChainEpoch) -> Self { + self.finalization_blocks = finalization_blocks; + self + } } impl BottomUpCheckpointManager { @@ -70,7 +79,7 @@ impl Display for BottomUpCheckpointManager { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "bottom-up, parent: {:}, child: {:}", + "bottom-up relayer, parent: {:}, child: {:}", self.metadata.parent.id, self.metadata.child.id ) } @@ -94,6 +103,8 @@ impl BottomUpCheckpointMan /// Run the bottom up checkpoint submission daemon in the foreground pub async fn run(self, submitter: Address, submission_interval: Duration) { + log::info!("launching {self} for {submitter}"); + loop { if let Err(e) = self.submit_checkpoint(&submitter).await { log::error!("cannot submit checkpoint for submitter: {submitter} due to {e}"); @@ -125,7 +136,7 @@ impl BottomUpCheckpointMan async fn submit_last_epoch(&self, submitter: &Address) -> Result<()> { let subnet = &self.metadata.child.id; if self - .child_handler + .parent_handler .has_submitted_in_last_checkpoint_height(subnet, submitter) .await? { @@ -133,16 +144,28 @@ impl BottomUpCheckpointMan } let height = self - .child_handler + .parent_handler .last_bottom_up_checkpoint_height(subnet) .await?; + + if height == 0 { + log::debug!("no previous checkpoint yet"); + return Ok(()); + } + let bundle = self.child_handler.checkpoint_bundle_at(height).await?; log::debug!("bottom up bundle: {bundle:?}"); - self.parent_handler + let epoch = self + .parent_handler .submit_checkpoint(submitter, bundle) .await .map_err(|e| anyhow!("cannot submit bottom up checkpoint due to: {e:}"))?; + log::info!( + "submitted bottom up checkpoint({}) in parent at height {}", + height, + epoch + ); Ok(()) } @@ -151,21 +174,46 @@ impl BottomUpCheckpointMan async fn submit_next_epoch(&self, submitter: &Address) -> Result<()> { let next_submission_height = self.next_submission_height().await?; let current_height = self.child_handler.current_epoch().await?; + let finalized_height = max(1, current_height - self.finalization_blocks); - if current_height < next_submission_height { + log::debug!("next_submission_height: {next_submission_height}, current height: {current_height}, finalized_height: {finalized_height}"); + + if finalized_height < next_submission_height { return Ok(()); } - let bundle = self - .child_handler - .checkpoint_bundle_at(next_submission_height) - .await?; - log::debug!("bottom up bundle: {bundle:?}"); + let prev_h = next_submission_height - self.checkpoint_period(); + log::debug!("start querying quorum reached events from : {prev_h} to {finalized_height}"); - self.parent_handler - .submit_checkpoint(submitter, bundle) - .await - .map_err(|e| anyhow!("cannot submit bottom up checkpoint due to: {e:}"))?; + for h in (prev_h + 1)..=finalized_height { + let events = self.child_handler.quorum_reached_events(h).await?; + if events.is_empty() { + log::debug!("no reached events at height : {h}"); + continue; + } + + log::debug!("found reached events at height : {h}"); + + for event in events { + let bundle = self + .child_handler + .checkpoint_bundle_at(event.height) + .await?; + log::debug!("bottom up bundle: {bundle:?}"); + + let epoch = self + .parent_handler + .submit_checkpoint(submitter, bundle) + .await + .map_err(|e| anyhow!("cannot submit bottom up checkpoint due to: {e:}"))?; + + log::info!( + "submitted bottom up checkpoint({}) in parent at height {}", + event.height, + epoch + ); + } + } Ok(()) } diff --git a/ipc/provider/src/lib.rs b/ipc/provider/src/lib.rs index 939d87fbe..f01cff11b 100644 --- a/ipc/provider/src/lib.rs +++ b/ipc/provider/src/lib.rs @@ -15,6 +15,7 @@ use fvm_shared::{ use ipc_identity::{ EthKeyAddress, EvmKeyStore, KeyStore, KeyStoreConfig, PersistentKeyStore, Wallet, }; +use ipc_sdk::checkpoint::{BottomUpCheckpointBundle, QuorumReachedEvent}; use ipc_sdk::staking::StakingChangeRequest; use ipc_sdk::{ cross::CrossMsg, @@ -641,6 +642,63 @@ impl IpcProvider { conn.manager().chain_head_height().await } + pub async fn get_bottom_up_bundle( + &self, + subnet: &SubnetID, + height: ChainEpoch, + ) -> anyhow::Result { + let conn = match self.connection(subnet) { + None => return Err(anyhow!("target subnet not found")), + Some(conn) => conn, + }; + + conn.manager().checkpoint_bundle_at(height).await + } + + pub async fn has_submitted_in_last_checkpoint_height( + &self, + subnet: &SubnetID, + addr: &Address, + ) -> anyhow::Result { + let parent = subnet.parent().ok_or_else(|| anyhow!("no parent found"))?; + let conn = match self.connection(&parent) { + None => return Err(anyhow!("parent subnet not found")), + Some(conn) => conn, + }; + + conn.manager() + .has_submitted_in_last_checkpoint_height(subnet, addr) + .await + } + + pub async fn last_bottom_up_checkpoint_height( + &self, + subnet: &SubnetID, + ) -> anyhow::Result { + let parent = subnet.parent().ok_or_else(|| anyhow!("no parent found"))?; + let conn = match self.connection(&parent) { + None => return Err(anyhow!("parent subnet not found")), + Some(conn) => conn, + }; + + conn.manager() + .last_bottom_up_checkpoint_height(subnet) + .await + } + + pub async fn quorum_reached_events( + &self, + subnet: &SubnetID, + height: ChainEpoch, + ) -> anyhow::Result> { + let conn = match self.connection(subnet) { + None => return Err(anyhow!("target subnet not found")), + Some(conn) => conn, + }; + + conn.manager().quorum_reached_events(height).await + } + /// Advertises the endpoint of a bootstrap node for the subnet. pub async fn add_bootstrap( &mut self, @@ -773,7 +831,7 @@ fn new_fvm_wallet_from_config(config: Arc) -> anyhow::Result { } } -fn new_evm_keystore_from_config( +pub fn new_evm_keystore_from_config( config: Arc, ) -> anyhow::Result> { let repo_str = &config.keystore_path; diff --git a/ipc/provider/src/manager/evm/manager.rs b/ipc/provider/src/manager/evm/manager.rs index 5a9ae2787..10e1b7b0c 100644 --- a/ipc/provider/src/manager/evm/manager.rs +++ b/ipc/provider/src/manager/evm/manager.rs @@ -7,8 +7,8 @@ use std::time::Duration; use ethers::types::H256; use ipc_actors_abis::{ - gateway_getter_facet, gateway_manager_facet, gateway_messenger_facet, lib_staking_change_log, - subnet_actor_getter_facet, subnet_actor_manager_facet, subnet_registry, + gateway_getter_facet, gateway_manager_facet, gateway_messenger_facet, gateway_router_facet, + lib_staking_change_log, subnet_actor_getter_facet, subnet_actor_manager_facet, subnet_registry, }; use ipc_sdk::evm::{fil_to_eth_amount, payload_to_evm_address, subnet_id_to_evm_addresses}; use ipc_sdk::validator::from_contract_validators; @@ -35,7 +35,7 @@ use ethers::types::{BlockId, Eip1559TransactionRequest, I256, U256}; use fvm_shared::clock::ChainEpoch; use fvm_shared::{address::Address, econ::TokenAmount}; use ipc_identity::{EthKeyAddress, EvmKeyStore, PersistentKeyStore}; -use ipc_sdk::checkpoint::{BottomUpCheckpoint, BottomUpCheckpointBundle}; +use ipc_sdk::checkpoint::{BottomUpCheckpoint, BottomUpCheckpointBundle, QuorumReachedEvent}; use ipc_sdk::cross::CrossMsg; use ipc_sdk::gateway::Status; use ipc_sdk::staking::StakingChangeRequest; @@ -887,7 +887,7 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { &self, submitter: &Address, bundle: BottomUpCheckpointBundle, - ) -> anyhow::Result<()> { + ) -> anyhow::Result { let BottomUpCheckpointBundle { checkpoint, signatures, @@ -896,7 +896,7 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { } = bundle; let address = contract_address_from_subnet(&checkpoint.subnet_id)?; - log::info!( + log::debug!( "submit bottom up checkpoint: {checkpoint:?} in evm subnet contract: {address:}" ); @@ -918,12 +918,11 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); let call = contract.submit_checkpoint(checkpoint, cross_msgs, signatories, signatures); - call_with_premium_estimation(signer, call) - .await? - .send() - .await?; + let call = call_with_premium_estimation(signer, call).await?; - Ok(()) + let pending_tx = call.send().await?; + let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; + block_number_from_receipt(receipt) } async fn last_bottom_up_checkpoint_height( @@ -1001,6 +1000,28 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { }) } + async fn quorum_reached_events(&self, height: ChainEpoch) -> Result> { + let contract = gateway_router_facet::GatewayRouterFacet::new( + self.ipc_contract_info.gateway_addr, + Arc::new(self.ipc_contract_info.provider.clone()), + ); + + let ev = contract + .event::() + .from_block(height as u64) + .to_block(height as u64); + + let mut events = vec![]; + for (event, _meta) in ev.query_with_meta().await? { + events.push(QuorumReachedEvent { + height: event.height as ChainEpoch, + checkpoint: event.checkpoint.to_vec(), + quorum_weight: eth_to_fil_amount(&event.quorum_weight)?, + }); + } + + Ok(events) + } async fn current_epoch(&self) -> Result { let epoch = self .ipc_contract_info diff --git a/ipc/provider/src/manager/subnet.rs b/ipc/provider/src/manager/subnet.rs index ab28783d5..049ed610e 100644 --- a/ipc/provider/src/manager/subnet.rs +++ b/ipc/provider/src/manager/subnet.rs @@ -7,7 +7,7 @@ use anyhow::Result; use async_trait::async_trait; use fvm_shared::clock::ChainEpoch; use fvm_shared::{address::Address, econ::TokenAmount}; -use ipc_sdk::checkpoint::BottomUpCheckpointBundle; +use ipc_sdk::checkpoint::{BottomUpCheckpointBundle, QuorumReachedEvent}; use ipc_sdk::cross::CrossMsg; use ipc_sdk::staking::StakingChangeRequest; use ipc_sdk::subnet::ConstructParams; @@ -188,11 +188,12 @@ pub trait TopDownCheckpointQuery: Send + Sync { pub trait BottomUpCheckpointRelayer: Send + Sync { /// Submit a checkpoint for execution. /// It triggers the commitment of the checkpoint and the execution of related cross-net messages. + /// Returns the epoch that the execution is successful async fn submit_checkpoint( &self, submitter: &Address, bundle: BottomUpCheckpointBundle, - ) -> Result<()>; + ) -> Result; /// The last confirmed/submitted checkpoint height. async fn last_bottom_up_checkpoint_height(&self, subnet_id: &SubnetID) -> Result; /// Check if the submitter has already submitted in the `last_bottom_up_checkpoint_height` @@ -205,6 +206,8 @@ pub trait BottomUpCheckpointRelayer: Send + Sync { async fn checkpoint_period(&self, subnet_id: &SubnetID) -> Result; /// Get the checkpoint bundle at a specific height. If it does not exist, it will through error. async fn checkpoint_bundle_at(&self, height: ChainEpoch) -> Result; + /// Queries the signature quorum reached events at target height. + async fn quorum_reached_events(&self, height: ChainEpoch) -> Result>; /// Get the current epoch in the current subnet async fn current_epoch(&self) -> Result; } diff --git a/ipc/sdk/src/checkpoint.rs b/ipc/sdk/src/checkpoint.rs index 7db89e678..ca1be206a 100644 --- a/ipc/sdk/src/checkpoint.rs +++ b/ipc/sdk/src/checkpoint.rs @@ -7,11 +7,14 @@ use crate::subnet_id::SubnetID; use cid::multihash::Code; use cid::multihash::MultihashDigest; use cid::Cid; +use ethers::utils::hex; use fvm_ipld_encoding::DAG_CBOR; use fvm_shared::address::Address; use fvm_shared::clock::ChainEpoch; +use fvm_shared::econ::TokenAmount; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; lazy_static! { // Default CID used for the genesis checkpoint. Using @@ -23,6 +26,27 @@ lazy_static! { pub type Signature = Vec; +/// The event emitted +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct QuorumReachedEvent { + pub height: ChainEpoch, + /// The checkpoint hash + pub checkpoint: Vec, + pub quorum_weight: TokenAmount, +} + +impl Display for QuorumReachedEvent { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "QuorumReachedEvent", + self.height, + hex::encode(&self.checkpoint), + self.quorum_weight + ) + } +} + /// The collection of items for the bottom up checkpoint submission #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct BottomUpCheckpointBundle {