Skip to main content

subspace_service/mmr/
request_handler.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
17#[cfg(test)]
18mod tests;
19
20use crate::mmr::sync::decode_mmr_data;
21use crate::mmr::{get_offchain_key, get_temp_key};
22use futures::channel::oneshot;
23use futures::stream::StreamExt;
24use parity_scale_codec::{Decode, Encode};
25use sc_client_api::{BlockBackend, ProofProvider};
26use sc_network::config::ProtocolId;
27use sc_network::request_responses::{IncomingRequest, OutgoingResponse};
28use sc_network::{NetworkBackend, PeerId};
29use schnellru::{ByLength, LruMap};
30use sp_blockchain::HeaderBackend;
31use sp_core::offchain::storage::OffchainDb;
32use sp_core::offchain::{DbExternalities, OffchainStorage, StorageKind};
33use sp_mmr_primitives::utils::NodesUtils;
34use sp_runtime::traits::Block as BlockT;
35use std::collections::BTreeMap;
36use std::marker::PhantomData;
37use std::sync::Arc;
38use std::time::Duration;
39use subspace_core_primitives::BlockNumber;
40use subspace_runtime_primitives::BlockHashFor;
41use tracing::{debug, error, trace};
42
43const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2;
44
45/// Defines max items per request
46pub const MAX_MMR_ITEMS: u32 = 20000;
47
48mod rep {
49    use sc_network::ReputationChange as Rep;
50
51    /// Reputation change when a peer sent us the same request multiple times.
52    pub const SAME_REQUEST: Rep = Rep::new(i32::MIN, "Same state request multiple times");
53}
54
55/// Generates a `RequestResponseProtocolConfig` for the state request protocol, refusing incoming
56/// requests.
57pub fn generate_protocol_config<Hash: AsRef<[u8]>, B: BlockT, N: NetworkBackend<B, B::Hash>>(
58    _: &ProtocolId,
59    genesis_hash: Hash,
60    fork_id: Option<&str>,
61    inbound_queue: async_channel::Sender<IncomingRequest>,
62) -> N::RequestResponseProtocolConfig {
63    N::request_response_config(
64        generate_protocol_name(genesis_hash, fork_id).into(),
65        Vec::new(),
66        1024 * 1024,
67        16 * 1024 * 1024,
68        Duration::from_secs(40),
69        Some(inbound_queue),
70    )
71}
72
73/// Generate the state protocol name from the genesis hash and fork id.
74pub fn generate_protocol_name<Hash: AsRef<[u8]>>(
75    genesis_hash: Hash,
76    fork_id: Option<&str>,
77) -> String {
78    let genesis_hash = genesis_hash.as_ref();
79    if let Some(fork_id) = fork_id {
80        format!("/{}/{}/mmr/1", hex::encode(genesis_hash), fork_id)
81    } else {
82        format!("/{}/mmr/1", hex::encode(genesis_hash))
83    }
84}
85
86fn leaf_index_that_added_node(position: BlockNumber) -> BlockNumber {
87    NodesUtils::leaf_index_that_added_node(position.into())
88        .try_into()
89        .expect("Always its into a block number; qed")
90}
91
92/// The key of [`BlockRequestHandler::seen_requests`].
93#[derive(Eq, PartialEq, Clone, Hash)]
94struct SeenRequestsKey {
95    peer: PeerId,
96    starting_position: u32,
97}
98
99/// Request MMR data from a peer.
100#[derive(Clone, PartialEq, Encode, Decode, Debug)]
101pub struct MmrRequest {
102    /// Starting position for MMR node.
103    pub starting_position: u32,
104    /// Max returned nodes.
105    pub limit: u32,
106}
107
108#[derive(Clone, PartialEq, Encode, Decode, Debug)]
109pub struct MmrResponse {
110    /// MMR-nodes related to node position
111    pub mmr_data: BTreeMap<u32, Vec<u8>>,
112}
113
114/// The value of [`StateRequestHandler::seen_requests`].
115enum SeenRequestsValue {
116    /// First time we have seen the request.
117    First,
118    /// We have fulfilled the request `n` times.
119    Fulfilled(usize),
120}
121
122/// Handler for incoming block requests from a remote peer.
123pub struct MmrRequestHandler<Block, OS, Client>
124where
125    Block: BlockT,
126{
127    request_receiver: async_channel::Receiver<IncomingRequest>,
128    /// Maps from request to number of times we have seen this request.
129    ///
130    /// This is used to check if a peer is spamming us with the same request.
131    seen_requests: LruMap<SeenRequestsKey, SeenRequestsValue>,
132
133    offchain_db: OffchainDb<OS>,
134
135    client: Arc<Client>,
136
137    _phantom: PhantomData<Block>,
138}
139
140impl<Block, OS, Client> MmrRequestHandler<Block, OS, Client>
141where
142    Block: BlockT<Hash = sp_core::H256>,
143    Client:
144        HeaderBackend<Block> + BlockBackend<Block> + ProofProvider<Block> + Send + Sync + 'static,
145    OS: OffchainStorage,
146{
147    /// Create a new [`MmrRequestHandler`].
148    pub fn new<NB>(
149        protocol_id: &ProtocolId,
150        fork_id: Option<&str>,
151        client: Arc<Client>,
152        num_peer_hint: usize,
153        offchain_storage: OS,
154    ) -> (Self, NB::RequestResponseProtocolConfig)
155    where
156        NB: NetworkBackend<Block, BlockHashFor<Block>>,
157    {
158        // Reserve enough request slots for one request per peer when we are at the maximum
159        // number of peers.
160        let capacity = std::cmp::max(num_peer_hint, 1);
161        let (tx, request_receiver) = async_channel::bounded(capacity);
162
163        let protocol_config = generate_protocol_config::<_, Block, NB>(
164            protocol_id,
165            client
166                .block_hash(0u32.into())
167                .ok()
168                .flatten()
169                .expect("Genesis block exists; qed"),
170            fork_id,
171            tx,
172        );
173
174        let capacity = ByLength::new(num_peer_hint.max(1) as u32 * 2);
175        let seen_requests = LruMap::new(capacity);
176
177        (
178            Self {
179                client,
180                request_receiver,
181                seen_requests,
182                offchain_db: OffchainDb::new(offchain_storage),
183                _phantom: PhantomData,
184            },
185            protocol_config,
186        )
187    }
188
189    /// Run [`StateRequestHandler`].
190    pub async fn run(mut self) {
191        while let Some(request) = self.request_receiver.next().await {
192            let IncomingRequest {
193                peer,
194                payload,
195                pending_response,
196            } = request;
197
198            match self.handle_request(payload, pending_response, &peer) {
199                Ok(()) => debug!("Handled MMR request from {}.", peer),
200                Err(e) => {
201                    error!("Failed to handle MMR request from {}: {}", peer, e,)
202                }
203            }
204        }
205    }
206
207    fn handle_request(
208        &mut self,
209        payload: Vec<u8>,
210        pending_response: oneshot::Sender<OutgoingResponse>,
211        peer: &PeerId,
212    ) -> Result<(), HandleRequestError> {
213        let request = MmrRequest::decode(&mut payload.as_slice())?;
214
215        let key = SeenRequestsKey {
216            peer: *peer,
217            starting_position: request.starting_position,
218        };
219
220        let mut reputation_changes = Vec::new();
221
222        match self.seen_requests.get(&key) {
223            Some(SeenRequestsValue::First) => {}
224            Some(SeenRequestsValue::Fulfilled(requests)) => {
225                *requests = requests.saturating_add(1);
226
227                if *requests > MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER {
228                    reputation_changes.push(rep::SAME_REQUEST);
229                }
230            }
231            None => {
232                self.seen_requests
233                    .insert(key.clone(), SeenRequestsValue::First);
234            }
235        }
236
237        trace!("Handle MMR request: {peer}, request: {request:?}",);
238
239        let result = if request.limit > MAX_MMR_ITEMS {
240            error!(
241                "Invalid MMR request from peer={peer}: {:?}",
242                HandleRequestError::MaxItemsLimitExceeded
243            );
244
245            Err(())
246        } else {
247            let mut mmr_data = BTreeMap::new();
248            for position in request.starting_position..(request.starting_position + request.limit) {
249                let canon_key = get_offchain_key(position.into());
250                let storage_value = self
251                    .offchain_db
252                    .local_storage_get(StorageKind::PERSISTENT, &canon_key);
253
254                let block_number = leaf_index_that_added_node(position);
255                trace!( %position, %block_number, "Storage data present: {}", storage_value.is_some());
256
257                if let Some(storage_value) = storage_value {
258                    mmr_data.insert(position, storage_value);
259                } else {
260                    if let Ok(Some(hash)) = self.client.hash(block_number.into()) {
261                        let temp_key = get_temp_key(position.into(), hash);
262                        let storage_value = self
263                            .offchain_db
264                            .local_storage_get(StorageKind::PERSISTENT, &temp_key);
265
266                        if let Some(storage_value) = storage_value {
267                            let data = decode_mmr_data(&storage_value);
268                            trace!( %position, %block_number,"MMR node: {data:?}");
269                            mmr_data.insert(position, storage_value);
270                            continue;
271                        } else {
272                            debug!( %position, %block_number, ?hash, "Didn't find value in storage.")
273                        }
274                    } else {
275                        debug!( %position, %block_number, "Didn't find hash.")
276                    }
277                    break; // No more storage values
278                }
279            }
280
281            if let Some(value) = self.seen_requests.get(&key) {
282                // If this is the first time we have processed this request, we need to change
283                // it to `Fulfilled`.
284                if let SeenRequestsValue::First = value {
285                    *value = SeenRequestsValue::Fulfilled(1);
286                }
287            }
288
289            let response = MmrResponse { mmr_data };
290
291            Ok(response.encode())
292        };
293
294        pending_response
295            .send(OutgoingResponse {
296                result,
297                reputation_changes,
298                sent_feedback: None,
299            })
300            .map_err(|_| HandleRequestError::SendResponse)
301    }
302}
303
304#[derive(Debug, thiserror::Error)]
305enum HandleRequestError {
306    #[error("Invalid request: max MMR nodes limit exceeded.")]
307    MaxItemsLimitExceeded,
308
309    #[error(transparent)]
310    Client(#[from] sp_blockchain::Error),
311
312    #[error("Failed to send response.")]
313    SendResponse,
314
315    #[error("Failed to decode request: {0}.")]
316    Decode(#[from] parity_scale_codec::Error),
317}