diff --git a/relayer-cli/src/commands/light/add.rs b/relayer-cli/src/commands/light/add.rs index 49c2934e18..73cc36b361 100644 --- a/relayer-cli/src/commands/light/add.rs +++ b/relayer-cli/src/commands/light/add.rs @@ -4,6 +4,7 @@ use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, use ibc::ics24_host::identifier::ChainId; use relayer::{ + config, config::{Config, LightClientConfig, PeersConfig}, util::block_on, }; @@ -183,6 +184,7 @@ fn update_config( let light_client_config = LightClientConfig { peer_id: status.peer_id, address: status.address.clone(), + timeout: config::default::timeout(), trusted_header_hash: status.latest_hash, trusted_height: status.latest_height, }; diff --git a/relayer-cli/src/commands/start.rs b/relayer-cli/src/commands/start.rs index 2d65f50bc4..5659f94433 100644 --- a/relayer-cli/src/commands/start.rs +++ b/relayer-cli/src/commands/start.rs @@ -1,12 +1,10 @@ use std::ops::Deref; -use abscissa_core::{ - application::fatal_error, error::BoxError, tracing::info, Command, Options, Runnable, -}; +use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, Runnable}; -use relayer::{chain::CosmosSDKChain, config::Config}; +use relayer::config::Config; -use crate::{application::APPLICATION, prelude::*, tasks}; +use crate::{application::APPLICATION, prelude::*}; #[derive(Command, Debug, Options)] pub struct StartCmd { @@ -33,29 +31,5 @@ impl Runnable for StartCmd { } async fn start(config: Config, reset: bool) -> Result<(), BoxError> { - let mut chains: Vec = vec![]; - - for chain_config in &config.chains { - let light_config = chain_config.primary().ok_or_else(|| { - format!( - "could not find light client configuration for chain {}", - chain_config.id - ) - })?; - - info!(chain.id = %chain_config.id, "spawning light client"); - - let mut chain = CosmosSDKChain::from_config(chain_config.clone())?; - - let client_task = - tasks::light_client::create(&mut chain, light_config.clone(), reset).await?; - - chains.push(chain); - - let _handle = tokio::task::spawn(client_task); - } - - tasks::relayer::start(&config, chains).await?; - - Ok(()) + todo!() // TODO: Move v0 command here } diff --git a/relayer-cli/src/commands/v0.rs b/relayer-cli/src/commands/v0.rs index 9b1c664c82..65ae070c20 100644 --- a/relayer-cli/src/commands/v0.rs +++ b/relayer-cli/src/commands/v0.rs @@ -9,6 +9,7 @@ use relayer::{ channel::{Channel, ChannelConfig}, connection::{Connection, ConnectionConfig}, foreign_client::{ForeignClient, ForeignClientConfig}, + light_client::tendermint::TendermintLightClient, link::{Link, LinkConfig}, }; @@ -60,16 +61,23 @@ pub fn v0_task(config: Config) -> Result<(), BoxError> { dst_chain_config .client_ids .get(0) - .ok_or_else(|| "Config for client for dest. chain not found")?, + .ok_or_else(|| "Config for client for destination chain not found")?, ) .map_err(|e| format!("Error validating client identifier for dst chain ({:?})", e))?; + let (src_light_client, src_supervisor) = + TendermintLightClient::from_config(&src_chain_config, true)?; + let (dst_light_client, dst_supervisor) = + TendermintLightClient::from_config(&dst_chain_config, true)?; + + thread::spawn(move || src_supervisor.run().unwrap()); + thread::spawn(move || dst_supervisor.run().unwrap()); + let src_chain = ChainRuntime::cosmos_sdk(src_chain_config)?; let dst_chain = ChainRuntime::cosmos_sdk(dst_chain_config)?; let src_chain_handle = src_chain.handle(); thread::spawn(move || { - // TODO: What should we do on return here? Probably unrecoverable error. src_chain.run().unwrap(); }); @@ -97,16 +105,14 @@ pub fn v0_task(config: Config) -> Result<(), BoxError> { &dst_chain_handle, &client_on_src, // Semantic dependency. ConnectionConfig::new(todo!(), todo!()), - ) - .unwrap(); + )?; let channel = Channel::new( &src_chain_handle, &dst_chain_handle, connection, // Semantic dependecy ChannelConfig::new(todo!(), todo!()), - ) - .unwrap(); + )?; let link = Link::new( src_chain_handle, diff --git a/relayer-cli/src/tasks.rs b/relayer-cli/src/tasks.rs index f8f3247d2b..0d116c0fb9 100644 --- a/relayer-cli/src/tasks.rs +++ b/relayer-cli/src/tasks.rs @@ -1,5 +1,3 @@ //! Tasks which make up the relayer application pub mod event_listener; -pub mod light_client; -pub mod relayer; diff --git a/relayer-cli/src/tasks/light_client.rs b/relayer-cli/src/tasks/light_client.rs deleted file mode 100644 index cbe9dae30a..0000000000 --- a/relayer-cli/src/tasks/light_client.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Task for initializing and spawning a light client for a given chain - -use std::time::Duration; - -use abscissa_core::{ - error::BoxError, - tracing::{debug, info}, -}; - -use futures::Future; -use ibc::ics24_host::identifier::ChainId; -use tendermint_light_client::{ - builder::{LightClientBuilder, SupervisorBuilder}, - light_client, store, - supervisor::{self, Handle, Supervisor, SupervisorHandle}, - types::PeerId, -}; - -use relayer::{ - chain::{Chain, CosmosSDKChain}, - client, - config::LightClientConfig, -}; - -use crate::prelude::*; - -/// Initialize a light client for a given chain. -/// -/// This function returns an async task which can be spawned -/// to start the light client and have it sync up to the latest -/// block of the chain. -/// -/// If the `reset` parameter is set to `true`, then the -/// light client will be initialized with the given trust options, -/// otherwise it will resume from the latest trusted block in the store. -pub async fn create( - chain: &mut CosmosSDKChain, - config: LightClientConfig, - reset: bool, -) -> Result>, BoxError> { - status_info!( - "Relayer", - "spawning light client for chain {}", - chain.config().id, - ); - - let supervisor = create_client(chain, config, reset).await?; - let handle = supervisor.handle(); - let thread_handle = std::thread::spawn(|| supervisor.run()); - let task = client_task(chain.id().clone(), handle.clone()); - - let light_client = client::tendermint::LightClient::new(handle); - chain.set_light_client(light_client); - - Ok(task) -} - -fn build_instance( - peer_id: PeerId, - chain: &CosmosSDKChain, - store: store::sled::SledStore, - options: light_client::Options, - config: LightClientConfig, - reset: bool, -) -> Result { - let builder = LightClientBuilder::prod( - peer_id, - chain.rpc_client().clone(), - Box::new(store), - options, - Some(Duration::from_secs(10)), // TODO: Make configurable - ); - - let builder = if reset { - info!(chain.id = %chain.id(), "resetting client to trust options state"); - builder.trust_primary_at(config.trusted_height, config.trusted_header_hash)? - } else { - info!(chain.id = %chain.id(), "starting client from stored trusted state"); - builder.trust_from_store()? - }; - - Ok(builder.build()) -} - -async fn create_client( - chain: &mut CosmosSDKChain, - config: LightClientConfig, - reset: bool, -) -> Result { - let chain_config = chain.config(); - let id = chain_config.id.clone(); - - let db_path = format!("store_{}.db", chain.id()); - let store = store::sled::SledStore::new(sled::open(db_path)?); - - // FIXME: Remove or make configurable - let primary_peer_id: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap(); - let witness_peer_id: PeerId = "DC0C0ADEADBEEFC0FFEEFACADEBADFADAD0BEFEE".parse().unwrap(); - - let options = light_client::Options { - trust_threshold: chain_config.trust_threshold, - trusting_period: chain_config.trusting_period, - clock_drift: chain_config.clock_drift, - }; - - let primary = build_instance( - primary_peer_id, - &chain, - store.clone(), - options, - config.clone(), - reset, - )?; - - let witness = build_instance(witness_peer_id, &chain, store, options, config, reset)?; - - let supervisor = SupervisorBuilder::new() - .primary(primary_peer_id, chain_config.rpc_addr.clone(), primary) - .witness(witness_peer_id, chain_config.rpc_addr.clone(), witness) - .build_prod(); - - Ok(supervisor) -} - -async fn client_task(chain_id: ChainId, handle: SupervisorHandle) -> Result<(), BoxError> { - match handle.latest_trusted() { - Ok(Some(trusted_state)) => { - info!( - chain.id = %chain_id, - "spawned new client now at trusted state: {} at height {}", - trusted_state.signed_header.header.hash(), - trusted_state.signed_header.header.height, - ); - - update_client(chain_id, handle).await?; - } - Ok(None) => { - error!( - chain.id = %chain_id, - "no initial trusted state, aborting" - ); - } - Err(e) => { - error!( - chain.id = %chain_id, - "error getting latest trusted state: {}", e - ); - } - } - - Ok(()) -} - -async fn update_client(chain_id: ChainId, handle0: SupervisorHandle) -> Result<(), BoxError> { - debug!(chain.id = %chain_id, "updating headers"); - - let mut interval = tokio::time::interval(Duration::from_secs(3)); - - loop { - interval.tick().await; - - info!(chain.id = %chain_id, "updating client"); - - let handle = handle0.clone(); - let result = tokio::task::spawn_blocking(move || handle.verify_to_highest()).await?; - - match result { - Ok(trusted_state) => info!( - chain.id = %chain_id, - "client updated to trusted state: {} at height {}", - trusted_state.signed_header.header.hash(), - trusted_state.signed_header.header.height - ), - - Err(err) => error!(chain.id = %chain_id, "error when updating headers: {}", err), - } - } -} diff --git a/relayer-cli/src/tasks/relayer.rs b/relayer-cli/src/tasks/relayer.rs deleted file mode 100644 index f737cab1ea..0000000000 --- a/relayer-cli/src/tasks/relayer.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Task for starting the relayer - -use std::time::Duration; - -use abscissa_core::tracing::info; - -use relayer::{ - chain::{Chain, CosmosSDKChain}, - client::LightClient, - config::Config, -}; - -use crate::prelude::*; - -/// Start the relayer with the given config. -/// -/// **Note:** The relayer loop is currently a no-op. -pub async fn start(config: &Config, chains: Vec) -> Result<(), BoxError> { - for chain in &chains { - let light_client = chain.light_client().ok_or_else(|| { - format!( - "light client for chain {} has not been initialized", - chain.id() - ) - })?; - - if let Some(latest_trusted) = light_client.latest_trusted().await? { - info!( - chain.id = %chain.id(), - "latest trusted state is at height {:?}", - latest_trusted.height(), - ); - } else { - warn!( - chain.id = %chain.id(), - "no latest trusted state", - ); - } - } - - let mut interval = tokio::time::interval(Duration::from_secs(2)); - - loop { - interval.tick().await; - } -} diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index c9cf86b418..0adce73a42 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -26,21 +26,16 @@ use ibc::{ use crate::config::ChainConfig; use crate::error::Error; +use crate::error::Kind; use crate::keyring::store::{KeyEntry, KeyRing}; +use crate::light_client::LightClient; use crate::util::block_on; -use crate::{client::LightClient, error::Kind}; /// Defines a blockchain as understood by the relayer pub trait Chain { /// Type of headers for this chain type Header: Send + Sync + Serialize + DeserializeOwned; - /// Type of light blocks for this chain - type LightBlock: Send + Sync + Serialize + DeserializeOwned; - - /// Type of light client for this chain - type LightClient: LightClient + Send + Sync; - /// Type of consensus state for this chain type ConsensusState: ConsensusState + Send + Sync + Serialize + DeserializeOwned; @@ -61,12 +56,6 @@ pub trait Chain { /// Get a low-level RPC client for this chain fn rpc_client(&self) -> &Self::RpcClient; - /// Get a light client for this chain - fn light_client(&self) -> Option<&Self::LightClient>; - - /// Set a light client for this chain - fn set_light_client(&mut self, light_client: Self::LightClient); - /// The unbonding period of this chain /// TODO - this is a GRPC query, needs to be implemented fn unbonding_period(&self) -> Duration; diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index c025d5c3d4..3175d8877e 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -28,23 +28,22 @@ use ibc_proto::cosmos::{ use tendermint::abci::{Path as ABCIPath, Transaction}; use tendermint::block::Height; -use tendermint_light_client::types::{LightBlock, ValidatorSet}; -use tendermint_light_client::types::{SignedHeader, TrustThreshold}; +use tendermint_light_client::types::{SignedHeader, TrustThreshold, ValidatorSet}; use tendermint_rpc::Client; use tendermint_rpc::HttpClient; use super::Chain; -use crate::client::tendermint::LightClient; use crate::config::ChainConfig; use crate::error; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRing, KeyRingOperations, StoreBackend}; +use crate::light_client::tendermint::TendermintLightClient; use crate::util::block_on; pub struct CosmosSDKChain { config: ChainConfig, rpc_client: HttpClient, - light_client: Option, + light_client: Option, pub keybase: KeyRing, } @@ -103,8 +102,6 @@ impl CosmosSDKChain { impl Chain for CosmosSDKChain { type Header = SignedHeader; - type LightBlock = LightBlock; - type LightClient = LightClient; type RpcClient = HttpClient; type ConsensusState = ConsensusState; type ClientState = ClientState; @@ -241,14 +238,6 @@ impl Chain for CosmosSDKChain { &self.rpc_client } - fn set_light_client(&mut self, light_client: LightClient) { - self.light_client = Some(light_client); - } - - fn light_client(&self) -> Option<&LightClient> { - self.light_client.as_ref() - } - fn trust_threshold(&self) -> TrustThreshold { TrustThreshold::default() } @@ -268,16 +257,6 @@ impl Chain for CosmosSDKChain { let signed_header = fetch_signed_header(client, height)?; assert_eq!(height, signed_header.header.height); - // let validator_set = fetch_validator_set(client, height)?; - // let next_validator_set = fetch_validator_set(client, height.increment())?; - - // let light_block = LightBlock::new( - // signed_header, - // validator_set, - // next_validator_set, - // primary.peer_id, - // ); - Ok(signed_header) } @@ -355,12 +334,3 @@ async fn broadcast_tx( Ok(response.data.as_bytes().to_vec()) } - -fn fetch_validator_set(client: &HttpClient, height: Height) -> Result { - let res = block_on(client.validators(height)); - - match res { - Ok(response) => Ok(ValidatorSet::new(response.validators)), - Err(err) => Err(Kind::Rpc.context(err).into()), - } -} diff --git a/relayer/src/client/tendermint.rs b/relayer/src/client/tendermint.rs deleted file mode 100644 index d7baa6656f..0000000000 --- a/relayer/src/client/tendermint.rs +++ /dev/null @@ -1,66 +0,0 @@ -use async_trait::async_trait; -use std::sync::Arc; -use tokio::task::spawn_blocking; - -use tendermint::block::Height; -use tendermint_light_client::supervisor::{Handle, SupervisorHandle}; -use tendermint_light_client::types::LightBlock; - -use crate::error; - -pub struct LightClient { - handle: SupervisorHandle, -} - -impl LightClient { - pub fn new(handle: SupervisorHandle) -> Self { - Self { handle } - } -} - -#[async_trait] -impl super::LightClient for LightClient { - async fn latest_trusted(&self) -> Result, error::Error> { - let handle = self.handle.clone(); - - spawn_blocking(move || { - handle - .latest_trusted() - .map_err(|e| error::Kind::LightClient.context(e).into()) - }) - .await - .expect("task failed to execute to completion") - } - - async fn verify_to_latest(&self) -> Result { - let handle = self.handle.clone(); - - spawn_blocking(move || { - handle - .verify_to_highest() - .map_err(|e| error::Kind::LightClient.context(e).into()) - }) - .await - .expect("task failed to execute to completion") - } - - async fn verify_to_target(&self, height: Height) -> Result { - let handle = self.handle.clone(); - - spawn_blocking(move || { - handle - .verify_to_target(height) - .map_err(|e| error::Kind::LightClient.context(e).into()) - }) - .await - .expect("task failed to execute to completion") - } - - async fn get_minimal_set( - &self, - latest_client_state_height: Height, - target_height: Height, - ) -> Result, error::Error> { - todo!() - } -} diff --git a/relayer/src/config.rs b/relayer/src/config.rs index a31b88ce62..d1c4e094ae 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -112,6 +112,18 @@ impl ChainConfig { let peers = self.peers.as_ref()?; peers.light_client(id) } + + pub fn witnesses(&self) -> Option> { + let peers = self.peers.as_ref()?; + + Some( + peers + .light_clients + .iter() + .filter(|p| p.peer_id != peers.primary) + .collect(), + ) + } } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -172,6 +184,8 @@ impl PeersConfig { pub struct LightClientConfig { pub peer_id: PeerId, pub address: net::Address, + #[serde(default = "default::timeout", with = "humantime_serde")] + pub timeout: Duration, pub trusted_header_hash: Hash, pub trusted_height: Height, } diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index 44052c3b57..82aca22d9d 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -15,7 +15,6 @@ pub mod auth; pub mod chain; pub mod channel; -pub mod client; pub mod config; pub mod connection; pub mod error; @@ -24,6 +23,7 @@ pub mod event_monitor; pub mod foreign_client; pub mod keyring; pub mod keys; +pub mod light_client; pub mod link; pub mod msgs; pub mod tx; diff --git a/relayer/src/client.rs b/relayer/src/light_client.rs similarity index 74% rename from relayer/src/client.rs rename to relayer/src/light_client.rs index 4af3ddd2a7..d8eb86ac49 100644 --- a/relayer/src/client.rs +++ b/relayer/src/light_client.rs @@ -2,7 +2,6 @@ use std::cmp::Ordering; use std::time::{Duration, SystemTime}; use anomaly::fail; -use async_trait::async_trait; use tracing::{debug, info, warn}; use tendermint_light_client::supervisor::Handle; @@ -16,20 +15,19 @@ use ::tendermint::block::Height; pub mod tendermint; /// Defines a client from the point of view of the relayer. -#[async_trait] pub trait LightClient { /// Get the latest trusted state of the light client - async fn latest_trusted(&self) -> Result, error::Error>; + fn latest_trusted(&self) -> Result, error::Error>; /// Fetch and verify the latest header from the chain - async fn verify_to_latest(&self) -> Result; + fn verify_to_latest(&self) -> Result; /// Fetch and verify the header from the chain at the given height - async fn verify_to_target(&self, height: Height) -> Result; + fn verify_to_target(&self, height: Height) -> Result; /// Compute the minimal ordered set of heights needed to update the light /// client state from from `latest_client_state_height` to `target_height`. - async fn get_minimal_set( + fn get_minimal_set( &self, latest_client_state_height: Height, target_height: Height, diff --git a/relayer/src/light_client/tendermint.rs b/relayer/src/light_client/tendermint.rs new file mode 100644 index 0000000000..7129ad0046 --- /dev/null +++ b/relayer/src/light_client/tendermint.rs @@ -0,0 +1,139 @@ +use anomaly::BoxError; +use async_trait::async_trait; +use humantime_serde::re::humantime::Duration; +use std::sync::Arc; +use tokio::task::spawn_blocking; + +use tendermint::block::Height; +use tendermint_light_client::{ + builder::LightClientBuilder, + builder::SupervisorBuilder, + light_client, store, supervisor, + supervisor::Supervisor, + supervisor::{Handle, SupervisorHandle}, + types::LightBlock, + types::PeerId, +}; +use tendermint_rpc as rpc; + +use crate::{ + chain::CosmosSDKChain, + config::{ChainConfig, LightClientConfig}, + error, +}; + +pub struct TendermintLightClient { + handle: Box, +} + +impl super::LightClient for TendermintLightClient { + fn latest_trusted(&self) -> Result, error::Error> { + self.handle + .latest_trusted() + .map_err(|e| error::Kind::LightClient.context(e).into()) + } + + fn verify_to_latest(&self) -> Result { + self.handle + .verify_to_highest() + .map_err(|e| error::Kind::LightClient.context(e).into()) + } + + fn verify_to_target(&self, height: Height) -> Result { + self.handle + .verify_to_target(height) + .map_err(|e| error::Kind::LightClient.context(e).into()) + } + + fn get_minimal_set( + &self, + latest_client_state_height: Height, + target_height: Height, + ) -> Result, error::Error> { + todo!() + } +} + +impl TendermintLightClient { + pub fn new(handle: impl Handle + 'static) -> Self { + Self { + handle: Box::new(handle), + } + } + + pub fn from_config( + chain_config: &ChainConfig, + reset: bool, + ) -> Result<(Self, Supervisor), error::Error> { + let supervisor = build_supervisor(&chain_config, reset)?; + let handle = supervisor.handle(); + + Ok((Self::new(handle), supervisor)) + } +} + +fn build_instance( + config: &LightClientConfig, + options: light_client::Options, + reset: bool, +) -> Result { + let rpc_client = rpc::HttpClient::new(config.address.clone()) + .map_err(|e| error::Kind::LightClient.context(e))?; + + let store = store::sled::SledStore::new(todo!()); + + let builder = LightClientBuilder::prod( + config.peer_id, + rpc_client, + Box::new(store), + options, + Some(config.timeout), + ); + + let builder = if reset { + builder.trust_primary_at(config.trusted_height, config.trusted_header_hash) + } else { + builder.trust_from_store() + } + .map_err(|e| error::Kind::LightClient.context(e))?; + + Ok(builder.build()) +} + +fn build_supervisor(config: &ChainConfig, reset: bool) -> Result { + let id = config.id.clone(); + + let options = light_client::Options { + trust_threshold: config.trust_threshold, + trusting_period: config.trusting_period, + clock_drift: config.clock_drift, + }; + + let primary_config = config.primary().ok_or_else(|| { + error::Kind::LightClient.context("missing light client primary peer config") + })?; + + let witnesses_configs = config.witnesses().ok_or_else(|| { + error::Kind::LightClient.context("missing light client witnesses peer config") + })?; + + let primary = build_instance(primary_config, options, reset)?; + + let mut witnesses = Vec::with_capacity(witnesses_configs.len()); + for conf in witnesses_configs { + let instance = build_instance(conf, options, reset)?; + witnesses.push((conf.peer_id, conf.address.clone(), instance)); + } + + let supervisor = SupervisorBuilder::new() + .primary( + primary_config.peer_id, + primary_config.address.clone(), + primary, + ) + .witnesses(witnesses) + .map_err(|e| error::Kind::LightClient.context(e))? + .build_prod(); + + Ok(supervisor) +}