Skip to content

Commit 1fb3547

Browse files
Implement verify_upgrade_and_update_state for Tendermint Client (#349)
* Implement verify_upgrade_and_update_state * Construct new_client_state and new_consensus_state * Split off verification and execution steps * Revise some namings and comments * Comment out some upgrade tests * Update mock tests related to ugrade_client * Add changelog entry * Refactor verify_upgrade_client to use Path and Encode error type * Remove pub before UPGRADE const * Rewrite upgrade_client unit tests * Add unbonding period validation step * Set root of new consensus state with sentinel value * Revise some comments * Refactor upgrade method to zero_custom_fields * Mend fields of new client state * Disable upgrade client handler * Fix issue with cargo test * Flip upgrade_client feature flag * Remove unnecessary unbonding period check --------- Signed-off-by: Farhad Shabani <[email protected]>
1 parent 0605dd3 commit 1fb3547

File tree

10 files changed

+446
-252
lines changed

10 files changed

+446
-252
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Implement `verify_upgrade_and_update_state` method for Tendermint clients
2+
([#19](https://github.com/cosmos/ibc-rs/issues/19)).

crates/ibc/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ serde = ["dep:serde", "dep:serde_derive", "serde_json", "erased-serde"]
4545
# This feature guards the unfinished implementation of ADR 5.
4646
val_exec_ctx = []
4747

48+
# This feature guards the unfinished implementation of the `UpgradeClient` handler.
49+
upgrade_client = []
50+
4851
# This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`.
4952
# Depends on the `testgen` suite for generating Tendermint light blocks.
5053
mocks = ["tendermint-testgen", "tendermint/clock", "cfg-if", "parking_lot"]

crates/ibc/src/clients/ics07_tendermint/client_state.rs

Lines changed: 155 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use core::time::Duration;
55

66
use ibc_proto::google::protobuf::Any;
77
use ibc_proto::ibc::core::client::v1::Height as RawHeight;
8-
use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof;
9-
use ibc_proto::ibc::lightclients::tendermint::v1::ClientState as RawTmClientState;
8+
use ibc_proto::ibc::core::commitment::v1::{MerklePath, MerkleProof as RawMerkleProof};
9+
use ibc_proto::ibc::lightclients::tendermint::v1::{
10+
ClientState as RawTmClientState, ConsensusState as RawTmConsensusState,
11+
};
1012
use ibc_proto::protobuf::Protobuf;
1113
use prost::Message;
1214
use tendermint::chain::id::MAX_LENGTH as MaxChainIdLen;
@@ -15,13 +17,12 @@ use tendermint_light_client_verifier::options::Options;
1517
use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState};
1618
use tendermint_light_client_verifier::{ProdVerifier, Verifier};
1719

20+
use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState;
1821
use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState;
1922
use crate::clients::ics07_tendermint::error::{Error, IntoResult};
2023
use crate::clients::ics07_tendermint::header::{Header as TmHeader, Header};
2124
use crate::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour;
22-
use crate::core::ics02_client::client_state::{
23-
ClientState as Ics2ClientState, UpdatedState, UpgradeOptions as CoreUpgradeOptions,
24-
};
25+
use crate::core::ics02_client::client_state::{ClientState as Ics2ClientState, UpdatedState};
2526
use crate::core::ics02_client::client_type::ClientType;
2627
use crate::core::ics02_client::consensus_state::ConsensusState;
2728
use crate::core::ics02_client::context::ClientReader;
@@ -38,8 +39,8 @@ use crate::core::ics23_commitment::merkle::{apply_prefix, MerkleProof};
3839
use crate::core::ics23_commitment::specs::ProofSpecs;
3940
use crate::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId};
4041
use crate::core::ics24_host::path::{
41-
AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath,
42-
ConnectionsPath, ReceiptsPath, SeqRecvsPath,
42+
AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, ClientUpgradePath,
43+
CommitmentsPath, ConnectionsPath, ReceiptsPath, SeqRecvsPath,
4344
};
4445
use crate::core::ics24_host::Path;
4546
use crate::timestamp::{Timestamp, ZERO_DURATION};
@@ -351,14 +352,6 @@ impl ClientState {
351352
}
352353
}
353354

