Skip to content

Commit

Permalink
ENG-842: Export topdown events (#871)
Browse files Browse the repository at this point in the history
  • Loading branch information
aakoshh authored Apr 10, 2024
1 parent ebb743f commit fdcca99
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 247 deletions.
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.

67 changes: 67 additions & 0 deletions fendermint/app/options/src/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2022-2024 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use std::path::PathBuf;

use crate::parse::parse_eth_address;
use clap::{Args, Subcommand};
use fvm_shared::address::Address;
use ipc_api::subnet_id::SubnetID;

#[derive(Args, Debug)]
pub struct DebugArgs {
#[command(subcommand)]
pub command: DebugCommands,
}

#[derive(Subcommand, Debug)]
pub enum DebugCommands {
/// IPC commands.
Ipc {
#[command(subcommand)]
command: DebugIpcCommands,
},
}

#[derive(Subcommand, Debug, Clone)]
pub enum DebugIpcCommands {
/// Fetch topdown events from the parent and export them to JSON.
///
/// This can be used to construct an upgrade to impute missing events.
ExportTopDownEvents(Box<DebugExportTopDownEventsArgs>),
}

#[derive(Args, Debug, Clone)]
pub struct DebugExportTopDownEventsArgs {
/// Child subnet for with the events will be fetched
#[arg(long, short)]
pub subnet_id: SubnetID,

/// Endpoint to the RPC of the child subnet's parent
#[arg(long, short)]
pub parent_endpoint: url::Url,

/// HTTP basic authentication token.
#[arg(long)]
pub parent_auth_token: Option<String>,

/// IPC gateway of the parent; 20 byte Ethereum address in 0x prefixed hex format
#[arg(long, value_parser = parse_eth_address)]
pub parent_gateway: Address,

/// IPC registry of the parent; 20 byte Ethereum address in 0x prefixed hex format
#[arg(long, value_parser = parse_eth_address)]
pub parent_registry: Address,

/// The first block to query for events.
#[arg(long)]
pub start_block_height: u64,

/// The last block to query for events.
#[arg(long)]
pub end_block_height: u64,

/// Location of the JSON file to write events to.
#[arg(long)]
pub events_file: PathBuf,
}
7 changes: 6 additions & 1 deletion fendermint/app/options/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub struct GenesisIntoTendermintArgs {
pub enum GenesisIpcCommands {
/// Set all gateway parameters.
Gateway(GenesisIpcGatewayArgs),
/// Fetch the genesis parameters of a subnet from the parent.
FromParent(Box<GenesisFromParentArgs>),
}

Expand Down Expand Up @@ -180,11 +181,15 @@ pub struct GenesisFromParentArgs {
#[arg(long, short)]
pub parent_endpoint: url::Url,

/// HTTP basic authentication token.
#[arg(long)]
pub parent_auth_token: Option<String>,

/// IPC gateway of the parent; 20 byte Ethereum address in 0x prefixed hex format
#[arg(long, value_parser = parse_eth_address, default_value = "0xff00000000000000000000000000000000000064")]
pub parent_gateway: Address,

/// IPC registry of the parent; 20 byte Ethereum address in 0x prefixed hex format
/// IPC registry of the parent; 20 byte Ethereum address in 0x prefixed hex format
#[arg(long, value_parser = parse_eth_address, default_value = "0xff00000000000000000000000000000000000065")]
pub parent_registry: Address,

Expand Down
4 changes: 4 additions & 0 deletions fendermint/app/options/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;

use clap::{Args, Parser, Subcommand};
use config::ConfigArgs;
use debug::DebugArgs;
use fvm_shared::address::Network;
use lazy_static::lazy_static;
use tracing_subscriber::EnvFilter;
Expand All @@ -15,6 +16,7 @@ use self::{
};

pub mod config;
pub mod debug;
pub mod eth;
pub mod genesis;
pub mod key;
Expand Down Expand Up @@ -181,6 +183,8 @@ impl Options {
pub enum Commands {
/// Parse the configuration file and print it to the console.
Config(ConfigArgs),
/// Arbitrary commands that aid in debugging.
Debug(DebugArgs),
/// Run the `App`, listening to ABCI requests from Tendermint.
Run(RunArgs),
/// Subcommands related to the construction of signing keys.
Expand Down
68 changes: 68 additions & 0 deletions fendermint/app/src/cmd/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2022-2024 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use anyhow::{anyhow, Context};
use fendermint_app_options::debug::{
DebugArgs, DebugCommands, DebugExportTopDownEventsArgs, DebugIpcCommands,
};
use fendermint_vm_topdown::proxy::IPCProviderProxy;
use ipc_provider::{
config::subnet::{EVMSubnet, SubnetConfig},
IpcProvider,
};

use crate::cmd;

cmd! {
DebugArgs(self) {
match &self.command {
DebugCommands::Ipc { command } => command.exec(()).await,
}
}
}

cmd! {
DebugIpcCommands(self) {
match self {
DebugIpcCommands::ExportTopDownEvents(args) =>
export_topdown_events(args).await
}
}
}

async fn export_topdown_events(args: &DebugExportTopDownEventsArgs) -> anyhow::Result<()> {
// Configuration for the child subnet on the parent network,
// based on how it's done in `run.rs` and the `genesis ipc from-parent` command.
let parent_provider = IpcProvider::new_with_subnet(
None,
ipc_provider::config::Subnet {
id: args
.subnet_id
.parent()
.ok_or_else(|| anyhow!("subnet is not a child"))?,
config: SubnetConfig::Fevm(EVMSubnet {
provider_http: args.parent_endpoint.clone(),
provider_timeout: None,
auth_token: args.parent_auth_token.clone(),
registry_addr: args.parent_registry,
gateway_addr: args.parent_gateway,
}),
},
)?;

let parent_proxy = IPCProviderProxy::new(parent_provider, args.subnet_id.clone())
.context("failed to create provider proxy")?;

let events = fendermint_vm_topdown::sync::fetch_topdown_events(
&parent_proxy,
args.start_block_height,
args.end_block_height,
)
.await
.context("failed to fetch topdown events")?;

let json = serde_json::to_string_pretty(&events)?;
std::fs::write(&args.events_file, json)?;

Ok(())
}
4 changes: 2 additions & 2 deletions fendermint/app/src/cmd/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ cmd! {
GenesisIpcCommands::Gateway(args) =>
set_ipc_gateway(&genesis_file, args),
GenesisIpcCommands::FromParent(args) =>
new_genesis_from_parent(&genesis_file, args).await
new_genesis_from_parent(&genesis_file, args).await,
}
}
}
Expand Down Expand Up @@ -294,7 +294,7 @@ async fn new_genesis_from_parent(
config: SubnetConfig::Fevm(EVMSubnet {
provider_http: args.parent_endpoint.clone(),
provider_timeout: None,
auth_token: None,
auth_token: args.parent_auth_token.clone(),
registry_addr: args.parent_registry,
gateway_addr: args.parent_gateway,
}),
Expand Down
2 changes: 2 additions & 0 deletions fendermint/app/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use anyhow::{anyhow, Context};
use async_trait::async_trait;

pub mod config;
pub mod debug;
pub mod eth;
pub mod genesis;
pub mod key;
Expand Down Expand Up @@ -62,6 +63,7 @@ macro_rules! cmd {
pub async fn exec(opts: &Options) -> anyhow::Result<()> {
match &opts.command {
Commands::Config(args) => args.exec(settings(opts)?).await,
Commands::Debug(args) => args.exec(()).await,
Commands::Run(args) => args.exec(settings(opts)?).await,
Commands::Key(args) => args.exec(()).await,
Commands::Genesis(args) => args.exec(()).await,
Expand Down
2 changes: 2 additions & 0 deletions fendermint/vm/topdown/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use std::time::Duration;

use fendermint_vm_genesis::{Power, Validator};

pub use syncer::fetch_topdown_events;

/// Query the parent finality from the block chain state.
///
/// It returns `None` from queries until the ledger has been initialized.
Expand Down
113 changes: 79 additions & 34 deletions fendermint/vm/topdown/src/sync/syncer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
use anyhow::anyhow;
use async_stm::{atomically, atomically_or_err, StmError};
use ethers::utils::hex;
use libp2p::futures::TryFutureExt;
use std::sync::Arc;
use tracing::instrument;

Expand Down Expand Up @@ -266,45 +267,12 @@ where
Ok(data.0)
}

#[instrument(skip(self))]
async fn fetch_data(
&self,
height: BlockHeight,
block_hash: BlockHash,
) -> Result<ParentViewPayload, Error> {
let changes_res = self
.parent_proxy
.get_validator_changes(height)
.await
.map_err(|e| Error::CannotQueryParent(format!("get_validator_changes: {e}"), height))?;

if changes_res.block_hash != block_hash {
tracing::warn!(
height,
change_set_hash = hex::encode(&changes_res.block_hash),
block_hash = hex::encode(&block_hash),
"change set block hash does not equal block hash",
);
return Err(Error::ParentChainReorgDetected);
}

let topdown_msgs_res = self
.parent_proxy
.get_top_down_msgs(height)
.await
.map_err(|e| Error::CannotQueryParent(format!("get_top_down_msgs: {e}"), height))?;

if topdown_msgs_res.block_hash != block_hash {
tracing::warn!(
height,
topdown_msgs_hash = hex::encode(&topdown_msgs_res.block_hash),
block_hash = hex::encode(&block_hash),
"topdown messages block hash does not equal block hash",
);
return Err(Error::ParentChainReorgDetected);
}

Ok((block_hash, changes_res.value, topdown_msgs_res.value))
fetch_data(self.parent_proxy.as_ref(), height, block_hash).await
}

async fn finalized_chain_head(&self) -> anyhow::Result<Option<BlockHeight>> {
Expand Down Expand Up @@ -342,6 +310,83 @@ fn map_voting_err(e: StmError<voting::Error>) -> StmError<Error> {
}
}

#[instrument(skip(parent_proxy))]
async fn fetch_data<P>(
parent_proxy: &P,
height: BlockHeight,
block_hash: BlockHash,
) -> Result<ParentViewPayload, Error>
where
P: ParentQueryProxy + Send + Sync + 'static,
{
let changes_res = parent_proxy
.get_validator_changes(height)
.map_err(|e| Error::CannotQueryParent(format!("get_validator_changes: {e}"), height));

let topdown_msgs_res = parent_proxy
.get_top_down_msgs(height)
.map_err(|e| Error::CannotQueryParent(format!("get_top_down_msgs: {e}"), height));

let (changes_res, topdown_msgs_res) = tokio::join!(changes_res, topdown_msgs_res);
let (changes_res, topdown_msgs_res) = (changes_res?, topdown_msgs_res?);

if changes_res.block_hash != block_hash {
tracing::warn!(
height,
change_set_hash = hex::encode(&changes_res.block_hash),
block_hash = hex::encode(&block_hash),
"change set block hash does not equal block hash",
);
return Err(Error::ParentChainReorgDetected);
}

if topdown_msgs_res.block_hash != block_hash {
tracing::warn!(
height,
topdown_msgs_hash = hex::encode(&topdown_msgs_res.block_hash),
block_hash = hex::encode(&block_hash),
"topdown messages block hash does not equal block hash",
);
return Err(Error::ParentChainReorgDetected);
}

Ok((block_hash, changes_res.value, topdown_msgs_res.value))
}

pub async fn fetch_topdown_events<P>(
parent_proxy: &P,
start_height: BlockHeight,
end_height: BlockHeight,
) -> Result<Vec<(BlockHeight, ParentViewPayload)>, Error>
where
P: ParentQueryProxy + Send + Sync + 'static,
{
let mut events = Vec::new();
for height in start_height..=end_height {
match parent_proxy.get_block_hash(height).await {
Ok(res) => {
let (block_hash, changes, msgs) =
fetch_data(parent_proxy, height, res.block_hash).await?;

if !(changes.is_empty() && msgs.is_empty()) {
events.push((height, (block_hash, changes, msgs)));
}
}
Err(e) => {
if is_null_round_str(&e.to_string()) {
continue;
} else {
return Err(Error::CannotQueryParent(
format!("get_block_hash: {e}"),
height,
));
}
}
}
}
Ok(events)
}

#[cfg(test)]
mod tests {
use crate::proxy::ParentQueryProxy;
Expand Down
Loading

0 comments on commit fdcca99

Please sign in to comment.