Skip to content

Commit 9d19cc9

Browse files
yito88seanchen1991
andauthored
Fix upgrade path check (informalsystems#1298)
* fix upgrade_path prefix * fix conversion * add more check * user-defined upgrade path * fix a test * Update ibc-clients/ics07-tendermint/src/client_state/common.rs Co-authored-by: Sean Chen <[email protected]> Signed-off-by: Yuji Ito <[email protected]> * Update ibc-clients/ics07-tendermint/src/client_state/common.rs Co-authored-by: Sean Chen <[email protected]> Signed-off-by: Yuji Ito <[email protected]> * fmt * disallow empty upgrade_path * remove unused import * add InvalidUpgradePath * add changelogs --------- Signed-off-by: Yuji Ito <[email protected]> Co-authored-by: Sean Chen <[email protected]>
1 parent 966d0de commit 9d19cc9

File tree

9 files changed

+194
-65
lines changed

9 files changed

+194
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- [ibc-client-tendermint] Fix client verification panic on upgrades when the
2+
`upgrade_path` size is 1.
3+
([\#1297](https://github.com/cosmos/ibc-rs/issues/1297))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- [ibc-client-tendermint] Use the user-defined upgrade path for client upgrades,
2+
instead of defaulting to `UPGRADED_IBC_STATE`.
3+
([\#1303](https://github.com/cosmos/ibc-rs/issues/1303))

ibc-clients/ics07-tendermint/src/client_state/common.rs

+44-21
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use ibc_core_commitment_types::merkle::{MerklePath, MerkleProof};
1313
use ibc_core_commitment_types::proto::ics23::{HostFunctionsManager, HostFunctionsProvider};
1414
use ibc_core_commitment_types::specs::ProofSpecs;
1515
use ibc_core_host::types::identifiers::ClientType;
16-
use ibc_core_host::types::path::{Path, PathBytes, UpgradeClientPath};
16+
use ibc_core_host::types::path::{
17+
Path, PathBytes, UpgradeClientStatePath, UpgradeConsensusStatePath,
18+
};
1719
use ibc_primitives::prelude::*;
1820
use ibc_primitives::proto::Any;
1921
use ibc_primitives::{Timestamp, ToVec};
@@ -56,20 +58,51 @@ impl ClientStateCommon for ClientState {
5658
) -> Result<(), ClientError> {
5759
let last_height = self.latest_height().revision_height();
5860

59-
let upgrade_client_path_bytes = self.serialize_path(Path::UpgradeClient(
60-
UpgradeClientPath::UpgradedClientState(last_height),
61-
))?;
62-
63-
let upgrade_consensus_path_bytes = self.serialize_path(Path::UpgradeClient(
64-
UpgradeClientPath::UpgradedClientConsensusState(last_height),
65-
))?;
61+
// The client state's upgrade path vector needs to parsed into a tuple in the form
62+
// of `(upgrade_path_prefix, upgrade_path)`. Given the length of the client
63+
// state's upgrade path vector, the following determinations are made:
64+
// 1: The commitment prefix is left empty and the upgrade path is used as-is.
65+
// 2: The commitment prefix and upgrade path are both taken as-is.
66+
let upgrade_path = &self.inner().upgrade_path;
67+
let (upgrade_path_prefix, upgrade_path) = match upgrade_path.len() {
68+
0 => {
69+
return Err(UpgradeClientError::InvalidUpgradePath {
70+
reason: "no upgrade path has been set".to_string(),
71+
}
72+
.into());
73+
}
74+
1 => (CommitmentPrefix::empty(), upgrade_path[0].clone()),
75+
2 => (
76+
upgrade_path[0].as_bytes().to_vec().into(),
77+
upgrade_path[1].clone(),
78+
),
79+
_ => {
80+
return Err(UpgradeClientError::InvalidUpgradePath {
81+
reason: "upgrade path is too long".to_string(),
82+
}
83+
.into());
84+
}
85+
};
86+
87+
let upgrade_client_path_bytes =
88+
self.serialize_path(Path::UpgradeClientState(UpgradeClientStatePath {
89+
upgrade_path: upgrade_path.clone(),
90+
height: last_height,
91+
}))?;
92+
93+
let upgrade_consensus_path_bytes =
94+
self.serialize_path(Path::UpgradeConsensusState(UpgradeConsensusStatePath {
95+
upgrade_path,
96+
height: last_height,
97+
}))?;
6698

6799
verify_upgrade_client::<HostFunctionsManager>(
68100
self.inner(),
69101
upgraded_client_state,
70102
upgraded_consensus_state,
71103
proof_upgrade_client,
72104
proof_upgrade_consensus_state,
105+
upgrade_path_prefix,
73106
upgrade_client_path_bytes,
74107
upgrade_consensus_path_bytes,
75108
root,
@@ -207,6 +240,7 @@ pub fn verify_upgrade_client<H: HostFunctionsProvider>(
207240
upgraded_consensus_state: Any,
208241
proof_upgrade_client: CommitmentProofBytes,
209242
proof_upgrade_consensus_state: CommitmentProofBytes,
243+
upgrade_path_prefix: CommitmentPrefix,
210244
upgrade_client_path_bytes: PathBytes,
211245
upgrade_consensus_path_bytes: PathBytes,
212246
root: &CommitmentRoot,
@@ -225,22 +259,11 @@ pub fn verify_upgrade_client<H: HostFunctionsProvider>(
225259
// the height
226260
if latest_height >= upgraded_tm_client_state_height {
227261
Err(UpgradeClientError::LowUpgradeHeight {
228-
upgraded_height: latest_height,
229-
client_height: upgraded_tm_client_state_height,
262+
upgraded_height: upgraded_tm_client_state_height,
263+
client_height: latest_height,
230264
})?
231265
}
232266

233-
// Check to see if the upgrade path is set
234-
let mut upgrade_path = client_state.upgrade_path.clone();
235-
236-
if upgrade_path.pop().is_none() {
237-
return Err(ClientError::ClientSpecific {
238-
description: "cannot upgrade client as no upgrade path has been set".to_string(),
239-
});
240-
};
241-
242-
let upgrade_path_prefix = CommitmentPrefix::from(upgrade_path[0].clone().into_bytes());
243-
244267
// Verify the proof of the upgraded client state
245268
verify_membership::<H>(
246269
&client_state.proof_specs,

ibc-core/ics02-client/types/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ pub enum UpgradeClientError {
148148
upgraded_height: Height,
149149
client_height: Height,
150150
},
151+
/// Invalid upgrade path: `{reason}`
152+
InvalidUpgradePath { reason: String },
151153
/// invalid upgrade proposal: `{reason}`
152154
InvalidUpgradeProposal { reason: String },
153155
/// invalid upgrade plan: `{reason}`

ibc-core/ics24-host/cosmos/src/upgrade_proposal/context.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
use ibc_core_client_context::ClientValidationContext;
99
use ibc_core_client_types::error::UpgradeClientError;
10-
use ibc_core_host_types::path::UpgradeClientPath;
10+
use ibc_core_host_types::path::{UpgradeClientStatePath, UpgradeConsensusStatePath};
1111

1212
use super::Plan;
1313

@@ -28,13 +28,13 @@ pub trait UpgradeValidationContext {
2828
/// Returns the upgraded client state at the specified upgrade path.
2929
fn upgraded_client_state(
3030
&self,
31-
upgrade_path: &UpgradeClientPath,
31+
upgrade_path: &UpgradeClientStatePath,
3232
) -> Result<UpgradedClientStateRef<Self>, UpgradeClientError>;
3333

3434
/// Returns the upgraded consensus state at the specified upgrade path.
3535
fn upgraded_consensus_state(
3636
&self,
37-
upgrade_path: &UpgradeClientPath,
37+
upgrade_path: &UpgradeConsensusStatePath,
3838
) -> Result<UpgradedConsensusStateRef<Self>, UpgradeClientError>;
3939
}
4040

@@ -50,14 +50,14 @@ pub trait UpgradeExecutionContext: UpgradeValidationContext {
5050
/// Stores the upgraded client state at the specified upgrade path.
5151
fn store_upgraded_client_state(
5252
&mut self,
53-
upgrade_path: UpgradeClientPath,
53+
upgrade_path: UpgradeClientStatePath,
5454
client_state: UpgradedClientStateRef<Self>,
5555
) -> Result<(), UpgradeClientError>;
5656

5757
/// Stores the upgraded consensus state at the specified upgrade path.
5858
fn store_upgraded_consensus_state(
5959
&mut self,
60-
upgrade_path: UpgradeClientPath,
60+
upgrade_path: UpgradeConsensusStatePath,
6161
consensus_state: UpgradedConsensusStateRef<Self>,
6262
) -> Result<(), UpgradeClientError>;
6363
}

ibc-core/ics24-host/cosmos/src/upgrade_proposal/handler.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ibc_client_tendermint::types::ClientState as TmClientState;
22
use ibc_core_client_types::error::UpgradeClientError;
3-
use ibc_core_host_types::path::UpgradeClientPath;
3+
use ibc_core_host_types::path::UpgradeClientStatePath;
44
use ibc_primitives::prelude::*;
55
use tendermint::abci::Event as TmEvent;
66

@@ -37,7 +37,7 @@ where
3737

3838
ctx.schedule_upgrade(plan.clone())?;
3939

40-
let upgraded_client_state_path = UpgradeClientPath::UpgradedClientState(plan.height);
40+
let upgraded_client_state_path = UpgradeClientStatePath::new_with_default_path(plan.height);
4141

4242
ctx.store_upgraded_client_state(upgraded_client_state_path, client_state.into())?;
4343

ibc-core/ics24-host/types/src/path.rs

+104-29
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ pub enum Path {
9999
Commitment(CommitmentPath),
100100
Ack(AckPath),
101101
Receipt(ReceiptPath),
102-
UpgradeClient(UpgradeClientPath),
102+
UpgradeClientState(UpgradeClientStatePath),
103+
UpgradeConsensusState(UpgradeConsensusStatePath),
103104
}
104105

105106
#[cfg_attr(
@@ -693,13 +694,51 @@ impl ReceiptPath {
693694
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
694695
)]
695696
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
696-
/// Paths that are specific for client upgrades.
697697
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
698-
pub enum UpgradeClientPath {
699-
#[display(fmt = "{UPGRADED_IBC_STATE}/{_0}/{UPGRADED_CLIENT_STATE}")]
700-
UpgradedClientState(u64),
701-
#[display(fmt = "{UPGRADED_IBC_STATE}/{_0}/{UPGRADED_CLIENT_CONSENSUS_STATE}")]
702-
UpgradedClientConsensusState(u64),
698+
#[display(fmt = "{upgrade_path}/{height}/{UPGRADED_CLIENT_STATE}")]
699+
pub struct UpgradeClientStatePath {
700+
pub upgrade_path: String,
701+
pub height: u64,
702+
}
703+
704+
impl UpgradeClientStatePath {
705+
/// Create with the default upgrade path
706+
pub fn new_with_default_path(height: u64) -> Self {
707+
Self {
708+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
709+
height,
710+
}
711+
}
712+
}
713+
714+
#[cfg_attr(
715+
feature = "parity-scale-codec",
716+
derive(
717+
parity_scale_codec::Encode,
718+
parity_scale_codec::Decode,
719+
scale_info::TypeInfo
720+
)
721+
)]
722+
#[cfg_attr(
723+
feature = "borsh",
724+
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
725+
)]
726+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
727+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
728+
#[display(fmt = "{upgrade_path}/{height}/{UPGRADED_CLIENT_CONSENSUS_STATE}")]
729+
pub struct UpgradeConsensusStatePath {
730+
pub upgrade_path: String,
731+
pub height: u64,
732+
}
733+
734+
impl UpgradeConsensusStatePath {
735+
/// Create with the default upgrade path
736+
pub fn new_with_default_path(height: u64) -> Self {
737+
Self {
738+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
739+
height,
740+
}
741+
}
703742
}
704743

705744
#[cfg_attr(
@@ -760,7 +799,8 @@ impl FromStr for Path {
760799
.or_else(|| parse_commitments(&components))
761800
.or_else(|| parse_acks(&components))
762801
.or_else(|| parse_receipts(&components))
763-
.or_else(|| parse_upgrades(&components))
802+
.or_else(|| parse_upgrade_client_state(&components))
803+
.or_else(|| parse_upgrade_consensus_state(&components))
764804
.ok_or(PathError::ParseFailure {
765805
path: s.to_string(),
766806
})
@@ -1084,28 +1124,52 @@ fn parse_receipts(components: &[&str]) -> Option<Path> {
10841124
)
10851125
}
10861126

1087-
fn parse_upgrades(components: &[&str]) -> Option<Path> {
1127+
fn parse_upgrade_client_state(components: &[&str]) -> Option<Path> {
10881128
if components.len() != 3 {
10891129
return None;
10901130
}
10911131

1092-
let first = *components.first()?;
1132+
let last = *components.last()?;
10931133

1094-
if first != UPGRADED_IBC_STATE {
1134+
if last != UPGRADED_CLIENT_STATE {
10951135
return None;
10961136
}
10971137

1098-
let last = *components.last()?;
1138+
let upgrade_path = components.first()?.to_string();
10991139

1100-
let height = components[1].parse::<u64>().ok()?;
1140+
let height = u64::from_str(components[1]).ok()?;
11011141

1102-
match last {
1103-
UPGRADED_CLIENT_STATE => Some(UpgradeClientPath::UpgradedClientState(height).into()),
1104-
UPGRADED_CLIENT_CONSENSUS_STATE => {
1105-
Some(UpgradeClientPath::UpgradedClientConsensusState(height).into())
1142+
Some(
1143+
UpgradeClientStatePath {
1144+
upgrade_path,
1145+
height,
11061146
}
1107-
_ => None,
1147+
.into(),
1148+
)
1149+
}
1150+
1151+
fn parse_upgrade_consensus_state(components: &[&str]) -> Option<Path> {
1152+
if components.len() != 3 {
1153+
return None;
1154+
}
1155+
1156+
let last = *components.last()?;
1157+
1158+
if last != UPGRADED_CLIENT_CONSENSUS_STATE {
1159+
return None;
11081160
}
1161+
1162+
let upgrade_path = components.first()?.to_string();
1163+
1164+
let height = u64::from_str(components[1]).ok()?;
1165+
1166+
Some(
1167+
UpgradeConsensusStatePath {
1168+
upgrade_path,
1169+
height,
1170+
}
1171+
.into(),
1172+
)
11091173
}
11101174

11111175
#[cfg(test)]
@@ -1207,11 +1271,17 @@ mod tests {
12071271
)]
12081272
#[case(
12091273
"upgradedIBCState/0/upgradedClient",
1210-
Path::UpgradeClient(UpgradeClientPath::UpgradedClientState(0))
1274+
Path::UpgradeClientState(UpgradeClientStatePath {
1275+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
1276+
height: 0,
1277+
})
12111278
)]
12121279
#[case(
12131280
"upgradedIBCState/0/upgradedConsState",
1214-
Path::UpgradeClient(UpgradeClientPath::UpgradedClientConsensusState(0))
1281+
Path::UpgradeConsensusState(UpgradeConsensusStatePath {
1282+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
1283+
height: 0,
1284+
})
12151285
)]
12161286
fn test_successful_parsing(#[case] path_str: &str, #[case] path: Path) {
12171287
// can be parsed into Path
@@ -1419,25 +1489,30 @@ mod tests {
14191489
}
14201490

14211491
#[test]
1422-
fn test_parse_upgrades_fn() {
1492+
fn test_parse_upgrade_client_state_fn() {
14231493
let path = "upgradedIBCState/0/upgradedClient";
14241494
let components: Vec<&str> = path.split('/').collect();
14251495

14261496
assert_eq!(
1427-
parse_upgrades(&components),
1428-
Some(Path::UpgradeClient(UpgradeClientPath::UpgradedClientState(
1429-
0
1430-
))),
1497+
parse_upgrade_client_state(&components),
1498+
Some(Path::UpgradeClientState(UpgradeClientStatePath {
1499+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
1500+
height: 0,
1501+
})),
14311502
);
1503+
}
14321504

1505+
#[test]
1506+
fn test_parse_upgrade_consensus_state_fn() {
14331507
let path = "upgradedIBCState/0/upgradedConsState";
14341508
let components: Vec<&str> = path.split('/').collect();
14351509

14361510
assert_eq!(
1437-
parse_upgrades(&components),
1438-
Some(Path::UpgradeClient(
1439-
UpgradeClientPath::UpgradedClientConsensusState(0)
1440-
)),
1511+
parse_upgrade_consensus_state(&components),
1512+
Some(Path::UpgradeConsensusState(UpgradeConsensusStatePath {
1513+
upgrade_path: UPGRADED_IBC_STATE.to_string(),
1514+
height: 0,
1515+
})),
14411516
)
14421517
}
14431518
}

0 commit comments

Comments
 (0)