ismp/
handlers.rs

1// Copyright (C) Polytope Labs Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! ISMP handler definitions
17use crate::{
18	consensus::{ConsensusClientId, StateMachineClient, StateMachineHeight},
19	error::Error,
20	events::Event,
21	host::IsmpHost,
22	messaging::Message,
23};
24
25use crate::{consensus::ConsensusStateId, module::DispatchResult};
26use alloc::{boxed::Box, vec::Vec};
27pub use consensus::create_client;
28use sp_weights::Weight;
29
30mod consensus;
31mod request;
32mod response;
33mod timeout;
34
35/// The result of successfully processing a `CreateConsensusClient` message
36pub struct ConsensusClientCreatedResult {
37	/// Consensus client Id
38	pub consensus_client_id: ConsensusClientId,
39	/// Consensus state Id
40	pub consensus_state_id: ConsensusStateId,
41}
42
43/// Result returned when ismp messages are handled successfully
44#[derive(Debug)]
45pub enum MessageResult {
46	/// The `ConsensusMessage` result
47	ConsensusMessage(Vec<Event>),
48	/// Result of freezing a consensus state.
49	FrozenClient(ConsensusStateId),
50	/// The result of processing a batch of requests.
51	Request {
52		/// A Vec containing the results of each individual request dispatch.
53		events: Vec<DispatchResult>,
54		/// The total weight consumed by all module `on_accept` calls for this batch.
55		weight: Weight,
56	},
57	/// The result of processing a batch of responses.
58	Response {
59		/// A Vec containing the results of each individual response dispatch.
60		events: Vec<DispatchResult>,
61		/// The total weight consumed by all module `on_accept` calls for this batch.
62		weight: Weight,
63	},
64	/// The result of processing a timeouts.
65	Timeout {
66		/// A Vec containing the results of each individual response dispatch.
67		events: Vec<DispatchResult>,
68		/// The total weight consumed by all module `on_accept` calls for this batch.
69		weight: Weight,
70	},
71}
72
73impl MessageResult {
74	/// Returns the total weight consumed by this message
75	pub fn weight(&self) -> Weight {
76		match self {
77			MessageResult::Request { weight, .. } => *weight,
78			MessageResult::Response { weight, .. } => *weight,
79			MessageResult::Timeout { weight, .. } => *weight,
80			MessageResult::ConsensusMessage(_) | MessageResult::FrozenClient(_) => Weight::zero(),
81		}
82	}
83}
84
85/// This function serves as an entry point to handle the message types provided by the ISMP protocol
86pub fn handle_incoming_message<H>(
87	host: &H,
88	message: Message,
89) -> Result<MessageResult, anyhow::Error>
90where
91	H: IsmpHost,
92{
93	match message {
94		Message::Consensus(consensus_message) => consensus::update_client(host, consensus_message),
95		Message::FraudProof(fraud_proof) => consensus::freeze_client(host, fraud_proof),
96		Message::Request(req) => request::handle(host, req),
97		Message::Response(resp) => response::handle(host, resp),
98		Message::Timeout(timeout) => timeout::handle(host, timeout),
99	}
100}
101
102/// This function checks to see that the delay period configured on the host chain
103/// for the state machine has elasped.
104pub fn verify_delay_passed<H>(host: &H, proof_height: &StateMachineHeight) -> Result<bool, Error>
105where
106	H: IsmpHost,
107{
108	let update_time = host.state_machine_update_time(*proof_height)?;
109	let delay_period = host
110		.challenge_period(proof_height.id)
111		.ok_or(Error::ChallengePeriodNotConfigured { state_machine: proof_height.id })?;
112	let current_timestamp = host.timestamp();
113	Ok(delay_period.as_secs() == 0 || current_timestamp.saturating_sub(update_time) > delay_period)
114}
115
116/// This function does the preliminary checks for a request or response message
117/// - It ensures the consensus client is not frozen
118/// - Checks for frozen state machine is deprecated and malicious state machine commitment will be
119///   deleted instead
120/// - Checks that the delay period configured for the state machine has elapsed.
121pub fn validate_state_machine<H>(
122	host: &H,
123	proof_height: StateMachineHeight,
124) -> Result<Box<dyn StateMachineClient>, Error>
125where
126	H: IsmpHost,
127{
128	// Ensure consensus client is not frozen
129	let consensus_client_id = host.consensus_client_id(proof_height.id.consensus_state_id).ok_or(
130		Error::ConsensusStateIdNotRecognized {
131			consensus_state_id: proof_height.id.consensus_state_id,
132		},
133	)?;
134	let consensus_client = host.consensus_client(consensus_client_id)?;
135	// Ensure client is not frozen
136	host.is_consensus_client_frozen(proof_height.id.consensus_state_id)?;
137
138	// Ensure delay period has elapsed
139	if !verify_delay_passed(host, &proof_height)? {
140		return Err(Error::ChallengePeriodNotElapsed {
141			state_machine_id: proof_height.id,
142			current_time: host.timestamp(),
143			update_time: host.state_machine_update_time(proof_height)?,
144		});
145	}
146
147	consensus_client.state_machine(proof_height.id.state_id)
148}