use super::{
client_type as sm_client_type, ExecutionContext as SmExecutionContext,
ValidationContext as SmValidationContext,
};
use crate::consensus_state::ConsensusState as SmConsensusState;
use crate::cosmos::crypto::PublicKey;
use crate::error::Error;
use crate::header::Header as SmHeader;
use crate::misbehaviour::Misbehaviour as SmMisbehaviour;
use crate::prelude::*;
use crate::proof::types::sign_bytes::SignBytes;
use crate::proof::types::signature_and_data::SignatureAndData;
use crate::proof::types::timestamped_signature_data::TimestampedSignatureData;
use crate::proof::verify_signature;
use ibc::core::ics02_client::client_state::Status;
use ibc::core::ics02_client::client_state::{
ClientStateCommon, ClientStateExecution, ClientStateValidation, UpdateKind,
};
use ibc::core::ics02_client::client_type::ClientType;
use ibc::core::ics02_client::consensus_state::ConsensusState;
use ibc::core::ics02_client::error::ClientError;
use ibc::core::ics02_client::ClientExecutionContext;
use ibc::core::ics23_commitment::commitment::{
CommitmentPrefix, CommitmentProofBytes, CommitmentRoot,
};
use ibc::core::ics23_commitment::merkle::apply_prefix;
use ibc::core::ics24_host::identifier::ClientId;
use ibc::core::ics24_host::path::Path;
use ibc::core::ics24_host::path::{ClientConsensusStatePath, ClientStatePath};
use ibc::core::timestamp::Timestamp;
use ibc::Height;
use ibc_proto::google::protobuf::Any;
use ibc_proto::ibc::lightclients::solomachine::v3::ClientState as RawSmClientState;
use ibc_proto::protobuf::Protobuf;
use prost::Message;
pub mod misbehaviour;
pub mod update_client;
pub const SOLOMACHINE_CLIENT_STATE_TYPE_URL: &str = "/ibc.lightclients.solomachine.v3.ClientState";
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Debug)]
pub struct ClientState {
pub sequence: Height,
pub is_frozen: bool,
pub consensus_state: SmConsensusState,
}
impl ClientStateCommon for ClientState {
fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError> {
let tm_consensus_state = SmConsensusState::try_from(consensus_state)?;
if tm_consensus_state.root().is_empty() {
return Err(ClientError::Other {
description: "empty commitment root".into(),
});
};
Ok(())
}
fn client_type(&self) -> ClientType {
sm_client_type()
}
fn latest_height(&self) -> Height {
self.latest_height()
}
fn validate_proof_height(&self, proof_height: Height) -> Result<(), ClientError> {
if self.latest_height() < proof_height {
return Err(ClientError::InvalidProofHeight {
latest_height: self.latest_height(),
proof_height,
});
}
Ok(())
}
fn verify_upgrade_client(
&self,
_upgraded_client_state: Any,
_upgraded_consensus_state: Any,
_proof_upgrade_client: CommitmentProofBytes,
_proof_upgrade_consensus_state: CommitmentProofBytes,
_root: &CommitmentRoot,
) -> Result<(), ClientError> {
Ok(())
}
fn verify_membership(
&self,
prefix: &CommitmentPrefix,
proof: &CommitmentProofBytes,
_root: &CommitmentRoot,
path: Path,
value: Vec<u8>,
) -> Result<(), ClientError> {
let (public_key, sig_data, timestamp, sequence) = self.produce_verification_args(proof)?;
let merkle_path = apply_prefix(prefix, vec![path.to_string()]);
if merkle_path.key_path.is_empty() {
return Err(ClientError::Other {
description: "path is empty".to_string(),
});
}
let sign_bytes = SignBytes {
sequence,
timestamp: timestamp.nanoseconds(),
diversifier: self.consensus_state.diversifier.clone(),
path: merkle_path,
data: value,
};
let sign_bz = sign_bytes.encode_vec();
verify_signature(public_key, sign_bz, sig_data).map_err(|e| ClientError::Other {
description: e.to_string(),
})
}
fn verify_non_membership(
&self,
prefix: &CommitmentPrefix,
proof: &CommitmentProofBytes,
_root: &CommitmentRoot,
path: Path,
) -> Result<(), ClientError> {
let (public_key, sig_data, timestamp, sequence) = self.produce_verification_args(proof)?;
let merkle_path = apply_prefix(prefix, vec![path.to_string()]);
if merkle_path.key_path.is_empty() {
return Err(ClientError::Other {
description: "path is empty".to_string(),
});
}
let sign_bytes = SignBytes {
sequence,
timestamp: timestamp.nanoseconds(),
diversifier: self.consensus_state.diversifier.clone(),
path: merkle_path,
data: vec![],
};
let sign_bz = sign_bytes.encode_vec();
verify_signature(public_key, sign_bz, sig_data).map_err(|e| ClientError::Other {
description: e.to_string(),
})
}
}
impl<ClientValidationContext> ClientStateValidation<ClientValidationContext> for ClientState
where
ClientValidationContext: SmValidationContext,
{
fn verify_client_message(
&self,
ctx: &ClientValidationContext,
client_id: &ClientId,
client_message: Any,
update_kind: &UpdateKind,
) -> Result<(), ClientError> {
match update_kind {
UpdateKind::UpdateClient => {
let header = SmHeader::try_from(client_message)?;
self.verify_header(ctx, client_id, header)
}
UpdateKind::SubmitMisbehaviour => {
let misbehaviour = SmMisbehaviour::try_from(client_message)?;
self.verify_misbehaviour(ctx, client_id, misbehaviour)
}
}
}
fn check_for_misbehaviour(
&self,
ctx: &ClientValidationContext,
client_id: &ClientId,
client_message: Any,
update_kind: &UpdateKind,
) -> Result<bool, ClientError> {
match update_kind {
UpdateKind::UpdateClient => {
let header = SmHeader::try_from(client_message)?;
self.check_for_misbehaviour_update_client(ctx, client_id, header)
}
UpdateKind::SubmitMisbehaviour => {
let misbehaviour = SmMisbehaviour::try_from(client_message)?;
self.check_for_misbehaviour_misbehavior(&misbehaviour)
}
}
}
fn status(
&self,
_ctx: &ClientValidationContext,
_client_id: &ClientId,
) -> Result<Status, ClientError> {
if self.is_frozen {
return Ok(Status::Frozen);
}
Ok(Status::Active)
}
}
impl<E> ClientStateExecution<E> for ClientState
where
E: SmExecutionContext,
<E as ClientExecutionContext>::AnyClientState: From<ClientState>,
<E as ClientExecutionContext>::AnyConsensusState: From<SmConsensusState>,
{
fn initialise(
&self,
ctx: &mut E,
client_id: &ClientId,
consensus_state: Any,
) -> Result<(), ClientError> {
let sm_consensus_state = SmConsensusState::try_from(consensus_state)?;
ctx.store_client_state(ClientStatePath::new(client_id), self.clone().into())?;
ctx.store_consensus_state(
ClientConsensusStatePath::new(client_id, &self.latest_height()),
sm_consensus_state.into(),
)?;
Ok(())
}
fn update_state(
&self,
ctx: &mut E,
client_id: &ClientId,
header: Any,
) -> Result<Vec<Height>, ClientError> {
let sm_header = SmHeader::try_from(header).map_err(|e| ClientError::Other {
description: format!("decode SmHeader Error({})", e),
})?;
let consensus_state = SmConsensusState::new(
sm_header.new_public_key,
sm_header.new_diversifier,
sm_header.timestamp,
);
let mut new_client_state = self.clone();
new_client_state.sequence.increment();
let new_height = new_client_state.sequence;
new_client_state.consensus_state = consensus_state;
ctx.store_client_state(ClientStatePath::new(client_id), new_client_state.into())?;
Ok(vec![new_height])
}
fn update_state_on_misbehaviour(
&self,
ctx: &mut E,
client_id: &ClientId,
_client_message: Any,
_update_kind: &UpdateKind,
) -> Result<(), ClientError> {
let frozen_client_state = self.clone().with_frozen_height(Height::min(0));
ctx.store_client_state(ClientStatePath::new(client_id), frozen_client_state.into())?;
Ok(())
}
fn update_state_on_upgrade(
&self,
_ctx: &mut E,
_client_id: &ClientId,
_upgraded_client_state: Any,
_upgraded_consensus_state: Any,
) -> Result<Height, ClientError> {
Err(ClientError::Other {
description: "No implement".to_string(),
})
}
}
impl ClientState {
pub fn new(sequence: Height, is_frozen: bool, consensus_state: SmConsensusState) -> Self {
Self {
sequence,
is_frozen,
consensus_state,
}
}
pub fn with_frozen(self) -> Self {
Self {
is_frozen: true,
..self
}
}
pub fn with_frozen_height(self, h: Height) -> Self {
Self {
sequence: h,
..self
}
}
pub fn latest_height(&self) -> Height {
self.sequence
}
pub fn time_stamp(&self) -> Timestamp {
self.consensus_state.timestamp
}
pub fn valida_basic(&self) -> Result<(), Error> {
if self.sequence.revision_height() == 0 {
return Err(Error::SequenceCannotZero);
}
self.consensus_state.valida_basic()
}
pub fn produce_verification_args(
&self,
proof: &CommitmentProofBytes,
) -> Result<(PublicKey, SignatureAndData, Timestamp, u64), Error> {
let proof = Vec::<u8>::from(proof.clone());
if proof.is_empty() {
return Err(Error::Other("proof cannot be empty".into()));
}
let timestamped_sig_data = TimestampedSignatureData::decode_vec(&proof).map_err(|e| {
Error::Other(format!(
"failed to decode proof into type TimestampedSignatureData: {}",
e
))
})?;
let timestamp = timestamped_sig_data.timestamp;
let signature_and_data = timestamped_sig_data.signature_data;
if self.consensus_state.timestamp > timestamp {
return Err(Error::Other(format!(
"the consensus state timestamp is greater than the signature timestamp ({} >= {})",
self.consensus_state.timestamp, timestamp
)));
}
let latest_sequence = self.sequence.revision_height();
let public_key = self.consensus_state.public_key();
Ok((public_key, signature_and_data, timestamp, latest_sequence))
}
}
impl Protobuf<RawSmClientState> for ClientState {}
impl TryFrom<RawSmClientState> for ClientState {
type Error = Error;
fn try_from(raw: RawSmClientState) -> Result<Self, Self::Error> {
let sequence = Height::new(0, raw.sequence).map_err(Error::InvalidHeight)?;
let consensus_state: SmConsensusState = raw
.consensus_state
.ok_or(Error::ConsensusStateIsEmpty)?
.try_into()?;
Ok(Self {
sequence,
is_frozen: raw.is_frozen,
consensus_state,
})
}
}
impl From<ClientState> for RawSmClientState {
fn from(value: ClientState) -> Self {
Self {
sequence: value.sequence.revision_height(),
is_frozen: value.is_frozen,
consensus_state: Some(value.consensus_state.into()),
}
}
}
impl Protobuf<Any> for ClientState {}
impl TryFrom<Any> for ClientState {
type Error = ClientError;
fn try_from(raw: Any) -> Result<Self, Self::Error> {
use bytes::Buf;
use core::ops::Deref;
fn decode_client_state<B: Buf>(buf: B) -> Result<ClientState, Error> {
RawSmClientState::decode(buf)
.map_err(Error::Decode)?
.try_into()
}
match raw.type_url.as_str() {
SOLOMACHINE_CLIENT_STATE_TYPE_URL => {
decode_client_state(raw.value.deref()).map_err(Into::into)
}
_ => Err(ClientError::UnknownClientStateType {
client_state_type: raw.type_url,
}),
}
}
}
impl From<ClientState> for Any {
fn from(client_state: ClientState) -> Self {
Any {
type_url: SOLOMACHINE_CLIENT_STATE_TYPE_URL.to_string(),
value: Protobuf::<RawSmClientState>::encode_vec(&client_state),
}
}
}