use crate::schema;
use codec::{self, Decode, Encode};
use futures::prelude::*;
use log::{debug, trace};
use prost::Message;
use sc_client_api::{BlockBackend, ProofProvider};
use sc_network::{
config::ProtocolId,
request_responses::{IncomingRequest, OutgoingResponse},
NetworkBackend, ReputationChange,
};
use sc_network_types::PeerId;
use sp_core::{
hexdisplay::HexDisplay,
storage::{ChildInfo, ChildType, PrefixedStorageKey},
};
use sp_runtime::traits::Block;
use std::{marker::PhantomData, sync::Arc};
const LOG_TARGET: &str = "light-client-request-handler";
const MAX_LIGHT_REQUEST_QUEUE: usize = 20;
pub struct LightClientRequestHandler<B, Client> {
request_receiver: async_channel::Receiver<IncomingRequest>,
client: Arc<Client>,
_block: PhantomData<B>,
}
impl<B, Client> LightClientRequestHandler<B, Client>
where
B: Block,
Client: BlockBackend<B> + ProofProvider<B> + Send + Sync + 'static,
{
pub fn new<N: NetworkBackend<B, <B as Block>::Hash>>(
protocol_id: &ProtocolId,
fork_id: Option<&str>,
client: Arc<Client>,
) -> (Self, N::RequestResponseProtocolConfig) {
let (tx, request_receiver) = async_channel::bounded(MAX_LIGHT_REQUEST_QUEUE);
let protocol_config = super::generate_protocol_config::<_, B, N>(
protocol_id,
client
.block_hash(0u32.into())
.ok()
.flatten()
.expect("Genesis block exists; qed"),
fork_id,
tx,
);
(Self { client, request_receiver, _block: PhantomData::default() }, protocol_config)
}
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::RemoteReadChildRequest(r)) =>
self.on_remote_read_child_request(&peer, r)?,
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 response = match self.client.execution_proof(block, &request.method, &request.data) {
Ok((_, proof)) => schema::v1::light::RemoteCallResponse { proof: Some(proof.encode()) },
Err(e) => {
trace!(
"remote call request from {} ({} at {:?}) failed with: {}",
peer,
request.method,
request.block,
e,
);
schema::v1::light::RemoteCallResponse { proof: None }
},
};
Ok(schema::v1::light::Response {
response: Some(schema::v1::light::response::Response::RemoteCallResponse(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 response =
match self.client.read_proof(block, &mut request.keys.iter().map(AsRef::as_ref)) {
Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
Err(error) => {
trace!(
"remote read request from {} ({} at {:?}) failed with: {}",
peer,
fmt_keys(request.keys.first(), request.keys.last()),
request.block,
error,
);
schema::v1::light::RemoteReadResponse { proof: None }
},
};
Ok(schema::v1::light::Response {
response: Some(schema::v1::light::response::Response::RemoteReadResponse(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 response = match child_info.and_then(|child_info| {
self.client.read_child_proof(
block,
&child_info,
&mut request.keys.iter().map(AsRef::as_ref),
)
}) {
Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) },
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,
);
schema::v1::light::RemoteReadResponse { proof: None }
},
};
Ok(schema::v1::light::Response {
response: Some(schema::v1::light::response::Response::RemoteReadResponse(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,
#[error("bad request: {0}")]
BadRequest(&'static str),
#[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")
}
}