penumbra_ibc/component/msg_handler/
connection_open_try.rsuse crate::component::{proof_verification, HostInterface};
use crate::version::pick_connection_version;
use crate::IBC_COMMITMENT_PREFIX;
use anyhow::{Context, Result};
use async_trait::async_trait;
use cnidarium::{StateRead, StateWrite};
use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ConnectionPath};
use ibc_types::{
core::client::Height as IBCHeight,
core::connection::{
events, msgs::MsgConnectionOpenTry, ConnectionEnd, ConnectionId, Counterparty,
State as ConnectionState,
},
};
use crate::component::{
client::StateReadExt as _,
connection::{StateReadExt as _, StateWriteExt as _},
connection_counter::SUPPORTED_VERSIONS,
ics02_validation::validate_penumbra_client_state,
MsgHandler,
};
#[async_trait]
impl MsgHandler for MsgConnectionOpenTry {
async fn check_stateless<H>(&self) -> Result<()> {
Ok(())
}
async fn try_execute<S: StateWrite, AH, HI: HostInterface>(&self, mut state: S) -> Result<()> {
tracing::debug!(msg = ?self);
consensus_height_is_correct::<&S, HI>(&mut state, self).await?;
penumbra_client_state_is_well_formed::<&S, HI>(&mut state, self).await?;
let supported_versions = SUPPORTED_VERSIONS.clone();
pick_connection_version(&supported_versions, &self.versions_on_a.clone())?;
let expected_conn = ConnectionEnd {
state: ConnectionState::Init,
client_id: self.counterparty.client_id.clone(),
counterparty: Counterparty {
client_id: self.client_id_on_b.clone(),
connection_id: None,
prefix: IBC_COMMITMENT_PREFIX.clone(),
},
versions: self.versions_on_a.clone(),
delay_period: self.delay_period,
};
let trusted_client_state = state.get_client_state(&self.client_id_on_b).await?;
if trusted_client_state.is_frozen() {
anyhow::bail!("client is frozen");
}
let trusted_consensus_state = state
.get_verified_consensus_state(&self.proofs_height_on_a, &self.client_id_on_b)
.await?;
let proof_conn_end_on_a = self.proof_conn_end_on_a.clone();
proof_verification::verify_connection_state(
&trusted_client_state,
self.proofs_height_on_a,
&self.counterparty.prefix,
&proof_conn_end_on_a,
&trusted_consensus_state.root,
&ConnectionPath::new(
self.counterparty
.connection_id
.as_ref()
.ok_or_else(|| anyhow::anyhow!("counterparty connection id is not set"))?,
),
&expected_conn,
)
.context("failed to verify connection state")?;
let proof_client_state_of_b_on_a = self.proof_client_state_of_b_on_a.clone();
let client_state_of_b_on_a: TendermintClientState =
self.client_state_of_b_on_a.clone().try_into()?;
proof_verification::verify_client_full_state(
&trusted_client_state,
self.proofs_height_on_a,
&self.counterparty.prefix,
&proof_client_state_of_b_on_a,
&trusted_consensus_state.root,
&ClientStatePath::new(&self.counterparty.client_id),
client_state_of_b_on_a,
)
.context("couldn't verify client state")?;
let expected_consensus = state
.get_penumbra_consensus_state(self.consensus_height_of_b_on_a)
.await?;
let proof_consensus_state_of_b_on_a = self.proof_consensus_state_of_b_on_a.clone();
proof_verification::verify_client_consensus_state(
&trusted_client_state,
self.proofs_height_on_a,
&self.counterparty.prefix,
&proof_consensus_state_of_b_on_a,
&trusted_consensus_state.root,
&ClientConsensusStatePath::new(
&self.counterparty.client_id,
&self.consensus_height_of_b_on_a,
),
expected_consensus,
)
.context("couldn't verify client consensus state")?;
let mut new_conn = ConnectionEnd {
state: ConnectionState::TryOpen,
client_id: self.client_id_on_b.clone(),
counterparty: self.counterparty.clone(),
versions: self.versions_on_a.clone(),
delay_period: self.delay_period,
};
new_conn.versions = vec![pick_connection_version(
&SUPPORTED_VERSIONS.to_vec(),
&self.versions_on_a.clone(),
)?];
let new_connection_id = ConnectionId::new(
state
.get_connection_counter()
.await
.context("unable to get connection counter")?
.0,
);
state
.put_new_connection(&new_connection_id, new_conn)
.await
.context("unable to put new connection")?;
state.record(
events::ConnectionOpenTry {
conn_id_on_b: new_connection_id.clone(),
client_id_on_b: self.client_id_on_b.clone(),
conn_id_on_a: self.counterparty.connection_id.clone().unwrap_or_default(),
client_id_on_a: self.counterparty.client_id.clone(),
}
.into(),
);
Ok(())
}
}
async fn consensus_height_is_correct<S: StateRead, HI: HostInterface>(
state: S,
msg: &MsgConnectionOpenTry,
) -> anyhow::Result<()> {
let current_height = IBCHeight::new(
HI::get_revision_number(&state).await?,
HI::get_block_height(&state).await?,
)?;
if msg.consensus_height_of_b_on_a >= current_height {
anyhow::bail!("consensus height is greater than the current block height",);
}
Ok(())
}
async fn penumbra_client_state_is_well_formed<S: StateRead, HI: HostInterface>(
state: S,
msg: &MsgConnectionOpenTry,
) -> anyhow::Result<()> {
let height = HI::get_block_height(&state).await?;
let chain_id = HI::get_chain_id(&state).await?;
validate_penumbra_client_state(msg.client_state_of_b_on_a.clone(), &chain_id, height)?;
Ok(())
}