Skip to content

Commit

Permalink
ibc: draw the rest of the client upgrade handler
Browse files Browse the repository at this point in the history
  • Loading branch information
avahowell committed Oct 6, 2023
1 parent d5afb6a commit 55bedc7
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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?,
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions crates/core/component/ibc/src/component/msg_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
120 changes: 120 additions & 0 deletions crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs
Original file line number Diff line number Diff line change
@@ -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<S: StateWrite>(&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(())
}
}
19 changes: 9 additions & 10 deletions crates/core/component/ibc/src/component/proof_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -170,6 +167,8 @@ pub trait ClientUpgradeProofVerifier: StateReadExt {
}
}

impl<T: StateRead> ClientUpgradeProofVerifier for T {}

#[async_trait]
pub trait ChannelProofVerifier: StateReadExt {
async fn verify_channel_proof(
Expand Down
14 changes: 13 additions & 1 deletion crates/core/component/ibc/src/ibc_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize};
pub enum IbcAction {
CreateClient(MsgCreateClient),
UpdateClient(MsgUpdateClient),
UpgradeClient(MsgUpgradeClient),
SubmitMisbehavior(MsgSubmitMisbehaviour),
ConnectionOpenInit(MsgConnectionOpenInit),
ConnectionOpenTry(MsgConnectionOpenTry),
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -135,6 +139,10 @@ impl TryFrom<pb::IbcAction> 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)
Expand Down Expand Up @@ -203,6 +211,10 @@ impl From<IbcAction> 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(),
Expand Down

0 comments on commit 55bedc7

Please sign in to comment.