use polkadot_sdk::*;
use crate::{
child_trie,
dispatcher::{RefundingRouter, RequestMetadata},
utils::{ConsensusClientProvider, ResponseReceipt},
BoundedStateCommitments, BoundedStateMachineUpdateTime, ChallengePeriod, Config,
ConsensusClientUpdateTime, ConsensusStateClient, ConsensusStates, FrozenConsensusClients,
KnownStateMachineHeights, LatestStateMachineHeight, Nonce, Pallet, PreviousStateMachineHeight,
Responded, UnbondingPeriod,
};
use alloc::{format, string::ToString};
use codec::{Decode, Encode};
use core::time::Duration;
use crypto_utils::verification::Signature;
use frame_support::traits::{Get, UnixTime};
use ismp::{
consensus::{
ConsensusClient, ConsensusClientId, ConsensusStateId, StateCommitment, StateMachineHeight,
StateMachineId,
},
error::Error,
host::{IsmpHost, StateMachine},
messaging::{hash_post_response, hash_request, hash_response},
router::{IsmpRouter, PostResponse, Request, Response},
};
use sp_core::H256;
use sp_runtime::SaturatedConversion;
use sp_std::prelude::*;
impl<T: Config> IsmpHost for Pallet<T> {
fn host_state_machine(&self) -> StateMachine {
<T as Config>::HostStateMachine::get()
}
fn latest_commitment_height(&self, id: StateMachineId) -> Result<u64, Error> {
Ok(LatestStateMachineHeight::<T>::get(id).unwrap_or_default())
}
fn state_machine_commitment(
&self,
height: StateMachineHeight,
) -> Result<StateCommitment, Error> {
BoundedStateCommitments::<T>::get(height.id, height.height)
.ok_or_else(|| Error::StateCommitmentNotFound { height })
}
fn consensus_update_time(&self, id: ConsensusClientId) -> Result<Duration, Error> {
ConsensusClientUpdateTime::<T>::get(id)
.map(|timestamp| Duration::from_secs(timestamp))
.ok_or_else(|| Error::Custom(format!("Update time not found for {:?}", id)))
}
fn state_machine_update_time(
&self,
state_machine_height: StateMachineHeight,
) -> Result<Duration, Error> {
BoundedStateMachineUpdateTime::<T>::get(
state_machine_height.id,
state_machine_height.height,
)
.map(|timestamp| Duration::from_secs(timestamp))
.ok_or_else(|| {
Error::Custom(format!("Update time not found for {:?}", state_machine_height))
})
}
fn consensus_client_id(
&self,
consensus_state_id: ConsensusStateId,
) -> Option<ConsensusClientId> {
ConsensusStateClient::<T>::get(&consensus_state_id)
}
fn consensus_state(&self, id: ConsensusClientId) -> Result<Vec<u8>, Error> {
ConsensusStates::<T>::get(id)
.ok_or_else(|| Error::ConsensusStateNotFound { consensus_state_id: id })
}
fn timestamp(&self) -> Duration {
<T::TimestampProvider as UnixTime>::now()
}
fn is_consensus_client_frozen(&self, client: ConsensusStateId) -> Result<(), Error> {
if FrozenConsensusClients::<T>::get(client) {
Err(Error::FrozenConsensusClient { consensus_state_id: client })?
}
Ok(())
}
fn request_commitment(&self, commitment: H256) -> Result<(), Error> {
let _ = child_trie::RequestCommitments::<T>::get(commitment)
.ok_or_else(|| Error::Custom("Request commitment not found".to_string()))?;
Ok(())
}
fn response_commitment(&self, commitment: H256) -> Result<(), Error> {
let _ = child_trie::ResponseCommitments::<T>::get(commitment)
.ok_or_else(|| Error::Custom("Response commitment not found".to_string()))?;
Ok(())
}
fn next_nonce(&self) -> u64 {
let nonce = Nonce::<T>::get();
Nonce::<T>::put(nonce + 1);
nonce
}
fn request_receipt(&self, req: &Request) -> Option<()> {
let commitment = hash_request::<Self>(req);
let _ = child_trie::RequestReceipts::<T>::get(commitment)
.ok_or_else(|| Error::RequestCommitmentNotFound { meta: req.into() })
.ok()?;
Some(())
}
fn response_receipt(&self, res: &Response) -> Option<()> {
let commitment = hash_request::<Self>(&res.request());
let _ = child_trie::ResponseReceipts::<T>::get(commitment)
.ok_or_else(|| Error::Custom("Response receipt not found".to_string()))
.ok()?;
Some(())
}
fn store_consensus_state_id(
&self,
consensus_state_id: ConsensusStateId,
client_id: ConsensusClientId,
) -> Result<(), Error> {
ConsensusStateClient::<T>::insert(consensus_state_id, client_id);
Ok(())
}
fn store_consensus_state(&self, id: ConsensusClientId, state: Vec<u8>) -> Result<(), Error> {
ConsensusStates::<T>::insert(id, state);
Ok(())
}
fn store_unbonding_period(
&self,
consensus_state_id: ConsensusStateId,
period: u64,
) -> Result<(), Error> {
UnbondingPeriod::<T>::insert(consensus_state_id, period);
Ok(())
}
fn store_consensus_update_time(
&self,
id: ConsensusClientId,
timestamp: Duration,
) -> Result<(), Error> {
ConsensusClientUpdateTime::<T>::insert(id, timestamp.as_secs().saturated_into::<u64>());
Ok(())
}
fn store_state_machine_update_time(
&self,
state_machine_height: StateMachineHeight,
timestamp: Duration,
) -> Result<(), Error> {
let ts = timestamp.as_secs().saturated_into::<u64>();
Pallet::<T>::insert_bounded_update_time(state_machine_height, ts);
Ok(())
}
fn store_state_machine_commitment(
&self,
height: StateMachineHeight,
state: StateCommitment,
) -> Result<(), Error> {
Pallet::<T>::insert_bounded_state_commitment(height, state);
Ok(())
}
fn delete_state_commitment(&self, height: StateMachineHeight) -> Result<(), Error> {
BoundedStateCommitments::<T>::remove(height.id, height.height);
BoundedStateMachineUpdateTime::<T>::remove(height.id, height.height);
KnownStateMachineHeights::<T>::mutate(height.id, |heights| {
heights.remove(&height.height);
});
if let Some(latest) = LatestStateMachineHeight::<T>::get(height.id) {
if latest == height.height {
let prev_height =
PreviousStateMachineHeight::<T>::get(height.id).ok_or_else(|| {
Error::Custom("Previous state machine height should exist".to_string())
})?;
LatestStateMachineHeight::<T>::insert(height.id, prev_height);
}
}
Ok(())
}
fn freeze_consensus_client(&self, client: ConsensusStateId) -> Result<(), Error> {
FrozenConsensusClients::<T>::insert(client, true);
Ok(())
}
fn store_latest_commitment_height(&self, height: StateMachineHeight) -> Result<(), Error> {
let previous_height = LatestStateMachineHeight::<T>::get(height.id).unwrap_or_default();
PreviousStateMachineHeight::<T>::insert(height.id, previous_height);
LatestStateMachineHeight::<T>::insert(height.id, height.height);
Ok(())
}
fn delete_request_commitment(&self, req: &Request) -> Result<Vec<u8>, Error> {
let hash = hash_request::<Self>(req);
let meta = child_trie::RequestCommitments::<T>::get(hash)
.ok_or_else(|| Error::Custom("Request Commitment not found".to_string()))?;
child_trie::RequestCommitments::<T>::remove(hash);
Ok(meta.encode())
}
fn delete_response_commitment(&self, res: &PostResponse) -> Result<Vec<u8>, Error> {
let req_commitment = hash_request::<Self>(&res.request());
let hash = hash_post_response::<Self>(res);
let meta = child_trie::ResponseCommitments::<T>::get(hash)
.ok_or_else(|| Error::Custom("Response Commitment not found".to_string()))?;
child_trie::ResponseCommitments::<T>::remove(hash);
Responded::<T>::remove(req_commitment);
Ok(meta.encode())
}
fn delete_request_receipt(&self, req: &Request) -> Result<Vec<u8>, Error> {
let req_commitment = hash_request::<Self>(req);
let relayer = child_trie::RequestReceipts::<T>::get(req_commitment)
.ok_or_else(|| Error::Custom("Request receipt not found".to_string()))?;
child_trie::RequestReceipts::<T>::remove(req_commitment);
Ok(relayer)
}
fn delete_response_receipt(&self, res: &Response) -> Result<Vec<u8>, Error> {
let hash = hash_request::<Self>(&res.request());
let meta = child_trie::ResponseReceipts::<T>::get(hash)
.ok_or_else(|| Error::Custom("Response receipt not found".to_string()))?;
child_trie::ResponseReceipts::<T>::remove(hash);
Ok(meta.relayer)
}
fn store_request_receipt(&self, req: &Request, signer: &Vec<u8>) -> Result<Vec<u8>, Error> {
let signer = extract_signer(signer)?;
let hash = hash_request::<Self>(req);
child_trie::RequestReceipts::<T>::insert(hash, &signer);
Ok(signer)
}
fn store_response_receipt(&self, res: &Response, signer: &Vec<u8>) -> Result<Vec<u8>, Error> {
let signer = extract_signer(signer)?;
let hash = hash_request::<Self>(&res.request());
let response = hash_response::<Self>(&res);
child_trie::ResponseReceipts::<T>::insert(
hash,
ResponseReceipt { response, relayer: signer.clone() },
);
Ok(signer)
}
fn consensus_clients(&self) -> Vec<Box<dyn ConsensusClient>> {
<T as Config>::ConsensusClients::consensus_clients()
}
fn challenge_period(&self, state_machine: StateMachineId) -> Option<Duration> {
ChallengePeriod::<T>::get(&state_machine).map(Duration::from_secs)
}
fn store_challenge_period(
&self,
state_machine: StateMachineId,
period: u64,
) -> Result<(), Error> {
ChallengePeriod::<T>::insert(state_machine, period);
Ok(())
}
fn allowed_proxy(&self) -> Option<StateMachine> {
T::Coprocessor::get()
}
fn unbonding_period(&self, consensus_state_id: ConsensusStateId) -> Option<Duration> {
UnbondingPeriod::<T>::get(&consensus_state_id).map(Duration::from_secs)
}
fn ismp_router(&self) -> Box<dyn IsmpRouter> {
Box::new(RefundingRouter::<T>::new(Box::new(T::Router::default())))
}
fn store_request_commitment(&self, req: &Request, meta: Vec<u8>) -> Result<(), Error> {
let hash = hash_request::<Self>(req);
let leaf_meta = RequestMetadata::<T>::decode(&mut &*meta)
.map_err(|_| Error::Custom("Failed to decode leaf metadata".to_string()))?;
child_trie::RequestCommitments::<T>::insert(hash, leaf_meta);
Ok(())
}
fn store_response_commitment(&self, res: &PostResponse, meta: Vec<u8>) -> Result<(), Error> {
let hash = hash_post_response::<Self>(res);
let req_commitment = hash_request::<Self>(&res.request());
let leaf_meta = RequestMetadata::<T>::decode(&mut &*meta)
.map_err(|_| Error::Custom("Failed to decode leaf metadata".to_string()))?;
child_trie::ResponseCommitments::<T>::insert(hash, leaf_meta);
Responded::<T>::insert(req_commitment, true);
Ok(())
}
fn previous_commitment_height(&self, id: StateMachineId) -> Option<u64> {
PreviousStateMachineHeight::<T>::get(id)
}
}
impl<T: Config> ismp::messaging::Keccak256 for Pallet<T> {
fn keccak256(bytes: &[u8]) -> H256
where
Self: Sized,
{
sp_io::hashing::keccak_256(bytes).into()
}
}
fn extract_signer(signer: &[u8]) -> Result<Vec<u8>, Error> {
if signer.len() > 32 {
Signature::decode(&mut signer.as_ref())
.map(|sig| sig.signer())
.map_err(|_| Error::SignatureDecodingFailed)
} else {
Ok(signer.to_vec())
}
}