Skip to main content

soil_network/light/light_client_requests/
handler.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Helper for incoming light client requests.
8//!
9//! Handle (i.e. answer) incoming light client requests from a remote peer received via
10//! `crate::request_responses::RequestResponsesBehaviour` with
11//! [`LightClientRequestHandler`](handler::LightClientRequestHandler).
12
13use crate::light::schema;
14use crate::types::PeerId;
15use crate::{
16	config::ProtocolId,
17	request_responses::{IncomingRequest, OutgoingResponse},
18	NetworkBackend, ReputationChange,
19};
20use codec::{self, Decode, Encode};
21use futures::prelude::*;
22use log::{debug, trace};
23use prost::Message;
24use soil_client::client_api::{BlockBackend, ProofProvider};
25use std::{marker::PhantomData, sync::Arc};
26use subsoil::core::{
27	hexdisplay::HexDisplay,
28	storage::{ChildInfo, ChildType, PrefixedStorageKey},
29};
30use subsoil::runtime::traits::Block;
31
32const LOG_TARGET: &str = "light-client-request-handler";
33
34/// Incoming requests bounded queue size. For now due to lack of data on light client request
35/// handling in production systems, this value is chosen to match the block request limit.
36const MAX_LIGHT_REQUEST_QUEUE: usize = 20;
37
38/// Handler for incoming light client requests from a remote peer.
39pub struct LightClientRequestHandler<B, Client> {
40	request_receiver: async_channel::Receiver<IncomingRequest>,
41	/// Blockchain client.
42	client: Arc<Client>,
43	_block: PhantomData<B>,
44}
45
46impl<B, Client> LightClientRequestHandler<B, Client>
47where
48	B: Block,
49	Client: BlockBackend<B> + ProofProvider<B> + Send + Sync + 'static,
50{
51	/// Create a new [`LightClientRequestHandler`].
52	pub fn new<N: NetworkBackend<B, <B as Block>::Hash>>(
53		protocol_id: &ProtocolId,
54		fork_id: Option<&str>,
55		client: Arc<Client>,
56	) -> (Self, N::RequestResponseProtocolConfig) {
57		let (tx, request_receiver) = async_channel::bounded(MAX_LIGHT_REQUEST_QUEUE);
58
59		let protocol_config = super::generate_protocol_config::<_, B, N>(
60			protocol_id,
61			client
62				.block_hash(0u32.into())
63				.ok()
64				.flatten()
65				.expect("Genesis block exists; qed"),
66			fork_id,
67			tx,
68		);
69
70		(Self { client, request_receiver, _block: PhantomData::default() }, protocol_config)
71	}
72
73	/// Run [`LightClientRequestHandler`].
74	pub async fn run(mut self) {
75		while let Some(request) = self.request_receiver.next().await {
76			let IncomingRequest { peer, payload, pending_response } = request;
77
78			match self.handle_request(peer, payload) {
79				Ok(response_data) => {
80					let response = OutgoingResponse {
81						result: Ok(response_data),
82						reputation_changes: Vec::new(),
83						sent_feedback: None,
84					};
85
86					match pending_response.send(response) {
87						Ok(()) => trace!(
88							target: LOG_TARGET,
89							"Handled light client request from {}.",
90							peer,
91						),
92						Err(_) => debug!(
93							target: LOG_TARGET,
94							"Failed to handle light client request from {}: {}",
95							peer,
96							HandleRequestError::SendResponse,
97						),
98					};
99				},
100				Err(e) => {
101					debug!(
102						target: LOG_TARGET,
103						"Failed to handle light client request from {}: {}", peer, e,
104					);
105
106					let reputation_changes = match e {
107						HandleRequestError::BadRequest(_) => {
108							vec![ReputationChange::new(-(1 << 12), "bad request")]
109						},
110						_ => Vec::new(),
111					};
112
113					let response = OutgoingResponse {
114						result: Err(()),
115						reputation_changes,
116						sent_feedback: None,
117					};
118
119					if pending_response.send(response).is_err() {
120						debug!(
121							target: LOG_TARGET,
122							"Failed to handle light client request from {}: {}",
123							peer,
124							HandleRequestError::SendResponse,
125						);
126					};
127				},
128			}
129		}
130	}
131
132	fn handle_request(
133		&mut self,
134		peer: PeerId,
135		payload: Vec<u8>,
136	) -> Result<Vec<u8>, HandleRequestError> {
137		let request = schema::v1::light::Request::decode(&payload[..])?;
138
139		let response = match &request.request {
140			Some(schema::v1::light::request::Request::RemoteCallRequest(r)) => {
141				self.on_remote_call_request(&peer, r)?
142			},
143			Some(schema::v1::light::request::Request::RemoteReadRequest(r)) => {
144				self.on_remote_read_request(&peer, r)?
145			},
146			Some(schema::v1::light::request::Request::RemoteReadChildRequest(r)) => {
147				self.on_remote_read_child_request(&peer, r)?
148			},
149			None => {
150				return Err(HandleRequestError::BadRequest("Remote request without request data."))
151			},
152		};
153
154		let mut data = Vec::new();
155		response.encode(&mut data)?;
156
157		Ok(data)
158	}
159
160	fn on_remote_call_request(
161		&mut self,
162		peer: &PeerId,
163		request: &schema::v1::light::RemoteCallRequest,
164	) -> Result<schema::v1::light::Response, HandleRequestError> {
165		trace!("Remote call request from {} ({} at {:?}).", peer, request.method, request.block,);
166
167		let block = Decode::decode(&mut request.block.as_ref())?;
168
169		let response = match self.client.execution_proof(block, &request.method, &request.data) {
170			Ok((_, proof)) => schema::v1::light::RemoteCallResponse { proof: Some(proof.encode()) },
171			Err(e) => {
172				trace!(
173					"remote call request from {} ({} at {:?}) failed with: {}",
174					peer,
175					request.method,
176					request.block,
177					e,
178				);
179				schema::v1::light::RemoteCallResponse { proof: None }
180			},
181		};
182
183		Ok(schema::v1::light::Response {
184			response: Some(schema::v1::light::response::Response::RemoteCallResponse(response)),
185		})
186	}
187
188	fn on_remote_read_request(
189		&mut self,
190		peer: &PeerId,
191		request: &schema::v1::light::RemoteReadRequest,
192	) -> Result<schema::v1::light::Response, HandleRequestError> {
193		if request.keys.is_empty() {
194			debug!("Invalid remote read request sent by {}.", peer);
195			return Err(HandleRequestError::BadRequest("Remote read request without keys."));
196		}
197
198		trace!(
199			"Remote read request from {} ({} at {:?}).",
200			peer,
201			fmt_keys(request.keys.first(), request.keys.last()),
202			request.block,
203		);
204
205		let block = Decode::decode(&mut request.block.as_ref())?;
206
207		let response =
208			match self.client.read_proof(block, &mut request.keys.iter().map(AsRef::as_ref)) {
209				Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
210				Err(error) => {
211					trace!(
212						"remote read request from {} ({} at {:?}) failed with: {}",
213						peer,
214						fmt_keys(request.keys.first(), request.keys.last()),
215						request.block,
216						error,
217					);
218					schema::v1::light::RemoteReadResponse { proof: None }
219				},
220			};
221
222		Ok(schema::v1::light::Response {
223			response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)),
224		})
225	}
226
227	fn on_remote_read_child_request(
228		&mut self,
229		peer: &PeerId,
230		request: &schema::v1::light::RemoteReadChildRequest,
231	) -> Result<schema::v1::light::Response, HandleRequestError> {
232		if request.keys.is_empty() {
233			debug!("Invalid remote child read request sent by {}.", peer);
234			return Err(HandleRequestError::BadRequest("Remove read child request without keys."));
235		}
236
237		trace!(
238			"Remote read child request from {} ({} {} at {:?}).",
239			peer,
240			HexDisplay::from(&request.storage_key),
241			fmt_keys(request.keys.first(), request.keys.last()),
242			request.block,
243		);
244
245		let block = Decode::decode(&mut request.block.as_ref())?;
246
247		let prefixed_key = PrefixedStorageKey::new_ref(&request.storage_key);
248		let child_info = match ChildType::from_prefixed_key(prefixed_key) {
249			Some((ChildType::ParentKeyId, storage_key)) => Ok(ChildInfo::new_default(storage_key)),
250			None => Err(soil_client::blockchain::Error::InvalidChildStorageKey),
251		};
252		let response = match child_info.and_then(|child_info| {
253			self.client.read_child_proof(
254				block,
255				&child_info,
256				&mut request.keys.iter().map(AsRef::as_ref),
257			)
258		}) {
259			Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
260			Err(error) => {
261				trace!(
262					"remote read child request from {} ({} {} at {:?}) failed with: {}",
263					peer,
264					HexDisplay::from(&request.storage_key),
265					fmt_keys(request.keys.first(), request.keys.last()),
266					request.block,
267					error,
268				);
269				schema::v1::light::RemoteReadResponse { proof: None }
270			},
271		};
272
273		Ok(schema::v1::light::Response {
274			response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)),
275		})
276	}
277}
278
279#[derive(Debug, thiserror::Error)]
280enum HandleRequestError {
281	#[error("Failed to decode request: {0}.")]
282	DecodeProto(#[from] prost::DecodeError),
283	#[error("Failed to encode response: {0}.")]
284	EncodeProto(#[from] prost::EncodeError),
285	#[error("Failed to send response.")]
286	SendResponse,
287	/// A bad request has been received.
288	#[error("bad request: {0}")]
289	BadRequest(&'static str),
290	/// Encoding or decoding of some data failed.
291	#[error("codec error: {0}")]
292	Codec(#[from] codec::Error),
293}
294
295fn fmt_keys(first: Option<&Vec<u8>>, last: Option<&Vec<u8>>) -> String {
296	if let (Some(first), Some(last)) = (first, last) {
297		if first == last {
298			HexDisplay::from(first).to_string()
299		} else {
300			format!("{}..{}", HexDisplay::from(first), HexDisplay::from(last))
301		}
302	} else {
303		String::from("n/a")
304	}
305}