Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Relayer cli #339

Merged
merged 9 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions ipc/cli/src/commands/checkpoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
use crate::commands::checkpoint::list_checkpoints::{
ListBottomUpCheckpoints, ListBottomUpCheckpointsArgs,
};
use crate::commands::checkpoint::relayer::{BottomUpRelayer, BottomUpRelayerArgs};
use crate::{CommandLineHandler, GlobalArguments};
use clap::{Args, Subcommand};

mod list_checkpoints;
mod relayer;

#[derive(Debug, Args)]
#[command(name = "checkpoint", about = "checkpoint related commands")]
Expand All @@ -20,11 +22,13 @@ impl CheckpointCommandsArgs {
pub async fn handle(&self, global: &GlobalArguments) -> anyhow::Result<()> {
match &self.command {
Commands::ListBottomup(args) => ListBottomUpCheckpoints::handle(global, args).await,
Commands::Relayer(args) => BottomUpRelayer::handle(global, args).await,
}
}
}

#[derive(Debug, Subcommand)]
pub(crate) enum Commands {
ListBottomup(ListBottomUpCheckpointsArgs),
Relayer(BottomUpRelayerArgs),
}
79 changes: 79 additions & 0 deletions ipc/cli/src/commands/checkpoint/relayer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2022-2023 Protocol Labs
// SPDX-License-Identifier: MIT

use crate::commands::get_subnet_config;
use crate::{CommandLineHandler, GlobalArguments};
use anyhow::anyhow;
use async_trait::async_trait;
use clap::Args;
use fvm_shared::address::Address;
use ipc_identity::EvmKeyStore;
use ipc_provider::checkpoint::BottomUpCheckpointManager;
use ipc_provider::new_evm_keystore_from_path;
use ipc_sdk::subnet_id::SubnetID;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
use std::time::Duration;

const DEFAULT_POLLING_INTERVAL: u64 = 15;

/// The command to run the bottom up relayer in the background.
pub(crate) struct BottomUpRelayer;

#[async_trait]
impl CommandLineHandler for BottomUpRelayer {
type Arguments = BottomUpRelayerArgs;

async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> {
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 submitter = match (arguments.submitter.as_ref(), keystore.get_default()?) {
(Some(submitter), _) => Address::from_str(submitter)?,
(None, Some(addr)) => {
log::info!("using default address: {addr:?}");
Address::try_from(addr)?
}
_ => {
return Err(anyhow!("no submitter address provided"));
}
};

let subnet = SubnetID::from_str(&arguments.subnet)?;
let parent = subnet
.parent()
.ok_or_else(|| anyhow!("root does not have parent"))?;

let child = get_subnet_config(&config_path, &subnet)?;
let parent = get_subnet_config(&config_path, &parent)?;

let manager = BottomUpCheckpointManager::new_evm_manager(
parent.clone(),
child.clone(),
Arc::new(RwLock::new(keystore)),
)
.await?;

let interval = Duration::from_secs(
arguments
.checkpoint_interval_sec
.unwrap_or(DEFAULT_POLLING_INTERVAL),
);
manager.run(submitter, interval).await;

Ok(())
}
}

#[derive(Debug, Args)]
#[command(about = "Start the bottom up relayer daemon")]
pub(crate) struct BottomUpRelayerArgs {
#[arg(long, short, help = "The subnet id of the checkpointing subnet")]
pub subnet: String,
#[arg(long, short, help = "The number of seconds to submit checkpoint")]
pub checkpoint_interval_sec: Option<u64>,
#[arg(long, short, help = "The hex encoded address of the submitter")]
pub submitter: Option<String>,
}
18 changes: 17 additions & 1 deletion ipc/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ use crate::commands::checkpoint::CheckpointCommandsArgs;
use crate::commands::crossmsg::CrossMsgsCommandsArgs;
use crate::commands::util::UtilCommandsArgs;
use crate::GlobalArguments;
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};

