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