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