use clap::{Command, CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Generator, Shell};
use fvm_shared::econ::TokenAmount;
use ipc_sdk::ethers_address_to_fil_address;

use ipc_provider::config::{Config, Subnet};
use ipc_sdk::subnet_id::SubnetID;
use std::fmt::Debug;
use std::io;
use std::path::Path;
use std::str::FromStr;

use crate::commands::config::ConfigCommandsArgs;
Expand Down Expand Up @@ -146,6 +149,19 @@ pub(crate) fn require_fil_addr_from_str(s: &str) -> anyhow::Result<fvm_shared::a
Ok(addr)
}

/// Get the subnet configuration from the config path
pub(crate) fn get_subnet_config(
config_path: impl AsRef<Path>,
subnet: &SubnetID,
) -> Result<Subnet> {
let config = Config::from_file(&config_path)?;
Ok(config
.subnets
.get(subnet)
.ok_or_else(|| anyhow!("{subnet} is not configured"))?
.clone())
}

#[cfg(test)]
mod tests {
use crate::f64_to_token_amount;
Expand Down
2 changes: 1 addition & 1 deletion ipc/identity/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn random_eth_key_info() -> KeyInfo {
}

#[cfg(feature = "with-ethers")]
#[derive(Clone, Eq, Hash, PartialEq, Default)]
#[derive(Debug, Clone, Eq, Hash, PartialEq, Default)]
pub struct EthKeyAddress {
inner: ethers::types::Address,
}
Expand Down
82 changes: 58 additions & 24 deletions ipc/provider/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub struct CheckpointConfig {
period: ChainEpoch,
}

/// Manages the submission of bottom up checkpoint. It checks if the submitter has already
/// submitted in the `last_checkpoint_height`, if not, it will submit the checkpoint at that height.
/// Then it will submit at the next submission height for the new checkpoint.
pub struct BottomUpCheckpointManager<T> {
metadata: CheckpointConfig,
parent_handler: T,
Expand Down Expand Up @@ -88,21 +91,63 @@ impl<T: BottomUpCheckpointRelayer + Send + Sync + 'static> BottomUpCheckpointMan
self.metadata.period
}

/// Run the bottom up checkpoint submission daemon in the background
pub fn run(self, validator: Address, submission_interval: Duration) {
tokio::spawn(async move {
loop {
if let Err(e) = self.submit_checkpoint(&validator).await {
log::error!("cannot submit checkpoint for validator: {validator} due to {e}");
}

tokio::time::sleep(submission_interval).await;
/// Run the bottom up checkpoint submission daemon in the foreground
pub async fn run(self, submitter: Address, submission_interval: Duration) {
loop {
if let Err(e) = self.submit_checkpoint(&submitter).await {
log::error!("cannot submit checkpoint for submitter: {submitter} due to {e}");
}
});

tokio::time::sleep(submission_interval).await;
}
}

/// Submit the checkpoint from the target submitter address
pub async fn submit_checkpoint(&self, submitter: &Address) -> Result<()> {
self.submit_last_epoch(submitter).await?;
self.submit_next_epoch(submitter).await
}

/// Derive the next submission checkpoint height
async fn next_submission_height(&self) -> Result<ChainEpoch> {
let last_checkpoint_epoch = self
.parent_handler
.last_bottom_up_checkpoint_height(&self.metadata.child.id)
.await
.map_err(|e| {
anyhow!("cannot obtain the last bottom up checkpoint height due to: {e:}")
})?;
Ok(last_checkpoint_epoch + self.checkpoint_period())
}

/// Checks if the relayer has already submitted at the `last_checkpoint_height`, if not it submits it.
async fn submit_last_epoch(&self, submitter: &Address) -> Result<()> {
let subnet = &self.metadata.child.id;
if self
.child_handler
.has_submitted_in_last_checkpoint_height(subnet, submitter)
.await?
{
return Ok(());
}

let height = self
.child_handler
.last_bottom_up_checkpoint_height(subnet)
.await?;
let bundle = self.child_handler.checkpoint_bundle_at(height).await?;
log::debug!("bottom up bundle: {bundle:?}");

self.parent_handler
.submit_checkpoint(submitter, bundle)
.await
.map_err(|e| anyhow!("cannot submit bottom up checkpoint due to: {e:}"))?;

Ok(())
}

/// Submit the checkpoint from the target validator address
pub async fn submit_checkpoint(&self, validator: &Address) -> Result<()> {
/// Checks if the relayer has already submitted at the next submission epoch, if not it submits it.
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?;

Expand All @@ -117,21 +162,10 @@ impl<T: BottomUpCheckpointRelayer + Send + Sync + 'static> BottomUpCheckpointMan
log::debug!("bottom up bundle: {bundle:?}");

self.parent_handler
.submit_checkpoint(validator, bundle)
.submit_checkpoint(submitter, bundle)
.await
.map_err(|e| anyhow!("cannot submit bottom up checkpoint due to: {e:}"))?;

Ok(())
}

async fn next_submission_height(&self) -> Result<ChainEpoch> {
let last_checkpoint_epoch = self
.parent_handler
.last_bottom_up_checkpoint_height(&self.metadata.child.id)
.await
.map_err(|e| {
anyhow!("cannot obtain the last bottom up checkpoint height due to: {e:}")
})?;
Ok(last_checkpoint_epoch + self.checkpoint_period())
}
}
17 changes: 17 additions & 0 deletions ipc/provider/src/manager/evm/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,23 @@ impl BottomUpCheckpointRelayer for EthSubnetManager {
Ok(epoch as ChainEpoch)
}

async fn has_submitted_in_last_checkpoint_height(
&self,
subnet_id: &SubnetID,
submitter: &Address,
) -> Result<bool> {
let address = contract_address_from_subnet(subnet_id)?;
let contract = subnet_actor_getter_facet::SubnetActorGetterFacet::new(
address,
Arc::new(self.ipc_contract_info.provider.clone()),
);
let addr = payload_to_evm_address(submitter.payload())?;
Ok(contract
.has_submitted_in_last_bottom_up_checkpoint_height(addr)
.call()
.await?)
}

async fn checkpoint_period(&self, subnet_id: &SubnetID) -> anyhow::Result<ChainEpoch> {
let address = contract_address_from_subnet(subnet_id)?;
let contract = subnet_actor_getter_facet::SubnetActorGetterFacet::new(
Expand Down
8 changes: 7 additions & 1 deletion ipc/provider/src/manager/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,15 @@ pub trait BottomUpCheckpointRelayer: Send + Sync {
) -> Result<()>;
/// The last confirmed/submitted checkpoint height.
async fn last_bottom_up_checkpoint_height(&self, subnet_id: &SubnetID) -> Result<ChainEpoch>;
/// Check if the submitter has already submitted in the `last_bottom_up_checkpoint_height`
async fn has_submitted_in_last_checkpoint_height(
&self,
subnet_id: &SubnetID,
submitter: &Address,
) -> Result<bool>;
/// Get the checkpoint period, i.e the number of blocks to submit bottom up checkpoints.
async fn checkpoint_period(&self, subnet_id: &SubnetID) -> Result<ChainEpoch>;
/// Get the checkpoint at a specific height. If it does not exist, it will through error.
/// 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<BottomUpCheckpointBundle>;
/// Get the current epoch in the current subnet
async fn current_epoch(&self) -> Result<ChainEpoch>;
Expand Down
1 change: 1 addition & 0 deletions ipc/sdk/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ lazy_static! {

pub type Signature = Vec<u8>;

/// The collection of items for the bottom up checkpoint submission
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct BottomUpCheckpointBundle {
pub checkpoint: BottomUpCheckpoint,
Expand Down