Skip to content

Commit

Permalink
Upgrade client (#857)
Browse files Browse the repository at this point in the history
* handler and minor unit tests

* Update upgrade_client.rs

* Update client_def.rs

* work in progress

* test in ICS26

* fmt

* error update

* fmt + clippy

* Fix clippy, fmt, my review suggestions.

* Update test_utils.rs

Co-authored-by: Adi Seredinschi <adi@informal.systems>
  • Loading branch information
cezarad and adizere authored Jun 7, 2021
1 parent 3b2c309 commit 028616e
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 35 deletions.
61 changes: 61 additions & 0 deletions modules/src/ics02_client/client_def.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use ibc_proto::ibc::core::commitment::v1::MerkleProof;

use crate::downcast;
use crate::ics02_client::client_consensus::{AnyConsensusState, ConsensusState};
use crate::ics02_client::client_state::{AnyClientState, ClientState};
Expand Down Expand Up @@ -27,6 +29,14 @@ pub trait ClientDef: Clone {
header: Self::Header,
) -> Result<(Self::ClientState, Self::ConsensusState), Box<dyn std::error::Error>>;

fn verify_upgrade_and_update_state(
&self,
client_state: &Self::ClientState,
consensus_state: &Self::ConsensusState,
proof_upgrade_client: MerkleProof,
proof_upgrade_consensus_state: MerkleProof,
) -> Result<(Self::ClientState, Self::ConsensusState), Box<dyn std::error::Error>>;

/// Verification functions as specified in:
/// https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics
///
Expand Down Expand Up @@ -565,4 +575,55 @@ impl ClientDef for AnyClient {
}
}
}

fn verify_upgrade_and_update_state(
&self,
client_state: &Self::ClientState,
consensus_state: &Self::ConsensusState,
proof_upgrade_client: MerkleProof,
proof_upgrade_consensus_state: MerkleProof,
) -> Result<(Self::ClientState, Self::ConsensusState), Box<dyn std::error::Error>> {
match self {
Self::Tendermint(client) => {
let (client_state, consensus_state) = downcast!(
client_state => AnyClientState::Tendermint,
consensus_state => AnyConsensusState::Tendermint,
)
.ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?;

let (new_state, new_consensus) = client.verify_upgrade_and_update_state(
client_state,
consensus_state,
proof_upgrade_client,
proof_upgrade_consensus_state,
)?;

Ok((
AnyClientState::Tendermint(new_state),
AnyConsensusState::Tendermint(new_consensus),
))
}

#[cfg(any(test, feature = "mocks"))]
Self::Mock(client) => {
let (client_state, consensus_state) = downcast!(
client_state => AnyClientState::Mock,
consensus_state => AnyConsensusState::Mock,
)
.ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?;

let (new_state, new_consensus) = client.verify_upgrade_and_update_state(
client_state,
consensus_state,
proof_upgrade_client,
proof_upgrade_consensus_state,
)?;

Ok((
AnyClientState::Mock(new_state),
AnyConsensusState::Mock(new_consensus),
))
}
}
}
}
10 changes: 9 additions & 1 deletion modules/src/ics02_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ pub trait ClientKeeper {
)?;
Ok(())
}
Upgrade(_) => unimplemented!(),
Upgrade(res) => {
self.store_client_state(res.client_id.clone(), res.client_state.clone())?;
self.store_consensus_state(
res.client_id.clone(),
res.client_state.latest_height(),
res.consensus_state,
)?;
Ok(())
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions modules/src/ics02_client/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ pub enum Kind {
state_type: ClientType,
consensus_type: ClientType,
},

#[error("upgrade verification failed")]
UpgradeVerificationFailure,

#[error("upgraded client height {0} must be at greater than current client height {1}")]
LowUpgradeHeight(Height, Height),
}

