diff --git a/bridges/bin/node/node/Cargo.toml b/bridges/bin/node/node/Cargo.toml index 4834c783c8570..c163f8e89b77f 100644 --- a/bridges/bin/node/node/Cargo.toml +++ b/bridges/bin/node/node/Cargo.toml @@ -16,6 +16,7 @@ bp-eth-poa = { version = "0.1.0", path = "../../../primitives/ethereum-poa" } futures = "0.3.5" jsonrpc-core = "14.2.0" log = "0.4.11" +pallet-message-lane-rpc = { version = "0.1.0", path = "../../../modules/message-lane/rpc" } structopt = "0.3.17" [dependencies.bridge-node-runtime] diff --git a/bridges/modules/message-lane/rpc/Cargo.toml b/bridges/modules/message-lane/rpc/Cargo.toml new file mode 100644 index 0000000000000..83c41604ad1ec --- /dev/null +++ b/bridges/modules/message-lane/rpc/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-message-lane-rpc" +description = "Module that provides RPC methods specific to message-lane pallet." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +bp-runtime = { path = "../../../primitives/runtime" } +bp-message-lane = { path = "../../../primitives/message-lane" } +derive_more = "0.99.2" +futures = { version = "0.3.5", features = ["compat"] } +jsonrpc-core = "14.2.0" +jsonrpc-core-client = "14.2.0" +jsonrpc-derive = "14.2.1" + +# Substrate Based Dependencies + +[dependencies.sc-client-api] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-blockchain] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-core] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-runtime] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-state-machine] +version = "0.8.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-trie] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + +[dev-dependencies] +async-std = "1.6.2" + +[dev-dependencies.substrate-test-runtime-client] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" diff --git a/bridges/modules/message-lane/rpc/src/error.rs b/bridges/modules/message-lane/rpc/src/error.rs new file mode 100644 index 0000000000000..74fd829fcdb3b --- /dev/null +++ b/bridges/modules/message-lane/rpc/src/error.rs @@ -0,0 +1,59 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Possible errors and results of message-lane RPC calls. + +/// Future Result type. +pub type FutureResult = jsonrpc_core::BoxFuture; + +/// State RPC errors. +#[derive(Debug, derive_more::Display, derive_more::From)] +pub enum Error { + /// When unknown instance id is passed. + #[display(fmt = "Message lane instance is unknown")] + UnknownInstance, + /// Client error. + #[display(fmt = "Client error: {}", _0)] + Client(Box), +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::UnknownInstance => None, + Error::Client(ref err) => Some(&**err), + } + } +} + +impl From for jsonrpc_core::Error { + fn from(e: Error) -> Self { + const UNKNOW_INSTANCE_CODE: i64 = 1; + + match e { + Error::UnknownInstance => jsonrpc_core::Error { + code: jsonrpc_core::ErrorCode::ServerError(UNKNOW_INSTANCE_CODE), + message: "Unknown instance passed".into(), + data: None, + }, + Error::Client(e) => jsonrpc_core::Error { + code: jsonrpc_core::ErrorCode::InternalError, + message: format!("Unknown error occured: {}", e), + data: Some(format!("{:?}", e).into()), + }, + } + } +} diff --git a/bridges/modules/message-lane/rpc/src/lib.rs b/bridges/modules/message-lane/rpc/src/lib.rs new file mode 100644 index 0000000000000..3d0a0f484a1fd --- /dev/null +++ b/bridges/modules/message-lane/rpc/src/lib.rs @@ -0,0 +1,281 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Module that provides RPC methods specific to message-lane pallet. + +use crate::error::{Error, FutureResult}; + +use bp_message_lane::{LaneId, MessageNonce}; +use bp_runtime::InstanceId; +use futures::{FutureExt, TryFutureExt}; +use jsonrpc_core::futures::Future as _; +use jsonrpc_derive::rpc; +use sc_client_api::Backend as BackendT; +use sp_blockchain::{Error as BlockchainError, HeaderBackend}; +use sp_core::{storage::StorageKey, Bytes}; +use sp_runtime::{codec::Encode, generic::BlockId, traits::Block as BlockT}; +use sp_state_machine::prove_read; +use sp_trie::StorageProof; +use std::sync::Arc; + +mod error; + +/// Trie-based storage proof that the message(s) with given key(s) have been sent by the bridged chain. +/// SCALE-encoded trie nodes array `Vec>`. +pub type MessagesProof = Bytes; + +/// Trie-based storage proof that the message(s) with given key(s) have been received by the bridged chain. +/// SCALE-encoded trie nodes array `Vec>`. +pub type MessagesReceivingProof = Bytes; + +/// Trie-based storage proof that the message(s) with given key(s) have been processed by the bridged chain. +/// SCALE-encoded trie nodes array `Vec>`. +pub type MessagesProcessingProof = Bytes; + +/// Runtime adapter. +pub trait Runtime: Send + Sync + 'static { + /// Return runtime storage key for given message. May return None if instance is unknown. + fn message_key(&self, instance: &InstanceId, lane: &LaneId, nonce: MessageNonce) -> Option; + /// Return runtime storage key for inbound lane state. May return None if instance is unknown. + fn inbound_lane_data_key(&self, instance: &InstanceId, lane: &LaneId) -> Option; +} + +/// Provides RPC methods for interacting with message-lane pallet. +#[rpc] +pub trait MessageLaneApi { + /// Returns proof-of-message(s) in given inclusive range. + #[rpc(name = "messageLane_proveMessages")] + fn prove_messages( + &self, + instance: InstanceId, + lane: LaneId, + begin: MessageNonce, + end: MessageNonce, + block: Option, + ) -> FutureResult; + + /// Returns proof-of-message(s) receiving. + #[rpc(name = "messageLane_proveMessagesReceiving")] + fn prove_messages_receiving( + &self, + instance: InstanceId, + lane: LaneId, + block: Option, + ) -> FutureResult; + + /// Returns proof-of-message(s) processing. + #[rpc(name = "messageLane_proveMessagesProcessing")] + fn prove_messages_processing( + &self, + instance: InstanceId, + lane: LaneId, + block: Option, + ) -> FutureResult; +} + +/// Implements the MessageLaneApi trait for interacting with message lanes. +pub struct MessageLaneRpcHandler { + backend: Arc, + runtime: Arc, + _phantom: std::marker::PhantomData, +} + +impl MessageLaneRpcHandler { + /// Creates new mesage lane RPC handler. + pub fn new(backend: Arc, runtime: Arc) -> Self { + Self { + backend, + runtime, + _phantom: Default::default(), + } + } +} + +impl MessageLaneApi for MessageLaneRpcHandler +where + Block: BlockT, + Backend: BackendT + 'static, + R: Runtime, +{ + fn prove_messages( + &self, + instance: InstanceId, + lane: LaneId, + begin: MessageNonce, + end: MessageNonce, + block: Option, + ) -> FutureResult { + let runtime = self.runtime.clone(); + Box::new( + prove_keys_read( + self.backend.clone(), + block, + (begin..=end).map(move |nonce| runtime.message_key(&instance, &lane, nonce)), + ) + .boxed() + .compat() + .map(serialize_storage_proof) + .map_err(Into::into), + ) + } + + fn prove_messages_receiving( + &self, + instance: InstanceId, + lane: LaneId, + block: Option, + ) -> FutureResult { + Box::new( + prove_keys_read( + self.backend.clone(), + block, + vec![self.runtime.inbound_lane_data_key(&instance, &lane)], + ) + .boxed() + .compat() + .map(serialize_storage_proof) + .map_err(Into::into), + ) + } + + fn prove_messages_processing( + &self, + instance: InstanceId, + lane: LaneId, + block: Option, + ) -> FutureResult { + Box::new( + prove_keys_read( + self.backend.clone(), + block, + vec![self.runtime.inbound_lane_data_key(&instance, &lane)], + ) + .boxed() + .compat() + .map(serialize_storage_proof) + .map_err(Into::into), + ) + } +} + +async fn prove_keys_read( + backend: Arc, + block: Option, + keys: impl IntoIterator>, +) -> Result +where + Block: BlockT, + Backend: BackendT + 'static, +{ + let block = unwrap_or_best(&*backend, block); + let state = backend.state_at(BlockId::Hash(block)).map_err(blockchain_err)?; + let keys = keys + .into_iter() + .map(|key| key.ok_or(Error::UnknownInstance).map(|key| key.0)) + .collect::, _>>()?; + let storage_proof = prove_read(state, keys) + .map_err(BlockchainError::Execution) + .map_err(blockchain_err)?; + Ok(storage_proof) +} + +fn serialize_storage_proof(proof: StorageProof) -> Bytes { + let raw_nodes: Vec> = proof.iter_nodes().map(Into::into).collect(); + raw_nodes.encode().into() +} + +fn unwrap_or_best(backend: &impl BackendT, block: Option) -> Block::Hash { + match block { + Some(block) => block, + None => backend.blockchain().info().best_hash, + } +} + +fn blockchain_err(err: BlockchainError) -> Error { + Error::Client(Box::new(err)) +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::Blake2Hasher; + use sp_runtime::{codec::Decode, traits::Header as HeaderT}; + use substrate_test_runtime_client::{ + runtime::Block, Backend, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, + }; + + const TEST_INSTANCE: InstanceId = [0, 0, 0, 1]; + const TEST_LANE: LaneId = [0, 0, 0, 1]; + + fn test_key() -> StorageKey { + StorageKey(sp_core::storage::well_known_keys::CODE.to_vec()) + } + + struct TestRuntimeAdapter; + + impl Runtime for TestRuntimeAdapter { + fn message_key(&self, instance: &InstanceId, _lane: &LaneId, _nonce: MessageNonce) -> Option { + if *instance == TEST_INSTANCE { + Some(test_key()) + } else { + None + } + } + + fn inbound_lane_data_key(&self, instance: &InstanceId, _lane: &LaneId) -> Option { + if *instance == TEST_INSTANCE { + Some(test_key()) + } else { + None + } + } + } + + fn test_handler() -> MessageLaneRpcHandler { + let builder = TestClientBuilder::new(); + let (_, backend) = builder.build_with_backend(); + + MessageLaneRpcHandler::new(backend, Arc::new(TestRuntimeAdapter)) + } + + #[test] + fn storage_proof_is_actually_generated() { + // the only thing we actually care here is that RPC actually generates storage proof + // that can be verified from runtime + + // proof is generated by RPC + let handler = test_handler(); + let proof = handler + .prove_messages(TEST_INSTANCE, TEST_LANE, 1, 3, None) + .wait() + .unwrap(); + + // proof is then relayed + checked by runtime (sp_trie supports no_std) + // (storage root is known to underlying bridge pallet) + let root = *handler + .backend + .blockchain() + .header(BlockId::Number(0)) + .unwrap() + .unwrap() + .state_root(); + let proof = StorageProof::new(Decode::decode(&mut &proof[..]).unwrap()); + let trie_db = proof.into_memory_db::(); + let checked_storage_value = + sp_trie::read_trie_value::, _>(&trie_db, &root, &test_key().0).unwrap(); + assert!(checked_storage_value.is_some()); + } +} diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml new file mode 100644 index 0000000000000..84dd53e4d380c --- /dev/null +++ b/bridges/primitives/runtime/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bp-runtime" +description = "Primitives that may be used at (bridges) runtime level." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[features] +default = ["std"] +std = [] diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs new file mode 100644 index 0000000000000..fa41b2126a43d --- /dev/null +++ b/bridges/primitives/runtime/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives that may be used at (bridges) runtime level. + +#![cfg_attr(not(feature = "std"), no_std)] + +/// Id of deployed module instance. We have a bunch of pallets that may be used in +/// different bridges. E.g. message-lane pallet may be deployed twice in the same +/// runtime to bridge ThisChain with Chain1 and Chain2. Sometimes we need to be able +/// to identify deployed instance dynamically. This type is used for that. +pub type InstanceId = [u8; 4];