354-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
355-
#[derive(Clone, Debug, PartialEq, Eq)]
356-
pub struct UpgradeOptions {
357-
pub unbonding_period: Duration,
358-
}
359-
360-
impl CoreUpgradeOptions for UpgradeOptions {}
361-
362355
impl Ics2ClientState for ClientState {
363356
fn chain_id(&self) -> ChainId {
364357
self.chain_id.clone()
@@ -376,29 +369,14 @@ impl Ics2ClientState for ClientState {
376369
self.frozen_height
377370
}
378371

379-
fn upgrade(
380-
&mut self,
381-
upgrade_height: Height,
382-
upgrade_options: &dyn CoreUpgradeOptions,
383-
chain_id: ChainId,
384-
) {
385-
let upgrade_options = upgrade_options
386-
.as_any()
387-
.downcast_ref::<UpgradeOptions>()
388-
.expect("UpgradeOptions not of type Tendermint");
389-
372+
fn zero_custom_fields(&mut self) {
390373
// Reset custom fields to zero values
391374
self.trusting_period = ZERO_DURATION;
392375
self.trust_level = TrustThreshold::ZERO;
393376
self.allow_update.after_expiry = false;
394377
self.allow_update.after_misbehaviour = false;
395378
self.frozen_height = None;
396379
self.max_clock_drift = ZERO_DURATION;
397-
398-
// Upgrade the client state
399-
self.latest_height = upgrade_height;
400-
self.unbonding_period = upgrade_options.unbonding_period;
401-
self.chain_id = chain_id;
402380
}
403381

404382
fn expired(&self, elapsed: Duration) -> bool {
@@ -876,13 +854,154 @@ impl Ics2ClientState for ClientState {
876854
})
877855
}
878856

879-
fn verify_upgrade_and_update_state(
857+
/// Perform client-specific verifications and check all data in the new
858+
/// client state to be the same across all valid Tendermint clients for the
859+
/// new chain.
860+
///
861+
/// You can learn more about how to upgrade IBC-connected SDK chains in
862+
/// [this](https://ibc.cosmos.network/main/ibc/upgrades/quick-guide.html)
863+
/// guide
864+
fn verify_upgrade_client(
865+
&self,
866+
upgraded_client_state: Any,
867+
upgraded_consensus_state: Any,
868+
proof_upgrade_client: RawMerkleProof,
869+
proof_upgrade_consensus_state: RawMerkleProof,
870+
root: &CommitmentRoot,
871+
) -> Result<(), ClientError> {
872+
// Make sure that the client type is of Tendermint type `ClientState`
873+
let mut upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state)?;
874+
875+
// Make sure that the consensus type is of Tendermint type `ConsensusState`
876+
let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?;
877+
878+
// Note: verification of proofs that unmarshalled correctly has been done
879+
// while decoding the proto message into a `MsgEnvelope` domain type
880+
let merkle_proof_upgrade_client = MerkleProof::from(proof_upgrade_client);
881+
let merkle_proof_upgrade_cons_state = MerkleProof::from(proof_upgrade_consensus_state);
882+
883+
// Make sure the latest height of the current client is not greater then
884+
// the upgrade height This condition checks both the revision number and
885+
// the height
886+
if self.latest_height() >= upgraded_tm_client_state.latest_height() {
887+
return Err(ClientError::LowUpgradeHeight {
888+
upgraded_height: self.latest_height(),
889+
client_height: upgraded_tm_client_state.latest_height(),
890+
});
891+
}
892+
893+
// Check to see if the upgrade path is set
894+
let mut upgrade_path = self.upgrade_path.clone();
895+
if upgrade_path.pop().is_none() {
896+
return Err(ClientError::ClientSpecific {
897+
description: "cannot upgrade client as no upgrade path has been set".to_string(),
898+
});
899+
};
900+
901+
let last_height = self.latest_height().revision_height();
902+
903+
// Construct the merkle path for the client state
904+
let mut client_upgrade_path = upgrade_path.clone();
905+
client_upgrade_path.push(ClientUpgradePath::UpgradedClientState(last_height).to_string());
906+
907+
let client_upgrade_merkle_path = MerklePath {
908+
key_path: client_upgrade_path,
909+
};
910+
911+
upgraded_tm_client_state.zero_custom_fields();
912+
let client_state_value =
913+
Protobuf::<RawTmClientState>::encode_vec(&upgraded_tm_client_state)
914+
.map_err(ClientError::Encode)?;
915+
916+
// Verify the proof of the upgraded client state
917+
merkle_proof_upgrade_client
918+
.verify_membership(
919+
&self.proof_specs,
920+
root.clone().into(),
921+
client_upgrade_merkle_path,
922+
client_state_value,
923+
0,
924+
)
925+
.map_err(ClientError::Ics23Verification)?;
926+
927+
// Construct the merkle path for the consensus state
928+
let mut cons_upgrade_path = upgrade_path;
929+
cons_upgrade_path
930+
.push(ClientUpgradePath::UpgradedClientConsensusState(last_height).to_string());
931+
let cons_upgrade_merkle_path = MerklePath {
932+
key_path: cons_upgrade_path,
933+
};
934+
935+
let cons_state_value = Protobuf::<RawTmConsensusState>::encode_vec(&upgraded_tm_cons_state)
936+
.map_err(ClientError::Encode)?;
937+
938+
// Verify the proof of the upgraded consensus state
939+
merkle_proof_upgrade_cons_state
940+
.verify_membership(
941+
&self.proof_specs,
942+
root.clone().into(),
943+
cons_upgrade_merkle_path,
944+
cons_state_value,
945+
0,
946+
)
947+
.map_err(ClientError::Ics23Verification)?;
948+
949+
Ok(())
950+
}
951+
952+
// Commit the new client state and consensus state to the store
953+
fn update_state_with_upgrade_client(
880954
&self,
881-
_consensus_state: Any,
882-
_proof_upgrade_client: RawMerkleProof,
883-
_proof_upgrade_consensus_state: RawMerkleProof,
955+
upgraded_client_state: Any,
956+
upgraded_consensus_state: Any,
884957
) -> Result<UpdatedState, ClientError> {
885-
unimplemented!()
958+
let upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state)?;
959+
let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?;
960+
961+
// Frozen height is set to None fo the new client state
962+
let new_frozen_height = None;
963+
964+
// Construct new client state and consensus state relayer chosen client
965+
// parameters are ignored. All chain-chosen parameters come from
966+
// committed client, all client-chosen parameters come from current
967+
// client.
968+
let new_client_state = TmClientState::new(
969+
upgraded_tm_client_state.chain_id,
970+
self.trust_level,
971+
self.trusting_period,
972+
upgraded_tm_client_state.unbonding_period,
973+
self.max_clock_drift,
974+
upgraded_tm_client_state.latest_height,
975+
upgraded_tm_client_state.proof_specs,
976+
upgraded_tm_client_state.upgrade_path,
977+
self.allow_update,
978+
new_frozen_height,
979+
)?;
980+
981+
// The new consensus state is merely used as a trusted kernel against
982+
// which headers on the new chain can be verified. The root is just a
983+
// stand-in sentinel value as it cannot be known in advance, thus no
984+
// proof verification will pass. The timestamp and the
985+
// NextValidatorsHash of the consensus state is the blocktime and
986+
// NextValidatorsHash of the last block committed by the old chain. This
987+
// will allow the first block of the new chain to be verified against
988+
// the last validators of the old chain so long as it is submitted
989+
// within the TrustingPeriod of this client.
990+
// NOTE: We do not set processed time for this consensus state since
991+
// this consensus state should not be used for packet verification as
992+
// the root is empty. The next consensus state submitted using update
993+
// will be usable for packet-verification.
994+
let sentinel_root = "sentinel_root".as_bytes().to_vec();
995+
let new_consensus_state = TmConsensusState::new(
996+
sentinel_root.into(),
997+
upgraded_tm_cons_state.timestamp,
998+
upgraded_tm_cons_state.next_validators_hash,
999+
);
1000+
1001+
Ok(UpdatedState {
1002+
client_state: new_client_state.into_box(),
1003+
consensus_state: new_consensus_state.into_box(),
1004+
})
8861005
}
8871006

8881007
fn verify_client_consensus_state(

crates/ibc/src/core/ics02_client/client_state.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ pub trait ClientState:
6363
/// Helper function to verify the upgrade client procedure.
6464
/// Resets all fields except the blockchain-specific ones,
6565
/// and updates the given fields.
66-
fn upgrade(
67-
&mut self,
68-
upgrade_height: Height,
69-
upgrade_options: &dyn UpgradeOptions,
70-
chain_id: ChainId,
71-
);
66+
fn zero_custom_fields(&mut self);
7267

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

115-
fn verify_upgrade_and_update_state(
110+
/// Verify the upgraded client and consensus states and validate proofs
111+
/// against the given root.
112+
///
113+
/// NOTE: proof heights are not included as upgrade to a new revision is
114+
/// expected to pass only on the last height committed by the current
115+
/// revision. Clients are responsible for ensuring that the planned last
116+
/// height of the current revision is somehow encoded in the proof
117+
/// verification process. This is to ensure that no premature upgrades
118+
/// occur, since upgrade plans committed to by the counterparty may be
119+
/// cancelled or modified before the last planned height.
120+
fn verify_upgrade_client(
116121
&self,
117-
consensus_state: Any,
122+
upgraded_client_state: Any,
123+
upgraded_consensus_state: Any,
118124
proof_upgrade_client: MerkleProof,
119125
proof_upgrade_consensus_state: MerkleProof,
126+
root: &CommitmentRoot,
127+
) -> Result<(), ClientError>;
128+
129+
// Update the client state and consensus state in the store with the upgraded ones.
130+
fn update_state_with_upgrade_client(
131+
&self,
132+
upgraded_client_state: Any,
133+
upgraded_consensus_state: Any,
120134
) -> Result<UpdatedState, ClientError>;
121135

122136
/// Verification functions as specified in:
@@ -274,8 +288,6 @@ pub fn downcast_client_state<CS: ClientState>(h: &dyn ClientState) -> Option<&CS
274288
h.as_any().downcast_ref::<CS>()
275289
}
276290

277-
pub trait UpgradeOptions: AsAny {}
278-
279291
pub struct UpdatedState {
280292
pub client_state: Box<dyn ClientState>,
281293
pub consensus_state: Box<dyn ConsensusState>,

crates/ibc/src/core/ics02_client/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub enum ClientError {
4949
MissingRawConsensusState,
5050
/// invalid client id in the update client message: `{0}`
5151
InvalidMsgUpdateClientId(ValidationError),
52+
/// Encode error: `{0}`
53+
Encode(TendermintProtoError),
5254
/// decode error: `{0}`
5355
Decode(prost::DecodeError),
5456
/// invalid client identifier error: `{0}`

0 commit comments

Comments
 (0)