referrerpolicy=no-referrer-when-downgrade

sc_network_light/light_client_requests/
handler.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Helper for incoming light client requests.
20//!
21//! Handle (i.e. answer) incoming light client requests from a remote peer received via
22//! `crate::request_responses::RequestResponsesBehaviour` with
23//! [`LightClientRequestHandler`](handler::LightClientRequestHandler).
24
25use crate::schema;
26use codec::{self, Decode, Encode};
27use futures::prelude::*;
28use log::{debug, trace};
29use prost::Message;
30use sc_client_api::{BlockBackend, ProofProvider};
31use sc_network::{
32	config::ProtocolId,
33	request_responses::{IncomingRequest, OutgoingResponse},
34	NetworkBackend, ReputationChange,
35};
36use sc_network_types::PeerId;
37use sp_core::{
38	hexdisplay::HexDisplay,
39	storage::{ChildInfo, ChildType, PrefixedStorageKey},
40};
41use sp_runtime::traits::Block;
42use std::{marker::PhantomData, sync::Arc, time::Duration};
43
44const LOG_TARGET: &str = "light-client-request-handler";
45
46/// Incoming requests bounded queue size. For now due to lack of data on light client request
47/// handling in production systems, this value is chosen to match the block request limit.
48const MAX_LIGHT_REQUEST_QUEUE: usize = 20;
49
50/// Handler for incoming light client requests from a remote peer.
51pub struct LightClientRequestHandler<B, Client> {
52	request_receiver: async_channel::Receiver<IncomingRequest>,
53	/// Blockchain client.
54	client: Arc<Client>,
55	/// Wall-clock limit for a single runtime call serving a remote-call request.
56	execution_timeout: Option<Duration>,
57	_block: PhantomData<B>,
58}
59
60impl<B, Client> LightClientRequestHandler<B, Client>
61where
62	B: Block,
63	Client: BlockBackend<B> + ProofProvider<B> + Send + Sync + 'static,
64{
65	/// Create a new [`LightClientRequestHandler`].
66	pub fn new<N: NetworkBackend<B, <B as Block>::Hash>>(
67		protocol_id: &ProtocolId,
68		fork_id: Option<&str>,
69		client: Arc<Client>,
70		execution_timeout: Option<Duration>,
71	) -> (Self, N::RequestResponseProtocolConfig) {
72		let (tx, request_receiver) = async_channel::bounded(MAX_LIGHT_REQUEST_QUEUE);
73
74		let protocol_config = super::generate_protocol_config::<_, B, N>(
75			protocol_id,
76			client
77				.block_hash(0u32.into())
78				.ok()
79				.flatten()
80				.expect("Genesis block exists; qed"),
81			fork_id,
82			tx,
83		);
84
85		(
86			Self { client, request_receiver, execution_timeout, _block: PhantomData::default() },
87			protocol_config,
88		)
89	}
90
91	/// Run [`LightClientRequestHandler`].
92	///
93	/// Handling a remote call request executes runtime code synchronously (for up to the
94	/// execution timeout, if one is set), so spawn the returned future where blocking is
95	/// acceptable, e.g. via `spawn_blocking`.
96	pub async fn run(mut self) {
97		while let Some(request) = self.request_receiver.next().await {
98			let IncomingRequest { peer, payload, pending_response } = request;
99
100			match self.handle_request(peer, payload) {
101				Ok(response_data) => {
102					let response = OutgoingResponse {
103						result: Ok(response_data),
104						reputation_changes: Vec::new(),
105						sent_feedback: None,
106					};
107
108					match pending_response.send(response) {
109						Ok(()) => trace!(
110							target: LOG_TARGET,
111							"Handled light client request from {}.",
112							peer,
113						),
114						Err(_) => debug!(
115							target: LOG_TARGET,
116							"Failed to handle light client request from {}: {}",
117							peer,
118							HandleRequestError::SendResponse,
119						),
120					};
121				},
122				Err(e) => {
123					debug!(
124						target: LOG_TARGET,
125						"Failed to handle light client request from {}: {}", peer, e,
126					);
127
128					let reputation_changes = match e {
129						HandleRequestError::BadRequest(_) => {
130							vec![ReputationChange::new(-(1 << 12), "bad request")]
131						},
132						_ => Vec::new(),
133					};
134
135					let response = OutgoingResponse {
136						result: Err(()),
137						reputation_changes,
138						sent_feedback: None,
139					};
140
141					if pending_response.send(response).is_err() {
142						debug!(
143							target: LOG_TARGET,
144							"Failed to handle light client request from {}: {}",
145							peer,
146							HandleRequestError::SendResponse,
147						);
148					};
149				},
150			}
151		}
152	}
153
154	fn handle_request(
155		&mut self,
156		peer: PeerId,
157		payload: Vec<u8>,
158	) -> Result<Vec<u8>, HandleRequestError> {
159		let request = schema::v1::light::Request::decode(&payload[..])?;
160
161		let response = match &request.request {
162			Some(schema::v1::light::request::Request::RemoteCallRequest(r)) => {
163				self.on_remote_call_request(&peer, r)?
164			},
165			Some(schema::v1::light::request::Request::RemoteReadRequest(r)) => {
166				self.on_remote_read_request(&peer, r)?
167			},
168			Some(schema::v1::light::request::Request::RemoteReadChildRequest(r)) => {
169				self.on_remote_read_child_request(&peer, r)?
170			},
171			None => {
172				return Err(HandleRequestError::BadRequest("Remote request without request data."))
173			},
174		};
175
176		let mut data = Vec::new();
177		response.encode(&mut data)?;
178
179		Ok(data)
180	}
181
182	fn on_remote_call_request(
183		&mut self,
184		peer: &PeerId,
185		request: &schema::v1::light::RemoteCallRequest,
186	) -> Result<schema::v1::light::Response, HandleRequestError> {
187		trace!("Remote call request from {} ({} at {:?}).", peer, request.method, request.block,);
188
189		let block = Decode::decode(&mut request.block.as_ref())?;
190
191		let response = match self.client.execution_proof(
192			block,
193			&request.method,
194			&request.data,
195			self.execution_timeout,
196		) {
197			Ok((_, proof)) => schema::v1::light::RemoteCallResponse { proof: Some(proof.encode()) },
198			Err(e) => {
199				trace!(
200					"remote call request from {} ({} at {:?}) failed with: {}",
201					peer,
202					request.method,
203					request.block,
204					e,
205				);
206				schema::v1::light::RemoteCallResponse { proof: None }
207			},
208		};
209
210		Ok(schema::v1::light::Response {
211			response: Some(schema::v1::light::response::Response::RemoteCallResponse(response)),
212		})
213	}
214
215	fn on_remote_read_request(
216		&mut self,
217		peer: &PeerId,
218		request: &schema::v1::light::RemoteReadRequest,
219	) -> Result<schema::v1::light::Response, HandleRequestError> {
220		if request.keys.is_empty() {
221			debug!("Invalid remote read request sent by {}.", peer);
222			return Err(HandleRequestError::BadRequest("Remote read request without keys."));
223		}
224
225		trace!(
226			"Remote read request from {} ({} at {:?}).",
227			peer,
228			fmt_keys(request.keys.first(), request.keys.last()),
229			request.block,
230		);
231
232		let block = Decode::decode(&mut request.block.as_ref())?;
233
234		let response =
235			match self.client.read_proof(block, &mut request.keys.iter().map(AsRef::as_ref)) {
236				Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
237				Err(error) => {
238					trace!(
239						"remote read request from {} ({} at {:?}) failed with: {}",
240						peer,
241						fmt_keys(request.keys.first(), request.keys.last()),
242						request.block,
243						error,
244					);
245					schema::v1::light::RemoteReadResponse { proof: None }
246				},
247			};
248
249		Ok(schema::v1::light::Response {
250			response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)),
251		})
252	}
253
254	fn on_remote_read_child_request(
255		&mut self,
256		peer: &PeerId,
257		request: &schema::v1::light::RemoteReadChildRequest,
258	) -> Result<schema::v1::light::Response, HandleRequestError> {
259		if request.keys.is_empty() {
260			debug!("Invalid remote child read request sent by {}.", peer);
261			return Err(HandleRequestError::BadRequest("Remove read child request without keys."));
262		}
263
264		trace!(
265			"Remote read child request from {} ({} {} at {:?}).",
266			peer,
267			HexDisplay::from(&request.storage_key),
268			fmt_keys(request.keys.first(), request.keys.last()),
269			request.block,
270		);
271
272		let block = Decode::decode(&mut request.block.as_ref())?;
273
274		let prefixed_key = PrefixedStorageKey::new_ref(&request.storage_key);
275		let child_info = match ChildType::from_prefixed_key(prefixed_key) {
276			Some((ChildType::ParentKeyId, storage_key)) => Ok(ChildInfo::new_default(storage_key)),
277			None => Err(sp_blockchain::Error::InvalidChildStorageKey),
278		};
279		let response = match child_info.and_then(|child_info| {
280			self.client.read_child_proof(
281				block,
282				&child_info,
283				&mut request.keys.iter().map(AsRef::as_ref),
284			)
285		}) {
286			Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
287			Err(error) => {
288				trace!(
289					"remote read child request from {} ({} {} at {:?}) failed with: {}",
290					peer,
291					HexDisplay::from(&request.storage_key),
292					fmt_keys(request.keys.first(), request.keys.last()),
293					request.block,
294					error,
295				);
296				schema::v1::light::RemoteReadResponse { proof: None }
297			},
298		};
299
300		Ok(schema::v1::light::Response {
301			response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)),
302		})
303	}
304}
305
306#[derive(Debug, thiserror::Error)]
307enum HandleRequestError {
308	#[error("Failed to decode request: {0}.")]
309	DecodeProto(#[from] prost::DecodeError),
310	#[error("Failed to encode response: {0}.")]
311	EncodeProto(#[from] prost::EncodeError),
312	#[error("Failed to send response.")]
313	SendResponse,
314	/// A bad request has been received.
315	#[error("bad request: {0}")]
316	BadRequest(&'static str),
317	/// Encoding or decoding of some data failed.
318	#[error("codec error: {0}")]
319	Codec(#[from] codec::Error),
320}
321
322fn fmt_keys(first: Option<&Vec<u8>>, last: Option<&Vec<u8>>) -> String {
323	if let (Some(first), Some(last)) = (first, last) {
324		if first == last {
325			HexDisplay::from(first).to_string()
326		} else {
327			format!("{}..{}", HexDisplay::from(first), HexDisplay::from(last))
328		}
329	} else {
330		String::from("n/a")
331	}
332}
333
334#[cfg(test)]
335mod tests {
336	use super::*;
337	use sc_network::NetworkWorker;
338	use std::time::Instant;
339	use substrate_test_runtime_client::{
340		runtime::{Block, Hash},
341		TestClient,
342	};
343
344	fn make_handler(
345		execution_timeout: Option<Duration>,
346	) -> LightClientRequestHandler<Block, TestClient> {
347		let client = Arc::new(substrate_test_runtime_client::new());
348		let (handler, _protocol_config) =
349			LightClientRequestHandler::new::<NetworkWorker<Block, Hash>>(
350				&ProtocolId::from("test"),
351				None,
352				client,
353				execution_timeout,
354			);
355		handler
356	}
357
358	/// Handle a `RemoteCallRequest` for `method` at the genesis block.
359	fn remote_call(
360		handler: &mut LightClientRequestHandler<Block, TestClient>,
361		method: &str,
362	) -> schema::v1::light::RemoteCallResponse {
363		let genesis_hash = handler.client.block_hash(0u32.into()).unwrap().unwrap();
364		let request = schema::v1::light::Request {
365			request: Some(schema::v1::light::request::Request::RemoteCallRequest(
366				schema::v1::light::RemoteCallRequest {
367					block: genesis_hash.encode(),
368					method: method.into(),
369					data: Vec::new(),
370				},
371			)),
372		};
373
374		let response = handler.handle_request(PeerId::random(), request.encode_to_vec()).unwrap();
375		let response = schema::v1::light::Response::decode(&response[..]).unwrap();
376		match response.response {
377			Some(schema::v1::light::response::Response::RemoteCallResponse(response)) => response,
378			response => panic!("unexpected response: {response:?}"),
379		}
380	}
381
382	#[test]
383	fn remote_call_with_generous_timeout_succeeds() {
384		// A generous timeout must not interfere: the call is routed through the interruptible
385		// runtime (including waiting for its background compilation) and succeeds.
386		let mut handler = make_handler(Some(Duration::from_secs(60)));
387
388		let response = remote_call(&mut handler, "TestAPI_get_block_number");
389
390		assert!(response.proof.is_some());
391	}
392
393	#[test]
394	fn execution_timeout_interrupts_remote_call() {
395		let timeout = Duration::from_millis(300);
396		let mut handler = make_handler(Some(timeout));
397
398		let started = Instant::now();
399		let response = remote_call(&mut handler, "TestAPI_spin");
400
401		assert!(started.elapsed() >= timeout);
402		assert_eq!(response.proof, None);
403	}
404}