Network sync refactoring (part 3) (#11347)

* Move `light.v1.proto` schema into new crate `sc-network-light`

* Move `sc_network::light_client_requests` and submodule to `sc_network_light::light_client_requests`

* Fix apparently outdated reference in documentation and visibility modifier

* Fix rustdoc check

* Update lock file
This commit is contained in:
Nazar Mokrynskyi
2022-05-14 10:17:53 +03:00
committed by GitHub
parent 685162cfd6
commit 8a45870878
16 changed files with 120 additions and 20 deletions
+22
View File
@@ -0,0 +1,22 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Light client data structures of the networking layer.
pub mod light_client_requests;
mod schema;
@@ -0,0 +1,43 @@
// This file is part of Substrate.
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Helpers for outgoing and incoming light client requests.
/// For incoming light client requests.
pub mod handler;
use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig};
use std::time::Duration;
/// Generate the light client protocol name from chain specific protocol identifier.
fn generate_protocol_name(protocol_id: &ProtocolId) -> String {
format!("/{}/light/2", protocol_id.as_ref())
}
/// Generates a [`ProtocolConfig`] for the light client request protocol, refusing incoming
/// requests.
pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig {
ProtocolConfig {
name: generate_protocol_name(protocol_id).into(),
max_request_size: 1 * 1024 * 1024,
max_response_size: 16 * 1024 * 1024,
request_timeout: Duration::from_secs(15),
inbound_queue: None,
}
}
@@ -0,0 +1,318 @@
// This file is part of Substrate.
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Helper for incoming light client requests.
//!
//! Handle (i.e. answer) incoming light client requests from a remote peer received via
//! `crate::request_responses::RequestResponsesBehaviour` with
//! [`LightClientRequestHandler`](handler::LightClientRequestHandler).
use crate::schema;
use codec::{self, Decode, Encode};
use futures::{channel::mpsc, prelude::*};
use libp2p::PeerId;
use log::{debug, trace};
use prost::Message;
use sc_client_api::{ProofProvider, StorageProof};
use sc_network_common::{
config::ProtocolId,
request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig},
};
use sc_peerset::ReputationChange;
use sp_core::{
hexdisplay::HexDisplay,
storage::{ChildInfo, ChildType, PrefixedStorageKey},
};
use sp_runtime::{generic::BlockId, traits::Block};
use std::{marker::PhantomData, sync::Arc};
const LOG_TARGET: &str = "light-client-request-handler";
/// Handler for incoming light client requests from a remote peer.
pub struct LightClientRequestHandler<B, Client> {
request_receiver: mpsc::Receiver<IncomingRequest>,
/// Blockchain client.
client: Arc<Client>,
_block: PhantomData<B>,
}
impl<B, Client> LightClientRequestHandler<B, Client>
where
B: Block,
Client: ProofProvider<B> + Send + Sync + 'static,
{
/// Create a new [`LightClientRequestHandler`].
pub fn new(protocol_id: &ProtocolId, client: Arc<Client>) -> (Self, ProtocolConfig) {
// For now due to lack of data on light client request handling in production systems, this
// value is chosen to match the block request limit.
let (tx, request_receiver) = mpsc::channel(20);
let mut protocol_config = super::generate_protocol_config(protocol_id);
protocol_config.inbound_queue = Some(tx);
(Self { client, request_receiver, _block: PhantomData::default() }, protocol_config)
}
/// Run [`LightClientRequestHandler`].
pub async fn run(mut self) {
while let Some(request) = self.request_receiver.next().await {
let IncomingRequest { peer, payload, pending_response } = request;
match self.handle_request(peer, payload) {
Ok(response_data) => {
let response = OutgoingResponse {
result: Ok(response_data),
reputation_changes: Vec::new(),
sent_feedback: None,
};
match pending_response.send(response) {
Ok(()) => trace!(
target: LOG_TARGET,
"Handled light client request from {}.",
peer,
),
Err(_) => debug!(
target: LOG_TARGET,
"Failed to handle light client request from {}: {}",
peer,
HandleRequestError::SendResponse,
),
};
},
Err(e) => {
debug!(
target: LOG_TARGET,
"Failed to handle light client request from {}: {}", peer, e,
);
let reputation_changes = match e {
HandleRequestError::BadRequest(_) => {
vec![ReputationChange::new(-(1 << 12), "bad request")]
},
_ => Vec::new(),
};
let response = OutgoingResponse {
result: Err(()),
reputation_changes,
sent_feedback: None,
};
if pending_response.send(response).is_err() {
debug!(
target: LOG_TARGET,
"Failed to handle light client request from {}: {}",
peer,
HandleRequestError::SendResponse,
);
};
},
}
}
}
fn handle_request(
&mut self,
peer: PeerId,
payload: Vec<u8>,
) -> Result<Vec<u8>, HandleRequestError> {
let request = schema::v1::light::Request::decode(&payload[..])?;
let response = match &request.request {
Some(schema::v1::light::request::Request::RemoteCallRequest(r)) =>
self.on_remote_call_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteReadRequest(r)) =>
self.on_remote_read_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteHeaderRequest(_r)) =>
return Err(HandleRequestError::BadRequest("Not supported.")),
Some(schema::v1::light::request::Request::RemoteReadChildRequest(r)) =>
self.on_remote_read_child_request(&peer, r)?,
Some(schema::v1::light::request::Request::RemoteChangesRequest(_r)) =>
return Err(HandleRequestError::BadRequest("Not supported.")),
None =>
return Err(HandleRequestError::BadRequest("Remote request without request data.")),
};
let mut data = Vec::new();
response.encode(&mut data)?;
Ok(data)
}
fn on_remote_call_request(
&mut self,
peer: &PeerId,
request: &schema::v1::light::RemoteCallRequest,
) -> Result<schema::v1::light::Response, HandleRequestError> {
trace!("Remote call request from {} ({} at {:?}).", peer, request.method, request.block,);
let block = Decode::decode(&mut request.block.as_ref())?;
let proof =
match self
.client
.execution_proof(&BlockId::Hash(block), &request.method, &request.data)
{
Ok((_, proof)) => proof,
Err(e) => {
trace!(
"remote call request from {} ({} at {:?}) failed with: {}",
peer,
request.method,
request.block,
e,
);
StorageProof::empty()
},
};
let response = {
let r = schema::v1::light::RemoteCallResponse { proof: proof.encode() };
schema::v1::light::response::Response::RemoteCallResponse(r)
};
Ok(schema::v1::light::Response { response: Some(response) })
}
fn on_remote_read_request(
&mut self,
peer: &PeerId,
request: &schema::v1::light::RemoteReadRequest,
) -> Result<schema::v1::light::Response, HandleRequestError> {
if request.keys.is_empty() {
debug!("Invalid remote read request sent by {}.", peer);
return Err(HandleRequestError::BadRequest("Remote read request without keys."))
}
trace!(
"Remote read request from {} ({} at {:?}).",
peer,
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
);
let block = Decode::decode(&mut request.block.as_ref())?;
let proof = match self
.client
.read_proof(&BlockId::Hash(block), &mut request.keys.iter().map(AsRef::as_ref))
{
Ok(proof) => proof,
Err(error) => {
trace!(
"remote read request from {} ({} at {:?}) failed with: {}",
peer,
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
error,
);
StorageProof::empty()
},
};
let response = {
let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() };
schema::v1::light::response::Response::RemoteReadResponse(r)
};
Ok(schema::v1::light::Response { response: Some(response) })
}
fn on_remote_read_child_request(
&mut self,
peer: &PeerId,
request: &schema::v1::light::RemoteReadChildRequest,
) -> Result<schema::v1::light::Response, HandleRequestError> {
if request.keys.is_empty() {
debug!("Invalid remote child read request sent by {}.", peer);
return Err(HandleRequestError::BadRequest("Remove read child request without keys."))
}
trace!(
"Remote read child request from {} ({} {} at {:?}).",
peer,
HexDisplay::from(&request.storage_key),
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
);
let block = Decode::decode(&mut request.block.as_ref())?;
let prefixed_key = PrefixedStorageKey::new_ref(&request.storage_key);
let child_info = match ChildType::from_prefixed_key(prefixed_key) {
Some((ChildType::ParentKeyId, storage_key)) => Ok(ChildInfo::new_default(storage_key)),
None => Err(sp_blockchain::Error::InvalidChildStorageKey),
};
let proof = match child_info.and_then(|child_info| {
self.client.read_child_proof(
&BlockId::Hash(block),
&child_info,
&mut request.keys.iter().map(AsRef::as_ref),
)
}) {
Ok(proof) => proof,
Err(error) => {
trace!(
"remote read child request from {} ({} {} at {:?}) failed with: {}",
peer,
HexDisplay::from(&request.storage_key),
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
error,
);
StorageProof::empty()
},
};
let response = {
let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() };
schema::v1::light::response::Response::RemoteReadResponse(r)
};
Ok(schema::v1::light::Response { response: Some(response) })
}
}
#[derive(Debug, thiserror::Error)]
enum HandleRequestError {
#[error("Failed to decode request: {0}.")]
DecodeProto(#[from] prost::DecodeError),
#[error("Failed to encode response: {0}.")]
EncodeProto(#[from] prost::EncodeError),
#[error("Failed to send response.")]
SendResponse,
/// A bad request has been received.
#[error("bad request: {0}")]
BadRequest(&'static str),
/// Encoding or decoding of some data failed.
#[error("codec error: {0}")]
Codec(#[from] codec::Error),
}
fn fmt_keys(first: Option<&Vec<u8>>, last: Option<&Vec<u8>>) -> String {
if let (Some(first), Some(last)) = (first, last) {
if first == last {
HexDisplay::from(first).to_string()
} else {
format!("{}..{}", HexDisplay::from(first), HexDisplay::from(last))
}
} else {
String::from("n/a")
}
}
@@ -0,0 +1,25 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Include sources generated from protobuf definitions.
pub(crate) mod v1 {
pub(crate) mod light {
include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs"));
}
}
@@ -0,0 +1,120 @@
// Schema definition for light client messages.
syntax = "proto3";
package api.v1.light;
// A pair of arbitrary bytes.
message Pair {
// The first element of the pair.
bytes fst = 1;
// The second element of the pair.
bytes snd = 2;
}
// Enumerate all possible light client request messages.
message Request {
oneof request {
RemoteCallRequest remote_call_request = 1;
RemoteReadRequest remote_read_request = 2;
RemoteHeaderRequest remote_header_request = 3;
RemoteReadChildRequest remote_read_child_request = 4;
RemoteChangesRequest remote_changes_request = 5;
}
}
// Enumerate all possible light client response messages.
message Response {
oneof response {
RemoteCallResponse remote_call_response = 1;
RemoteReadResponse remote_read_response = 2;
RemoteHeaderResponse remote_header_response = 3;
RemoteChangesResponse remote_changes_response = 4;
}
}
// Remote call request.
message RemoteCallRequest {
// Block at which to perform call.
bytes block = 2;
// Method name.
string method = 3;
// Call data.
bytes data = 4;
}
// Remote call response.
message RemoteCallResponse {
// Execution proof.
bytes proof = 2;
}
// Remote storage read request.
message RemoteReadRequest {
// Block at which to perform call.
bytes block = 2;
// Storage keys.
repeated bytes keys = 3;
}
// Remote read response.
message RemoteReadResponse {
// Read proof.
bytes proof = 2;
}
// Remote storage read child request.
message RemoteReadChildRequest {
// Block at which to perform call.
bytes block = 2;
// Child Storage key, this is relative
// to the child type storage location.
bytes storage_key = 3;
// Storage keys.
repeated bytes keys = 6;
}
// Remote header request.
message RemoteHeaderRequest {
// Block number to request header for.
bytes block = 2;
}
// Remote header response.
message RemoteHeaderResponse {
// Header. None if proof generation has failed (e.g. header is unknown).
bytes header = 2; // optional
// Header proof.
bytes proof = 3;
}
/// Remote changes request.
message RemoteChangesRequest {
// Hash of the first block of the range (including first) where changes are requested.
bytes first = 2;
// Hash of the last block of the range (including last) where changes are requested.
bytes last = 3;
// Hash of the first block for which the requester has the changes trie root. All other
// affected roots must be proved.
bytes min = 4;
// Hash of the last block that we can use when querying changes.
bytes max = 5;
// Storage child node key which changes are requested.
bytes storage_key = 6; // optional
// Storage key which changes are requested.
bytes key = 7;
}
// Remote changes response.
message RemoteChangesResponse {
// Proof has been generated using block with this number as a max block. Should be
// less than or equal to the RemoteChangesRequest::max block number.
bytes max = 2;
// Changes proof.
repeated bytes proof = 3;
// Changes tries roots missing on the requester' node.
repeated Pair roots = 4;
// Missing changes tries roots proof.
bytes roots_proof = 5;
}