Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement verify_upgrade_and_update_state for Tendermint Client #349

Merged
merged 21 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9061610
Implement verify_upgrade_and_update_state
Farhad-Shabani Jan 13, 2023
73e87c1
Construct new_client_state and new_consensus_state
Farhad-Shabani Jan 13, 2023
d63fa4d
Split off verification and execution steps
Farhad-Shabani Jan 13, 2023
5b04088
Revise some namings and comments
Farhad-Shabani Jan 13, 2023
869d8de
Comment out some upgrade tests
Farhad-Shabani Jan 13, 2023
025a366
Update mock tests related to ugrade_client
Farhad-Shabani Jan 13, 2023
c968ee7
Merge branch 'main' into farhad/verify-upgrade-update-state
Farhad-Shabani Jan 13, 2023
5f51c7c
Add changelog entry
Farhad-Shabani Jan 13, 2023
a5b3f05
Refactor verify_upgrade_client to use Path and Encode error type
Farhad-Shabani Jan 17, 2023
d0c050e
Remove pub before UPGRADE const
Farhad-Shabani Jan 17, 2023
5dcf609
Rewrite upgrade_client unit tests
Farhad-Shabani Jan 17, 2023
8ebf323
Add unbonding period validation step
Farhad-Shabani Jan 27, 2023
9ea805a
Set root of new consensus state with sentinel value
Farhad-Shabani Jan 27, 2023
4155c75
Revise some comments
Farhad-Shabani Jan 27, 2023
a9b3198
Refactor upgrade method to zero_custom_fields
Farhad-Shabani Jan 27, 2023
5bc1a04
Mend fields of new client state
Farhad-Shabani Jan 31, 2023
cba4c94
Disable upgrade client handler
Farhad-Shabani Jan 31, 2023
efff958
Merge branch 'main' into farhad/verify-upgrade-update-state
Farhad-Shabani Jan 31, 2023
355cb1d
Fix issue with cargo test
Farhad-Shabani Jan 31, 2023
83de030
Flip upgrade_client feature flag
Farhad-Shabani Jan 31, 2023
3d76bfc
Remove unnecessary unbonding period check
Farhad-Shabani Feb 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Implement `verify_upgrade_and_update_state` method for Tendermint clients
([#19](https://github.com/cosmos/ibc-rs/issues/19)).
198 changes: 162 additions & 36 deletions crates/ibc/src/clients/ics07_tendermint/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use core::time::Duration;

use ibc_proto::google::protobuf::Any;
use ibc_proto::ibc::core::client::v1::Height as RawHeight;
use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof;
use ibc_proto::ibc::lightclients::tendermint::v1::ClientState as RawTmClientState;
use ibc_proto::ibc::core::commitment::v1::{MerklePath, MerkleProof as RawMerkleProof};
use ibc_proto::ibc::lightclients::tendermint::v1::{
ClientState as RawTmClientState, ConsensusState as RawTmConsensusState,
};
use ibc_proto::protobuf::Protobuf;
use prost::Message;
use tendermint::chain::id::MAX_LENGTH as MaxChainIdLen;
Expand All @@ -15,13 +17,12 @@ use tendermint_light_client_verifier::options::Options;
use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState};
use tendermint_light_client_verifier::{ProdVerifier, Verifier};

use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState;
use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState;
use crate::clients::ics07_tendermint::error::{Error, IntoResult};
use crate::clients::ics07_tendermint::header::{Header as TmHeader, Header};
use crate::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour;
use crate::core::ics02_client::client_state::{
ClientState as Ics2ClientState, UpdatedState, UpgradeOptions as CoreUpgradeOptions,
};
use crate::core::ics02_client::client_state::{ClientState as Ics2ClientState, UpdatedState};
use crate::core::ics02_client::client_type::ClientType;
use crate::core::ics02_client::consensus_state::ConsensusState;
use crate::core::ics02_client::context::ClientReader;
Expand All @@ -38,8 +39,8 @@ use crate::core::ics23_commitment::merkle::{apply_prefix, MerkleProof};
use crate::core::ics23_commitment::specs::ProofSpecs;
use crate::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId};
use crate::core::ics24_host::path::{
AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath,
ConnectionsPath, ReceiptsPath, SeqRecvsPath,
AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, ClientUpgradePath,
CommitmentsPath, ConnectionsPath, ReceiptsPath, SeqRecvsPath,
};
use crate::core::ics24_host::Path;
use crate::timestamp::{Timestamp, ZERO_DURATION};
Expand Down Expand Up @@ -351,14 +352,6 @@ impl ClientState {
}
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpgradeOptions {
pub unbonding_period: Duration,
}