impl Kind {
Expand Down
3 changes: 3 additions & 0 deletions modules/src/ics02_client/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ impl UpgradeClient {
pub fn set_height(&mut self, height: Height) {
self.0.height = height;
}
pub fn client_id(&self) -> &ClientId {
&self.0.client_id
}
}

impl From<Attributes> for UpgradeClient {
Expand Down
203 changes: 187 additions & 16 deletions modules/src/ics02_client/handler/upgrade_client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//! Protocol logic specific to processing ICS2 messages of type `MsgUpgradeAnyClient`.
//!
use crate::handler::HandlerResult;
use crate::events::IbcEvent;
use crate::handler::{HandlerOutput, HandlerResult};
use crate::ics02_client::client_consensus::AnyConsensusState;
use crate::ics02_client::client_def::{AnyClient, ClientDef};
use crate::ics02_client::client_state::AnyClientState;
use crate::ics02_client::client_state::ClientState;
use crate::ics02_client::context::ClientReader;
use crate::ics02_client::error::{Error, Kind};
use crate::ics02_client::events::Attributes;
use crate::ics02_client::handler::ClientResult;
use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient;
use crate::ics24_host::identifier::ClientId;
Expand All @@ -23,6 +26,7 @@ pub fn process(
ctx: &dyn ClientReader,
msg: MsgUpgradeAnyClient,
) -> HandlerResult<ClientResult, Error> {
let mut output = HandlerOutput::builder();
let MsgUpgradeAnyClient { client_id, .. } = msg;

// Read client state from the host chain store.
Expand All @@ -34,20 +38,187 @@ pub fn process(
return Err(Kind::ClientFrozen(client_id).into());
}

let upgrade_client_state = msg.client_state.clone();

if client_state.latest_height() >= upgrade_client_state.latest_height() {
return Err(Kind::LowUpgradeHeight(
client_state.latest_height(),
upgrade_client_state.latest_height(),
)
.into());
}

let client_type = ctx
.client_type(&client_id)
.ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?;

let client_def = AnyClient::from_client_type(client_type);

let (new_client_state, new_consensus_state) = client_def
.verify_upgrade_and_update_state(
&upgrade_client_state,
&msg.consensus_state,
msg.proof_upgrade_client.clone(),
msg.proof_upgrade_consensus_state,
)
.map_err(|e| Kind::UpgradeVerificationFailure.context(e.to_string()))?;

// Not implemented yet: https://github.com/informalsystems/ibc-rs/issues/722
todo!()

// let result = ClientResult::Upgrade(Result {
// client_id: client_id.clone(),
// client_state: new_client_state,
// consensus_state: new_consensus_state,
// });
//
// let event_attributes = Attributes {
// client_id,
// ..Default::default()
// };
// output.emit(IbcEvent::UpgradeClient(event_attributes.into()));
//
// Ok(output.with_result(result))
// todo!()

let result = ClientResult::Upgrade(Result {
client_id: client_id.clone(),
client_state: new_client_state,
consensus_state: new_consensus_state,
});
let event_attributes = Attributes {
client_id,
..Default::default()
};

output.emit(IbcEvent::UpgradeClient(event_attributes.into()));
Ok(output.with_result(result))
}

#[cfg(test)]
mod tests {
use std::{convert::TryFrom, str::FromStr};

use crate::events::IbcEvent;
use crate::handler::HandlerOutput;
use crate::ics02_client::error::Kind;
use crate::ics02_client::handler::dispatch;
use crate::ics02_client::handler::ClientResult::Upgrade;
use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient;
use crate::ics02_client::msgs::ClientMsg;
use crate::ics23_commitment::commitment::CommitmentProofBytes;
use crate::ics24_host::identifier::ClientId;
use crate::mock::client_state::{MockClientState, MockConsensusState};
use crate::mock::context::MockContext;
use crate::mock::header::MockHeader;
use crate::test_utils::get_dummy_account_id;
use crate::Height;
use ibc_proto::ibc::core::commitment::v1::MerkleProof;

#[test]
fn test_upgrade_client_ok() {
let client_id = ClientId::default();
let signer = get_dummy_account_id();

let ctx = MockContext::default().with_client(&client_id, Height::new(0, 42));

let buf: Vec<u8> = Vec::new();
let buf2: Vec<u8> = Vec::new();

let c_bytes = CommitmentProofBytes::from(buf);
let cs_bytes = CommitmentProofBytes::from(buf2);

let msg = MsgUpgradeAnyClient {
client_id: client_id.clone(),
client_state: MockClientState(MockHeader::new(Height::new(1, 26))).into(),
consensus_state: MockConsensusState(MockHeader::new(Height::new(1, 26))).into(),
proof_upgrade_client: MerkleProof::try_from(c_bytes).unwrap(),
proof_upgrade_consensus_state: MerkleProof::try_from(cs_bytes).unwrap(),
signer,
};

let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone()));

match output {
Ok(HandlerOutput {
result,
mut events,
log,
}) => {
assert_eq!(events.len(), 1);
let event = events.pop().unwrap();
assert!(
matches!(event, IbcEvent::UpgradeClient(e) if e.client_id() == &msg.client_id)
);
assert!(log.is_empty());
// Check the result
match result {
Upgrade(upg_res) => {
assert_eq!(upg_res.client_id, client_id);
assert_eq!(upg_res.client_state, msg.client_state)
}
_ => panic!("upgrade handler result has incorrect type"),
}
}
Err(err) => {
panic!("unexpected error: {}", err);
}
}
}

#[test]
fn test_upgrade_nonexisting_client() {
let client_id = ClientId::from_str("mockclient1").unwrap();
let signer = get_dummy_account_id();

let ctx = MockContext::default().with_client(&client_id, Height::new(0, 42));

let buf: Vec<u8> = Vec::new();
let buf2: Vec<u8> = Vec::new();

let c_bytes = CommitmentProofBytes::from(buf);
let cs_bytes = CommitmentProofBytes::from(buf2);

let msg = MsgUpgradeAnyClient {
client_id: ClientId::from_str("nonexistingclient").unwrap(),
client_state: MockClientState(MockHeader::new(Height::new(1, 26))).into(),
consensus_state: MockConsensusState(MockHeader::new(Height::new(1, 26))).into(),
proof_upgrade_client: MerkleProof::try_from(c_bytes).unwrap(),
proof_upgrade_consensus_state: MerkleProof::try_from(cs_bytes).unwrap(),
signer,
};

let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone()));

