From 55bedc710feaf11710de4498cb7240e81cc6090c Mon Sep 17 00:00:00 2001 From: Ava Howell Date: Fri, 6 Oct 2023 12:55:51 -0700 Subject: [PATCH] ibc: draw the rest of the client upgrade handler --- .../component/action_handler/ibc_action.rs | 5 + .../ibc/src/component/msg_handler.rs | 1 + .../component/msg_handler/upgrade_client.rs | 120 ++++++++++++++++++ .../ibc/src/component/proof_verification.rs | 19 ++- crates/core/component/ibc/src/ibc_action.rs | 14 +- 5 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs diff --git a/crates/core/component/ibc/src/component/action_handler/ibc_action.rs b/crates/core/component/ibc/src/component/action_handler/ibc_action.rs index 0afc896c2f..46fa414460 100644 --- a/crates/core/component/ibc/src/component/action_handler/ibc_action.rs +++ b/crates/core/component/ibc/src/component/action_handler/ibc_action.rs @@ -14,6 +14,7 @@ impl ActionHandler for IbcAction { match self { IbcAction::CreateClient(msg) => msg.check_stateless().await?, IbcAction::UpdateClient(msg) => msg.check_stateless().await?, + IbcAction::UpgradeClient(msg) => msg.check_stateless().await?, IbcAction::SubmitMisbehavior(msg) => msg.check_stateless().await?, IbcAction::ConnectionOpenInit(msg) => msg.check_stateless().await?, IbcAction::ConnectionOpenTry(msg) => msg.check_stateless().await?, @@ -51,6 +52,10 @@ impl ActionHandler for IbcAction { .try_execute(state) .await .with_context(|| "Failed to execute UpdateClient message")?, + IbcAction::UpgradeClient(msg) => msg + .try_execute(state) + .await + .with_context(|| "Failed to execute UpgradeClient message")?, IbcAction::SubmitMisbehavior(msg) => msg .try_execute(state) .await diff --git a/crates/core/component/ibc/src/component/msg_handler.rs b/crates/core/component/ibc/src/component/msg_handler.rs index 33f7ecad72..77f854404c 100644 --- a/crates/core/component/ibc/src/component/msg_handler.rs +++ b/crates/core/component/ibc/src/component/msg_handler.rs @@ -14,6 +14,7 @@ mod misbehavior; mod recv_packet; mod timeout; mod update_client; +mod upgrade_client; use anyhow::Result; use async_trait::async_trait; diff --git a/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs b/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs new file mode 100644 index 0000000000..bf7daa6588 --- /dev/null +++ b/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs @@ -0,0 +1,120 @@ +use anyhow::{Context, Result}; +use async_trait::async_trait; +use ibc_types::{ + core::{ + client::{events, msgs::MsgUpgradeClient}, + commitment::{MerkleProof, MerkleRoot}, + }, + lightclients::tendermint::consensus_state::ConsensusState as TendermintConsensusState, + lightclients::tendermint::{ + client_state::ClientState as TendermintClientState, TENDERMINT_CLIENT_TYPE, + }, +}; +use penumbra_storage::StateWrite; + +use crate::component::{ + client::{StateReadExt as _, StateWriteExt as _}, + proof_verification::ClientUpgradeProofVerifier, + MsgHandler, +}; + +static SENTINEL_UPGRADE_ROOT: &str = "sentinel_root"; + +#[async_trait] +impl MsgHandler for MsgUpgradeClient { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + // execute an ibc client upgrade for a counterparty client. + // + // the message being parsed here is initiating an upgrade that allows the counterparty to + // change certain parameters of its client state (such as the chain id), as well as the + // consensus state (the next set of validators). + // + // in order for a client upgrade to be valid, the counterparty must have committed (using the + // trusted, un-upgraded client state) the new client and consensus states to its state tree. + // + // the first consensus state of the upgraded client uses a sentinel root, against which no + // proofs will verify. subsequent client updates, post-upgrade, will provide usable roots. + async fn try_execute(&self, mut state: S) -> Result<()> { + tracing::debug!(msg = ?self); + + let upgraded_client_state_tm = TendermintClientState::try_from(self.client_state.clone()) + .context("client state is not a Tendermint client state")?; + let upgraded_consensus_state_tm = + TendermintConsensusState::try_from(self.consensus_state.clone()) + .context("consensus state is not a Tendermint consensus state")?; + + let proof_consensus_state: MerkleProof = self + .proof_upgrade_consensus_state + .clone() + .try_into() + .context("couldn't decode proof for upgraded consensus state")?; + let proof_client_state: MerkleProof = self + .proof_upgrade_client + .clone() + .try_into() + .context("couldn't decode proof for upgraded client state")?; + + state + .verify_client_upgrade_proof( + &self.client_id, + &proof_client_state, + &proof_consensus_state, + upgraded_consensus_state_tm.clone(), + upgraded_client_state_tm.clone(), + ) + .await?; + + let old_client_state = state.get_client_state(&self.client_id).await?; + + // construct the new client state to be committed to our state. we don't allow the + // trust_level, trusting_period, clock_drift, allow_update, or frozen_height to change + // across upgrades. + let new_client_state = TendermintClientState::new( + upgraded_client_state_tm.chain_id, + old_client_state.trust_level, + old_client_state.trusting_period, + upgraded_client_state_tm.unbonding_period, + old_client_state.max_clock_drift, + upgraded_client_state_tm.latest_height, + upgraded_client_state_tm.proof_specs, + upgraded_client_state_tm.upgrade_path, + old_client_state.allow_update, + old_client_state.frozen_height, + )?; + + let new_consensus_state = TendermintConsensusState::new( + MerkleRoot { + hash: SENTINEL_UPGRADE_ROOT.into(), + }, + upgraded_consensus_state_tm.timestamp, + upgraded_consensus_state_tm.next_validators_hash, + ); + + let latest_height = new_client_state.latest_height(); + + state.put_client(&self.client_id, new_client_state); + state + .put_verified_consensus_state( + latest_height, + self.client_id.clone(), + new_consensus_state, + ) + .await?; + + state.record( + events::UpgradeClient { + client_id: self.client_id.clone(), + client_type: ibc_types::core::client::ClientType( + TENDERMINT_CLIENT_TYPE.to_string(), + ), + consensus_height: latest_height, + } + .into(), + ); + + Ok(()) + } +} diff --git a/crates/core/component/ibc/src/component/proof_verification.rs b/crates/core/component/ibc/src/component/proof_verification.rs index cb444d0011..e7c964ecbb 100644 --- a/crates/core/component/ibc/src/component/proof_verification.rs +++ b/crates/core/component/ibc/src/component/proof_verification.rs @@ -111,14 +111,14 @@ fn verify_merkle_proof( pub trait ClientUpgradeProofVerifier: StateReadExt { async fn verify_client_upgrade_proof( &self, - connection: &ConnectionEnd, - proof: &MerkleProof, - proof_height: &Height, + client_id: &ClientId, + client_state_proof: &MerkleProof, + consensus_state_proof: &MerkleProof, upgraded_tm_consensus_state: TendermintConsensusState, upgraded_tm_client_state: TendermintClientState, ) -> anyhow::Result<()> { // get the stored client state for the counterparty - let trusted_client_state = self.get_client_state(&connection.client_id).await?; + let trusted_client_state = self.get_client_state(client_id).await?; // Check to see if the upgrade path is set let mut upgrade_path = trusted_client_state.upgrade_path.clone(); @@ -132,22 +132,19 @@ pub trait ClientUpgradeProofVerifier: StateReadExt { })?; // check if the client is frozen - // TODO: should we also check if the client is expired here? if trusted_client_state.is_frozen() { anyhow::bail!("client is frozen"); } // get the stored consensus state for the counterparty let trusted_consensus_state = self - .get_verified_consensus_state(*proof_height, connection.client_id.clone()) + .get_verified_consensus_state(trusted_client_state.latest_height(), client_id.clone()) .await?; - trusted_client_state.verify_height(*proof_height)?; - verify_merkle_proof( &trusted_client_state.proof_specs, &upgrade_path_prefix, - proof, + client_state_proof, &trusted_consensus_state.root, ClientUpgradePath::UpgradedClientState( trusted_client_state.latest_height().revision_height(), @@ -158,7 +155,7 @@ pub trait ClientUpgradeProofVerifier: StateReadExt { verify_merkle_proof( &trusted_client_state.proof_specs, &upgrade_path_prefix, - proof, + consensus_state_proof, &trusted_consensus_state.root, ClientUpgradePath::UpgradedClientConsensusState( trusted_client_state.latest_height().revision_height(), @@ -170,6 +167,8 @@ pub trait ClientUpgradeProofVerifier: StateReadExt { } } +impl ClientUpgradeProofVerifier for T {} + #[async_trait] pub trait ChannelProofVerifier: StateReadExt { async fn verify_channel_proof( diff --git a/crates/core/component/ibc/src/ibc_action.rs b/crates/core/component/ibc/src/ibc_action.rs index d8fb6afb1e..7cd5708e8a 100644 --- a/crates/core/component/ibc/src/ibc_action.rs +++ b/crates/core/component/ibc/src/ibc_action.rs @@ -3,7 +3,7 @@ use ibc_types::core::{ MsgAcknowledgement, MsgChannelCloseConfirm, MsgChannelCloseInit, MsgChannelOpenAck, MsgChannelOpenConfirm, MsgChannelOpenInit, MsgChannelOpenTry, MsgRecvPacket, MsgTimeout, }, - client::msgs::{MsgCreateClient, MsgSubmitMisbehaviour, MsgUpdateClient}, + client::msgs::{MsgCreateClient, MsgSubmitMisbehaviour, MsgUpdateClient, MsgUpgradeClient}, connection::msgs::{ MsgConnectionOpenAck, MsgConnectionOpenConfirm, MsgConnectionOpenInit, MsgConnectionOpenTry, }, @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize}; pub enum IbcAction { CreateClient(MsgCreateClient), UpdateClient(MsgUpdateClient), + UpgradeClient(MsgUpgradeClient), SubmitMisbehavior(MsgSubmitMisbehaviour), ConnectionOpenInit(MsgConnectionOpenInit), ConnectionOpenTry(MsgConnectionOpenTry), @@ -59,6 +60,9 @@ impl IbcAction { IbcAction::UpdateClient(msg) => { tracing::info_span!(parent: parent, "UpdateClient", client_id = %msg.client_id) } + IbcAction::UpgradeClient(msg) => { + tracing::info_span!(parent: parent, "UpgradeClient", client_id = %msg.client_id) + } IbcAction::SubmitMisbehavior(msg) => { tracing::info_span!(parent: parent, "SubmitMisbehavior", client_id = %msg.client_id) } @@ -135,6 +139,10 @@ impl TryFrom for IbcAction { let msg = MsgUpdateClient::decode(raw_action_bytes)?; IbcAction::UpdateClient(msg) } + MsgUpgradeClient::TYPE_URL => { + let msg = MsgUpgradeClient::decode(raw_action_bytes)?; + IbcAction::UpgradeClient(msg) + } MsgConnectionOpenInit::TYPE_URL => { let msg = MsgConnectionOpenInit::decode(raw_action_bytes)?; IbcAction::ConnectionOpenInit(msg) @@ -203,6 +211,10 @@ impl From for pb::IbcAction { type_url: MsgUpdateClient::TYPE_URL.to_string(), value: msg.encode_to_vec().into(), }, + IbcAction::UpgradeClient(msg) => pbjson_types::Any { + type_url: MsgUpgradeClient::TYPE_URL.to_string(), + value: msg.encode_to_vec().into(), + }, IbcAction::SubmitMisbehavior(msg) => pbjson_types::Any { type_url: MsgSubmitMisbehaviour::TYPE_URL.to_string(), value: msg.encode_to_vec().into(),