impl CoreUpgradeOptions for UpgradeOptions {}

impl Ics2ClientState for ClientState {
fn chain_id(&self) -> ChainId {
self.chain_id.clone()
Expand All @@ -376,29 +369,14 @@ impl Ics2ClientState for ClientState {
self.frozen_height
}

fn upgrade(
&mut self,
upgrade_height: Height,
upgrade_options: &dyn CoreUpgradeOptions,
chain_id: ChainId,
) {
let upgrade_options = upgrade_options
.as_any()
.downcast_ref::<UpgradeOptions>()
.expect("UpgradeOptions not of type Tendermint");

fn zero_custom_fields(&mut self) {
// Reset custom fields to zero values
self.trusting_period = ZERO_DURATION;
self.trust_level = TrustThreshold::ZERO;
self.allow_update.after_expiry = false;
self.allow_update.after_misbehaviour = false;
self.frozen_height = None;
self.max_clock_drift = ZERO_DURATION;

// Upgrade the client state
self.latest_height = upgrade_height;
self.unbonding_period = upgrade_options.unbonding_period;
self.chain_id = chain_id;
}

fn expired(&self, elapsed: Duration) -> bool {
Expand Down Expand Up @@ -876,13 +854,161 @@ impl Ics2ClientState for ClientState {
})
}

fn verify_upgrade_and_update_state(
/// Perform client-specific verifications and check all data in the new
/// client state to be the same across all valid Tendermint clients for the
/// new chain.
///
/// You can learn more about how to upgrade IBC-connected SDK chains in
/// [this](https://ibc.cosmos.network/main/ibc/upgrades/quick-guide.html)
/// guide
fn verify_upgrade_client(
&self,
_consensus_state: Any,
_proof_upgrade_client: RawMerkleProof,
_proof_upgrade_consensus_state: RawMerkleProof,
upgraded_client_state: Any,
upgraded_consensus_state: Any,
proof_upgrade_client: RawMerkleProof,
proof_upgrade_consensus_state: RawMerkleProof,
root: &CommitmentRoot,
) -> Result<(), ClientError> {
// Make sure that the client type is of Tendermint type `ClientState`
let mut upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state)?;

// Make sure that the consensus type is of Tendermint type `ConsensusState`
let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?;

// Note: verification of proofs that unmarshalled correctly has been done
// while decoding the proto message into a `MsgEnvelope` domain type
let merkle_proof_upgrade_client = MerkleProof::from(proof_upgrade_client);
let merkle_proof_upgrade_cons_state = MerkleProof::from(proof_upgrade_consensus_state);

// Make sure the latest height of the current client is not greater then
// the upgrade height This condition checks both the revision number and
// the height
if self.latest_height() >= upgraded_tm_client_state.latest_height() {
return Err(ClientError::LowUpgradeHeight {
upgraded_height: self.latest_height(),
client_height: upgraded_tm_client_state.latest_height(),
});
}

// Ensure that the new unbonding period is not shorter than the current's
// client unbonding period
if self.unbonding_period > upgraded_tm_client_state.unbonding_period {
return Err(ClientError::ClientSpecific {
description:
"cannot upgrade client with a shorter unbonding period than the current one"
.to_string(),
});
}

// Check to see if the upgrade path is set
let mut upgrade_path = upgraded_tm_client_state.clone().upgrade_path;
if upgrade_path.pop().is_none() {
return Err(ClientError::ClientSpecific {
description: "cannot upgrade client as no upgrade path has been set".to_string(),
});
};

let last_height = self.latest_height().revision_height();

// Construct the merkle path for the client state
let mut client_upgrade_path = upgrade_path.clone();
client_upgrade_path.push(ClientUpgradePath::UpgradedClientState(last_height).to_string());

let client_upgrade_merkle_path = MerklePath {
key_path: client_upgrade_path,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned that we're not building the right path, comparing with ibc-go. Can you confirm/disconfirm this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite understandable. Do you think we can keep this checked with issue #385?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If were that unsure about the current implementation, we should then put it behind a feature flag (similar to val_exec_ctx), which will allow us to test it with basecoin-rs, and at the same time, signal to users that this feature shouldn't be used yet.


upgraded_tm_client_state.zero_custom_fields();
plafer marked this conversation as resolved.
Show resolved Hide resolved
let client_state_value =
Protobuf::<RawTmClientState>::encode_vec(&upgraded_tm_client_state)
.map_err(ClientError::Encode)?;

// Verify the proof of the upgraded client state
merkle_proof_upgrade_client
.verify_membership(
&self.proof_specs,
root.clone().into(),
client_upgrade_merkle_path,
client_state_value,
0,
)
.map_err(ClientError::Ics23Verification)?;

// Construct the merkle path for the consensus state
let mut cons_upgrade_path = upgrade_path;
cons_upgrade_path
.push(ClientUpgradePath::UpgradedClientConsensusState(last_height).to_string());
let cons_upgrade_merkle_path = MerklePath {
key_path: cons_upgrade_path,
};
plafer marked this conversation as resolved.
Show resolved Hide resolved

let cons_state_value = Protobuf::<RawTmConsensusState>::encode_vec(&upgraded_tm_cons_state)
.map_err(ClientError::Encode)?;

// Verify the proof of the upgraded consensus state
merkle_proof_upgrade_cons_state
.verify_membership(
&self.proof_specs,
root.clone().into(),
cons_upgrade_merkle_path,
cons_state_value,
0,
)
.map_err(ClientError::Ics23Verification)?;

Ok(())
}

// Commit the new client state and consensus state to the store
fn update_state_with_upgrade_client(
&self,
upgraded_client_state: Any,
upgraded_consensus_state: Any,
) -> Result<UpdatedState, ClientError> {
unimplemented!()
let upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state)?;
let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?;

// Construct new client state and consensus state relayer chosen client
// parameters are ignored. All chain-chosen parameters come from
// committed client, all client-chosen parameters come from current
// client.
let new_client_state = TmClientState::new(
upgraded_tm_client_state.chain_id,
self.trust_level,
self.trusting_period,
upgraded_tm_client_state.unbonding_period,
self.max_clock_drift,
upgraded_tm_client_state.latest_height,
upgraded_tm_client_state.proof_specs,
upgraded_tm_client_state.upgrade_path,
upgraded_tm_client_state.allow_update,
plafer marked this conversation as resolved.
Show resolved Hide resolved
upgraded_tm_client_state.frozen_height,
plafer marked this conversation as resolved.
Show resolved Hide resolved
)?;

// The new consensus state is merely used as a trusted kernel against
// which headers on the new chain can be verified. The root is just a
// stand-in sentinel value as it cannot be known in advance, thus no
// proof verification will pass. The timestamp and the
// NextValidatorsHash of the consensus state is the blocktime and
// NextValidatorsHash of the last block committed by the old chain. This
// will allow the first block of the new chain to be verified against
// the last validators of the old chain so long as it is submitted
// within the TrustingPeriod of this client.
// NOTE: We do not set processed time for this consensus state since
// this consensus state should not be used for packet verification as
// the root is empty. The next consensus state submitted using update
// will be usable for packet-verification.
let sentinel_root = "sentinel_root".as_bytes().to_vec();
let new_consensus_state = TmConsensusState::new(
sentinel_root.into(),
upgraded_tm_cons_state.timestamp,
upgraded_tm_cons_state.next_validators_hash,
);

Ok(UpdatedState {
client_state: new_client_state.into_box(),
consensus_state: new_consensus_state.into_box(),
})
}

fn verify_client_consensus_state(
Expand Down
36 changes: 24 additions & 12 deletions crates/ibc/src/core/ics02_client/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ pub trait ClientState:
/// Helper function to verify the upgrade client procedure.
/// Resets all fields except the blockchain-specific ones,
/// and updates the given fields.
fn upgrade(
&mut self,
upgrade_height: Height,
upgrade_options: &dyn UpgradeOptions,
chain_id: ChainId,
);
fn zero_custom_fields(&mut self);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we need this (see previous comments), I don't think this should be in the ICS-2 interface. Can't it just be a private function in the tendermint client module?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that in any chain and client state struct there will be some relayer customizable fields that are needed to be zeroed out for the upgrade process. I think it makes sense to keep it here


/// Convert into a boxed trait object
fn into_box(self) -> Box<dyn ClientState>
Expand Down Expand Up @@ -112,11 +107,30 @@ pub trait ClientState:
misbehaviour: Any,
) -> Result<Box<dyn ClientState>, ContextError>;

fn verify_upgrade_and_update_state(
/// Verify the upgraded client and consensus states and validate proofs
/// against the given root.
///
/// NOTE: proof heights are not included as upgrade to a new revision is
/// expected to pass only on the last height committed by the current
/// revision. Clients are responsible for ensuring that the planned last
/// height of the current revision is somehow encoded in the proof
/// verification process. This is to ensure that no premature upgrades
/// occur, since upgrade plans committed to by the counterparty may be
/// cancelled or modified before the last planned height.
fn verify_upgrade_client(
&self,
consensus_state: Any,
upgraded_client_state: Any,
upgraded_consensus_state: Any,
proof_upgrade_client: MerkleProof,
proof_upgrade_consensus_state: MerkleProof,
root: &CommitmentRoot,
) -> Result<(), ClientError>;

// Update the client state and consensus state in the store with the upgraded ones.
fn update_state_with_upgrade_client(
&self,
upgraded_client_state: Any,
upgraded_consensus_state: Any,
) -> Result<UpdatedState, ClientError>;

/// Verification functions as specified in:
Expand Down Expand Up @@ -175,7 +189,7 @@ pub trait ClientState:
expected_client_state: Any,
) -> Result<(), ClientError>;

/// Verify a `proof` that a packet has been commited.
/// Verify a `proof` that a packet has been committed.
#[allow(clippy::too_many_arguments)]
fn verify_packet_data(
&self,
Expand All @@ -190,7 +204,7 @@ pub trait ClientState:
commitment: PacketCommitment,
) -> Result<(), ClientError>;

/// Verify a `proof` that a packet has been commited.
/// Verify a `proof` that a packet has been committed.
#[allow(clippy::too_many_arguments)]
fn verify_packet_acknowledgement(
&self,
Expand Down Expand Up @@ -258,8 +272,6 @@ pub fn downcast_client_state<CS: ClientState>(h: &dyn ClientState) -> Option<&CS
h.as_any().downcast_ref::<CS>()
}

pub trait UpgradeOptions: AsAny {}

pub struct UpdatedState {
pub client_state: Box<dyn ClientState>,
pub consensus_state: Box<dyn ConsensusState>,
Expand Down
2 changes: 2 additions & 0 deletions crates/ibc/src/core/ics02_client/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum ClientError {
MissingRawConsensusState,
/// invalid client id in the update client message: `{0}`
InvalidMsgUpdateClientId(ValidationError),
/// Encode error: `{0}`
Encode(TendermintProtoError),
/// decode error: `{0}`
Decode(prost::DecodeError),
/// invalid client identifier error: `{0}`
Expand Down
Loading