match output {
Ok(_) => {
panic!("unexpected success (expected error)");
}
Err(err) => {
assert_eq!(err.kind(), &Kind::ClientNotFound(msg.client_id));
}
}
}

#[test]
fn test_upgrade_client_low_height() {
let client_id = ClientId::default();
let signer = get_dummy_account_id();

let ctx = MockContext::default().with_client(&client_id, Height::new(0, 42));

let buf: Vec<u8> = Vec::new();
let buf2: Vec<u8> = Vec::new();

let c_bytes = CommitmentProofBytes::from(buf);
let cs_bytes = CommitmentProofBytes::from(buf2);

let msg = MsgUpgradeAnyClient {
client_id,
client_state: MockClientState(MockHeader::new(Height::new(0, 26))).into(),
consensus_state: MockConsensusState(MockHeader::new(Height::new(0, 26))).into(),
proof_upgrade_client: MerkleProof::try_from(c_bytes).unwrap(),
proof_upgrade_consensus_state: MerkleProof::try_from(cs_bytes).unwrap(),
signer,
};

let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone()));

match output {
Ok(_) => {
panic!("unexpected success (expected error)");
}
Err(err) => {
assert_eq!(
err.kind(),
&Kind::LowUpgradeHeight(Height::new(0, 42), msg.client_state.latest_height())
);
}
}
}
}
Loading

0 comments on commit 028616e

Please sign in to comment.