sc_domains/domain_block_er/
execution_receipt_protocol.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Substrate.
3
4// Substrate is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Substrate is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
16
17use domain_runtime_primitives::Balance;
18use futures::channel::oneshot;
19use futures::stream::StreamExt;
20use parity_scale_codec::{Decode, Encode};
21use sc_client_api::BlockBackend;
22use sc_network::request_responses::{IncomingRequest, OutgoingResponse};
23use sc_network::{NetworkBackend, PeerId};
24use sp_api::ProvideRuntimeApi;
25use sp_blockchain::HeaderBackend;
26use sp_domains::{DomainId, DomainsApi, ExecutionReceiptFor};
27use sp_runtime::traits::{Block as BlockT, Header};
28use std::marker::PhantomData;
29use std::sync::Arc;
30use std::time::Duration;
31use tracing::{debug, error, trace};
32
33/// Generates a `RequestResponseProtocolConfig` for the Execution receipt protocol.
34pub fn generate_protocol_config<Hash: AsRef<[u8]>, B: BlockT, N: NetworkBackend<B, B::Hash>>(
35    genesis_hash: Hash,
36    fork_id: Option<&str>,
37    inbound_queue: async_channel::Sender<IncomingRequest>,
38) -> N::RequestResponseProtocolConfig {
39    N::request_response_config(
40        generate_protocol_name(genesis_hash, fork_id).into(),
41        Vec::new(),
42        1024 * 1024,
43        16 * 1024 * 1024,
44        Duration::from_secs(40),
45        Some(inbound_queue),
46    )
47}
48
49/// Generate the state protocol name from the genesis hash and fork id.
50pub fn generate_protocol_name<Hash: AsRef<[u8]>>(
51    genesis_hash: Hash,
52    fork_id: Option<&str>,
53) -> String {
54    let genesis_hash = genesis_hash.as_ref();
55    if let Some(fork_id) = fork_id {
56        format!(
57            "/{}/{}/last-confirmed-domain-block-receipt/1",
58            array_bytes::bytes2hex("", genesis_hash),
59            fork_id
60        )
61    } else {
62        format!(
63            "/{}/last-confirmed-domain-block-receipt/1",
64            array_bytes::bytes2hex("", genesis_hash)
65        )
66    }
67}
68
69/// Domain block ER request.
70#[derive(Clone, PartialEq, Encode, Decode, Debug)]
71pub enum DomainBlockERRequest {
72    /// Last Confirmed ER request for given Domain.
73    LastConfirmedER(DomainId),
74}
75
76/// Response for Domain Block ER request.
77#[derive(Clone, PartialEq, Encode, Decode, Debug)]
78pub enum DomainBlockERResponse<CBlock: BlockT, DomainHeader: Header> {
79    /// Response for last confirmed Domain block ER.
80    LastConfirmedER(ExecutionReceiptFor<DomainHeader, CBlock, Balance>),
81}
82
83/// Handler for incoming block requests from a remote peer.
84pub struct DomainBlockERRequestHandler<CBlock, Block, CClient>
85where
86    CBlock: BlockT,
87    Block: BlockT,
88{
89    consensus_client: Arc<CClient>,
90    request_receiver: async_channel::Receiver<IncomingRequest>,
91    _phantom: PhantomData<(CBlock, Block)>,
92}
93
94impl<CBlock, Block, CClient> DomainBlockERRequestHandler<CBlock, Block, CClient>
95where
96    CBlock: BlockT,
97    Block: BlockT,
98    CClient: ProvideRuntimeApi<CBlock>
99        + BlockBackend<CBlock>
100        + HeaderBackend<CBlock>
101        + Send
102        + Sync
103        + 'static,
104    CClient::Api: DomainsApi<CBlock, Block::Header>,
105{
106    /// Create a new [`DomainBlockERRequestHandler`].
107    pub fn new<NB>(
108        fork_id: Option<&str>,
109        consensus_client: Arc<CClient>,
110        num_peer_hint: usize,
111    ) -> (Self, NB::RequestResponseProtocolConfig)
112    where
113        NB: NetworkBackend<Block, Block::Hash>,
114    {
115        // Reserve enough request slots for one request per peer when we are at the maximum
116        // number of peers.
117        let capacity = std::cmp::max(num_peer_hint, 1);
118        let (tx, request_receiver) = async_channel::bounded(capacity);
119
120        let protocol_config = generate_protocol_config::<_, Block, NB>(
121            consensus_client
122                .block_hash(0u32.into())
123                .ok()
124                .flatten()
125                .expect("Genesis block exists; qed"),
126            fork_id,
127            tx,
128        );
129
130        (
131            Self {
132                request_receiver,
133                consensus_client,
134                _phantom: PhantomData,
135            },
136            protocol_config,
137        )
138    }
139
140    /// Run [`DomainBlockERRequestHandler`].
141    pub async fn run(mut self) {
142        while let Some(request) = self.request_receiver.next().await {
143            let IncomingRequest {
144                peer,
145                payload,
146                pending_response,
147            } = request;
148
149            match self.handle_request(payload, pending_response, &peer) {
150                Ok(()) => {
151                    debug!("Handled domain block ER request from {}.", peer)
152                }
153                Err(e) => error!(
154                    "Failed to handle domain block ER request from {}: {}",
155                    peer, e,
156                ),
157            }
158        }
159    }
160
161    fn handle_request(
162        &self,
163        payload: Vec<u8>,
164        pending_response: oneshot::Sender<OutgoingResponse>,
165        peer: &PeerId,
166    ) -> Result<(), HandleRequestError> {
167        let request = DomainBlockERRequest::decode(&mut payload.as_slice())?;
168
169        trace!("Handle domain block ER request: {peer}, request: {request:?}",);
170
171        let result = {
172            let DomainBlockERRequest::LastConfirmedER(domain_id) = request;
173            let response = DomainBlockERResponse::<CBlock, Block::Header>::LastConfirmedER(
174                self.get_execution_receipts(domain_id)?,
175            );
176            Ok(response.encode())
177        };
178
179        pending_response
180            .send(OutgoingResponse {
181                result,
182                reputation_changes: Vec::new(),
183                sent_feedback: None,
184            })
185            .map_err(|_| HandleRequestError::SendResponse)
186    }
187
188    fn get_execution_receipts(
189        &self,
190        domain_id: DomainId,
191    ) -> Result<ExecutionReceiptFor<Block::Header, CBlock, Balance>, HandleRequestError> {
192        let best_consensus_hash = self.consensus_client.info().best_hash;
193
194        // Get the last confirmed block receipt
195        let last_confirmed_block_receipt = self
196            .consensus_client
197            .runtime_api()
198            .last_confirmed_domain_block_receipt(best_consensus_hash, domain_id);
199
200        let last_confirmed_block_receipt = match last_confirmed_block_receipt {
201            Ok(Some(last_confirmed_block_receipt)) => last_confirmed_block_receipt,
202            Ok(None) => {
203                debug!(
204                    %domain_id,
205                    %best_consensus_hash,
206                    "Last confirmed domain block ER acquisition failed: no data.",
207                );
208
209                return Err(HandleRequestError::AbsentLastConfirmedDomainBlockData);
210            }
211            Err(err) => {
212                debug!(
213                    %domain_id,
214                    %best_consensus_hash,
215                    ?err,
216                    "Last confirmed domain block ER acquisition failed.",
217                );
218
219                return Err(HandleRequestError::LastConfirmedDomainDataAcquisitionFailed(err));
220            }
221        };
222
223        debug!(
224            ?last_confirmed_block_receipt,
225            "Last confirmed domain block receipt."
226        );
227
228        Ok(last_confirmed_block_receipt)
229    }
230}
231
232#[derive(Debug, thiserror::Error)]
233enum HandleRequestError {
234    #[error(transparent)]
235    Client(#[from] sp_blockchain::Error),
236
237    #[error("Failed to send response.")]
238    SendResponse,
239
240    #[error("Failed to decode request: {0}.")]
241    Decode(#[from] parity_scale_codec::Error),
242
243    #[error("Last confirmed domain block acquisition failed: no data.")]
244    AbsentLastConfirmedDomainBlockData,
245
246    #[error("Last confirmed domain block acquisition failed: no data.")]
247    LastConfirmedDomainDataAcquisitionFailed(#[from] sp_api::ApiError),
248}