diff --git a/src/ibc_relayer_cli/commands/evidence.rs.html b/src/ibc_relayer_cli/commands/evidence.rs.html index b7edf5731344..3bece29f7250 100644 --- a/src/ibc_relayer_cli/commands/evidence.rs.html +++ b/src/ibc_relayer_cli/commands/evidence.rs.html @@ -776,15 +776,90 @@ 776 777 778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853
use std::collections::HashMap;
-use std::ops::Deref;
+use std::ops::{ControlFlow, Deref};
use std::sync::Arc;
use std::thread::sleep;
use std::time::Duration;
use abscissa_core::clap::Parser;
use abscissa_core::{Command, Runnable};
-use ibc_relayer::config::ChainConfig;
+use ibc_relayer::config::{ChainConfig, Config};
use tokio::runtime::Runtime as TokioRuntime;
use tendermint::block::Height as TendermintHeight;
@@ -874,7 +949,13 @@
);
let chain = CosmosSdkChain::bootstrap(chain_config, rt.clone()).unwrap();
- let res = monitor_misbehaviours(rt, chain, self.key_name.as_ref(), self.check_past_blocks);
+ let res = monitor_misbehaviours(
+ rt,
+ &config,
+ chain,
+ self.key_name.as_ref(),
+ self.check_past_blocks,
+ );
match res {
Ok(()) => Output::success(()).exit(),
@@ -885,6 +966,7 @@
fn monitor_misbehaviours(
rt: Arc<TokioRuntime>,
+ config: &Config,
mut chain: CosmosSdkChain,
key_name: Option<&String>,
check_past_blocks: u64,
@@ -914,7 +996,7 @@
while height >= target_height {
debug!("checking for evidence at height {height}");
- if let Err(e) = check_misbehaviour_at(rt.clone(), &chain, key_name, height) {
+ if let Err(e) = check_misbehaviour_at(rt.clone(), config, &chain, key_name, height) {
warn!("error while checking for misbehaviour at height {height}: {e}");
}
@@ -937,9 +1019,13 @@
if let IbcEvent::NewBlock(new_block) = &event_with_height.event {
info!("checking for evidence at height {}", new_block.height);
- if let Err(e) =
- check_misbehaviour_at(rt.clone(), &chain, key_name, new_block.height)
- {
+ if let Err(e) = check_misbehaviour_at(
+ rt.clone(),
+ config,
+ &chain,
+ key_name,
+ new_block.height,
+ ) {
error!(
"error while checking for misbehaviour at height {}: {e}",
new_block.height
@@ -962,6 +1048,7 @@
/// clients of the chain, freezing them.
fn check_misbehaviour_at(
rt: Arc<TokioRuntime>,
+ config: &Config,
chain: &CosmosSdkChain,
key_name: Option<&String>,
height: Height,
@@ -976,13 +1063,13 @@
warn!("found duplicate vote evidence");
trace!("{dv:#?}");
- handle_duplicate_vote(rt.clone(), chain, key_name, *dv)?;
+ handle_duplicate_vote(rt.clone(), config, chain, key_name, *dv)?;
}
tendermint::evidence::Evidence::LightClientAttack(lc) => {
warn!("found light client attack evidence");
trace!("{lc:#?}");
- handle_light_client_attack(rt.clone(), chain, key_name, *lc)?;
+ handle_light_client_attack(rt.clone(), config, chain, key_name, *lc)?;
}
}
}
@@ -990,114 +1077,167 @@
Ok(())
}
+fn spawn_runtime(
+ rt: Arc<TokioRuntime>,
+ config: &Config,
+ cache: &mut HashMap<ChainId, BaseChainHandle>,
+ chain_id: &ChainId,
+ key_name: Option<&String>,
+) -> eyre::Result<BaseChainHandle> {
+ if !cache.contains_key(chain_id) {
+ let chain_handle = spawn_chain_runtime_with_modified_config::<BaseChainHandle>(
+ config,
+ chain_id,
+ rt,
+ |chain_config| {
+ if let Some(key_name) = key_name {
+ chain_config.set_key_name(key_name.to_string());
+ }
+ },
+ )?;
+
+ cache.insert(chain_id.clone(), chain_handle);
+ }
+
+ Ok(cache
+ .get(chain_id)
+ .expect("chain handle was either already there or we just inserted it")
+ .clone())
+}
+
fn handle_duplicate_vote(
rt: Arc<TokioRuntime>,
+ config: &Config,
chain: &CosmosSdkChain,
key_name: Option<&String>,
- dv: DuplicateVoteEvidence,
+ evidence: DuplicateVoteEvidence,
) -> eyre::Result<()> {
- use ibc_relayer::chain::requests::QueryConsensusStateHeightsRequest;
-
- let config = app_config();
-
// Fetch all the counterparty clients of this chain.
- let counterparty_clients = fetch_all_counterparty_clients(chain)?;
+ let counterparty_clients = fetch_all_counterparty_clients(config, chain)?;
- let mut chains = HashMap::new();
+ // Cache for the chain handles
+ let mut chains = HashMap::new();
// For each counterparty client, build the double voting evidence and submit it to the chain,
// freezing that client.
for (counterparty_chain_id, counterparty_client_id) in counterparty_clients {
- if !chains.contains_key(&counterparty_chain_id) {
- let chain_handle = spawn_chain_runtime_with_modified_config::<BaseChainHandle>(
- &config,
- &counterparty_chain_id,
- rt.clone(),
- |chain_config| {
- if let Some(key_name) = key_name {
- chain_config.set_key_name(key_name.to_string());
- }
- },
- )?;
-
- chains.insert(counterparty_chain_id.clone(), chain_handle);
+ let counterparty_chain_handle = match spawn_runtime(
+ rt.clone(),
+ config,
+ &mut chains,
+ &counterparty_chain_id,
+ key_name,
+ ) {
+ Ok(chain_handle) => chain_handle,
+ Err(e) => {
+ error!("failed to spawn runtime for chain `{counterparty_chain_id}`: {e}");
+ continue;
+ }
};
- let counterparty_chain_handle = chains.get(&counterparty_chain_id).unwrap();
+ let next = submit_duplicate_vote_evidence(
+ &rt,
+ chain,
+ &counterparty_chain_handle,
+ &counterparty_chain_id,
+ &counterparty_client_id,
+ &evidence,
+ );
- let signer = counterparty_chain_handle.get_signer()?;
+ match next {
+ Ok(ControlFlow::Continue(())) => continue,
+ Ok(ControlFlow::Break(())) => break,
+ Err(e) => {
+ error!(
+ "failed to report double voting evidence to chain `{counterparty_chain_id}`: {e}"
+ );
- if !is_counterparty_provider(chain, counterparty_chain_handle, &counterparty_client_id) {
- debug!("counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` is not a CCV client, skipping...");
- continue;
+ continue;
+ }
}
+ }
- let infraction_height = dv.vote_a.height;
-
- // Get the trusted height in the same way we do for client updates,
- // ie. retrieve the consensus state at the highest height smaller than the infraction height.
- //
- // Note: The consensus state heights are sorted in increasing order.
- let consensus_state_heights =
- chain.query_consensus_state_heights(QueryConsensusStateHeightsRequest {
- client_id: counterparty_client_id.clone(),
- pagination: Some(PageRequest::all()),
- })?;
-
- // Retrieve the consensus state at the highest height smaller than the infraction height.
- let consensus_state_height_before_infraction_height = consensus_state_heights
- .into_iter()
- .filter(|height| height.revision_height() < infraction_height.value())
- .last();
-
- let Some(trusted_height) = consensus_state_height_before_infraction_height else {
- error!(
- "cannot build infraction block header for client `{counterparty_client_id}` on chain `{counterparty_chain_id}`,\
- reason: could not find consensus state at highest height smaller than infraction height {infraction_height}"
- );
+ Ok(())
+}
- continue;
- };
+fn submit_duplicate_vote_evidence(
+ rt: &TokioRuntime,
+ chain: &CosmosSdkChain,
+ counterparty_chain_handle: &BaseChainHandle,
+ counterparty_chain_id: &ChainId,
+ counterparty_client_id: &ClientId,
+ evidence: &DuplicateVoteEvidence,
+) -> eyre::Result<ControlFlow<()>> {
+ use ibc_relayer::chain::requests::QueryConsensusStateHeightsRequest;
- // Construct the light client block header for the consumer chain at the infraction height
- let infraction_block_header =
- fetch_infraction_block_header(&rt, chain, infraction_height, trusted_height)?;
+ let signer = counterparty_chain_handle.get_signer()?;
- let submit_msg = MsgSubmitIcsConsumerDoubleVoting {
- submitter: signer.clone(),
- duplicate_vote_evidence: dv.clone(),
- infraction_block_header,
- }
- .to_any();
+ if !is_counterparty_provider(chain, counterparty_chain_handle, counterparty_client_id) {
+ debug!("counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` is not a CCV client, skipping...");
+ return Ok(ControlFlow::Continue(()));
+ }
- info!(
- "submitting consumer double voting evidence to provider chain `{counterparty_chain_id}`"
+ let infraction_height = evidence.vote_a.height;
+
+ // Get the trusted height in the same way we do for client updates,
+ // ie. retrieve the consensus state at the highest height smaller than the infraction height.
+ //
+ // Note: The consensus state heights are sorted in increasing order.
+ let consensus_state_heights =
+ chain.query_consensus_state_heights(QueryConsensusStateHeightsRequest {
+ client_id: counterparty_client_id.clone(),
+ pagination: Some(PageRequest::all()),
+ })?;
+
+ // Retrieve the consensus state at the highest height smaller than the infraction height.
+ let consensus_state_height_before_infraction_height = consensus_state_heights
+ .into_iter()
+ .filter(|height| height.revision_height() < infraction_height.value())
+ .last();
+
+ let Some(trusted_height) = consensus_state_height_before_infraction_height else {
+ error!(
+ "cannot build infraction block header for client `{counterparty_client_id}` on chain `{counterparty_chain_id}`,\
+ reason: could not find consensus state at highest height smaller than infraction height {infraction_height}"
);
- let tracked_msgs = TrackedMsgs::new_static(vec![submit_msg], "double_voting_evidence");
- let responses = counterparty_chain_handle.send_messages_and_wait_check_tx(tracked_msgs)?;
+ return Ok(ControlFlow::Continue(()));
+ };
- for response in responses {
- if response.code.is_ok() {
- info!("successfully submitted double voting evidence to chain `{counterparty_chain_id}`, tx hash: {}", response.hash);
- } else {
- error!(
- "failed to submit double voting evidence to chain `{counterparty_chain_id}`: {response:?}"
- );
- }
- }
+ // Construct the light client block header for the consumer chain at the infraction height
+ let infraction_block_header =
+ fetch_infraction_block_header(rt, chain, infraction_height, trusted_height)?;
- // We have submitted the evidence to the provider, and because there can only be a single
- // provider for a consumer chain, we can stop now. No need to check all the other
- // counteparties.
- break;
+ let submit_msg = MsgSubmitIcsConsumerDoubleVoting {
+ submitter: signer.clone(),
+ duplicate_vote_evidence: evidence.clone(),
+ infraction_block_header,
}
+ .to_any();
- Ok(())
+ info!("submitting consumer double voting evidence to provider chain `{counterparty_chain_id}`");
+
+ let tracked_msgs = TrackedMsgs::new_static(vec![submit_msg], "double_voting_evidence");
+ let responses = counterparty_chain_handle.send_messages_and_wait_check_tx(tracked_msgs)?;
+
+ for response in responses {
+ if response.code.is_ok() {
+ info!("successfully submitted double voting evidence to chain `{counterparty_chain_id}`, tx hash: {}", response.hash);
+ } else {
+ error!(
+ "failed to submit double voting evidence to chain `{counterparty_chain_id}`: {response:?}"
+ );
+ }
+ }
+
+ // We have submitted the evidence to the provider, and because there can only be a single
+ // provider for a consumer chain, we can stop now. No need to check all the other
+ // counteparties.
+ Ok(ControlFlow::Break(()))
}
fn fetch_infraction_block_header(
- rt: &Arc<TokioRuntime>,
+ rt: &TokioRuntime,
chain: &CosmosSdkChain,
infraction_height: TendermintHeight,
trusted_height: Height,
@@ -1134,62 +1274,55 @@
fn handle_light_client_attack(
rt: Arc<TokioRuntime>,
+ config: &Config,
chain: &CosmosSdkChain,
key_name: Option<&String>,
evidence: LightClientAttackEvidence,
) -> eyre::Result<()> {
- let config = app_config();
-
// Build the two headers to submit as part of the `MsgSubmitMisbehaviour` message.
let (header1, header2) = build_evidence_headers(rt.clone(), chain, evidence.clone())?;
// Fetch all the counterparty clients of this chain.
- let counterparty_clients = fetch_all_counterparty_clients(chain)?;
+ let counterparty_clients = fetch_all_counterparty_clients(config, chain)?;
- let mut chains = HashMap::new();
+ // Cache for the chain handles
+ let mut chains = HashMap::new();
+
+ let chain_handle = spawn_runtime(rt.clone(), config, &mut chains, chain.id(), key_name)
+ .map_err(|e| {
+ eyre::eyre!(
+ "failed to spawn chain runtime for chain `{chain_id}`: {e}",
+ chain_id = chain.id()
+ )
+ })?;
// For each counterparty client, build the misbehaviour evidence and submit it to the chain,
// freezing that client.
for (counterparty_chain_id, counterparty_client_id) in counterparty_clients {
+ let counterparty_chain_handle = match spawn_runtime(
+ rt.clone(),
+ config,
+ &mut chains,
+ &counterparty_chain_id,
+ key_name,
+ ) {
+ Ok(chain_handle) => chain_handle,
+ Err(e) => {
+ error!(
+ "failed to spawn chain runtime for chain `{counterparty_chain_id}`: {e}",
+ counterparty_chain_id = counterparty_chain_id
+ );
+
+ continue;
+ }
+ };
+
let misbehaviour = TendermintMisbehaviour {
client_id: counterparty_client_id.clone(),
header1: header1.clone(),
header2: header2.clone(),
};
- if !chains.contains_key(chain.id()) {
- let chain_handle = spawn_chain_runtime_with_modified_config::<BaseChainHandle>(
- &config,
- chain.id(),
- rt.clone(),
- |chain_config| {
- if let Some(key_name) = key_name {
- chain_config.set_key_name(key_name.to_string());
- }
- },
- )?;
-
- chains.insert(chain.id().clone(), chain_handle);
- }
-
- if !chains.contains_key(&counterparty_chain_id) {
- let chain_handle = spawn_chain_runtime_with_modified_config::<BaseChainHandle>(
- &config,
- &counterparty_chain_id,
- rt.clone(),
- |chain_config| {
- if let Some(key_name) = key_name {
- chain_config.set_key_name(key_name.to_string());
- }
- },
- )?;
-
- chains.insert(counterparty_chain_id.clone(), chain_handle);
- };
-
- let chain_handle = chains.get(chain.id()).unwrap();
- let counterparty_chain_handle = chains.get(&counterparty_chain_id).unwrap();
-
let counterparty_client = ForeignClient::restore(
counterparty_client_id.clone(),
counterparty_chain_handle.clone(),
@@ -1201,7 +1334,7 @@
chain,
counterparty_client,
counterparty_client_id,
- counterparty_chain_handle,
+ &counterparty_chain_handle,
misbehaviour,
);
@@ -1414,6 +1547,7 @@
/// 2.2. From the client state, extract the chain id of the counterparty chain.
/// 4. Return a list of all counterparty chains and counterparty clients.
fn fetch_all_counterparty_clients(
+ config: &Config,
chain: &CosmosSdkChain,
) -> eyre::Result<Vec<(ChainId, ClientId)>> {
use ibc_relayer::chain::requests::{QueryClientStateRequest, QueryConnectionsRequest};
@@ -1427,12 +1561,18 @@
let mut counterparty_clients = vec![];
for connection in connections {
+ let client_id = connection.connection_end.client_id();
+ let counterparty_client_id = connection.connection_end.counterparty().client_id();
+
debug!(
- "fetching counterparty client state for connection `{}`",
+ "found connection `{}` with client `{client_id}` and counterparty client `{counterparty_client_id}`",
connection.connection_id
);
- let client_id = connection.connection_end.client_id();
+ debug!(
+ "fetching client state for client `{client_id}` on connection `{}`",
+ connection.connection_id
+ );
let client_state = chain.query_client_state(
QueryClientStateRequest {
@@ -1445,9 +1585,7 @@
let client_state = match client_state {
Ok((client_state, _)) => client_state,
Err(e) => {
- error!(
- "failed to fetch client state for counterparty client `{client_id}`, skipping..."
- );
+ error!("failed to fetch client state for client `{client_id}`, skipping...");
error!("reason: {e}");
continue;
@@ -1455,11 +1593,23 @@
};
let counterparty_chain_id = client_state.chain_id();
- info!("found counterparty client with id `{client_id}` on counterparty chain `{counterparty_chain_id}`");
- counterparty_clients.push((counterparty_chain_id, client_id.clone()));
+ if config.find_chain(&counterparty_chain_id).is_some() {
+ info!("found counterparty client `{counterparty_client_id}` which lives on counterparty chain `{counterparty_chain_id}`");
+
+ counterparty_clients.push((counterparty_chain_id, counterparty_client_id.clone()));
+ } else {
+ debug!(
+ "skipping counterparty client `{client_id}` on counterparty \
+ chain `{counterparty_chain_id}` which is not present in the config..."
+ );
+ }
}
+ // Remove duplicates
+ counterparty_clients.sort();
+ counterparty_clients.dedup();
+
Ok(counterparty_clients)
}