mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Allow to expose a subset of unsafe RPCs (#5233)
* sc-cli: Use type-safe constructors for RPC/Prometheus interfaces * service: Simplify rpc handler creation Could probably be further simplifies once [this][commit] lands. [commit]: https://github.com/paritytech/jsonrpc/commit/20485387ed06a48f1a70bf4d609a7cde6cf0accf * service: Streamline some HTTP & WS server start logic * client: Introduce a simple RPC policy mechanism * rpc/system: Check unsafe RPCs * rpc/offchain: Check unsafe RPCs * rpc/author: Check unsafe RPCs
This commit is contained in:
@@ -279,6 +279,12 @@ macro_rules! substrate_cli_subcommands {
|
||||
}
|
||||
}
|
||||
|
||||
fn unsafe_rpc_expose(&self) -> $crate::Result<bool> {
|
||||
match self {
|
||||
$($enum::$variant(cmd) => cmd.unsafe_rpc_expose()),*
|
||||
}
|
||||
}
|
||||
|
||||
fn rpc_ws_max_connections(&self) -> $crate::Result<::std::option::Option<usize>> {
|
||||
match self {
|
||||
$($enum::$variant(cmd) => cmd.rpc_ws_max_connections()),*
|
||||
|
||||
@@ -27,7 +27,7 @@ use sc_service::{
|
||||
ChainSpec, Role,
|
||||
};
|
||||
use sc_telemetry::TelemetryEndpoints;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
arg_enum! {
|
||||
@@ -93,6 +93,14 @@ pub struct RunCmd {
|
||||
#[structopt(long = "unsafe-rpc-external")]
|
||||
pub unsafe_rpc_external: bool,
|
||||
|
||||
/// Don't deny potentially unsafe RPCs when listening on external interfaces.
|
||||
///
|
||||
/// Default is false. This allows exposing RPC methods publicly (same as `--unsafe-{rpc,ws}-external` )
|
||||
/// but will allow doing so even on validator nodes, which is prohibited by default.
|
||||
/// Please do this if you know what you're doing.
|
||||
#[structopt(long = "unsafe-rpc-expose")]
|
||||
pub unsafe_rpc_expose: bool,
|
||||
|
||||
/// Listen to all Websocket interfaces.
|
||||
///
|
||||
/// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC proxy
|
||||
@@ -361,22 +369,19 @@ impl CliConfiguration for RunCmd {
|
||||
}
|
||||
|
||||
fn prometheus_config(&self) -> Result<Option<PrometheusConfig>> {
|
||||
if self.no_prometheus {
|
||||
Ok(None)
|
||||
Ok(if self.no_prometheus {
|
||||
None
|
||||
} else {
|
||||
let prometheus_interface: &str = if self.prometheus_external {
|
||||
"0.0.0.0"
|
||||
let interface = if self.prometheus_external {
|
||||
Ipv4Addr::UNSPECIFIED
|
||||
} else {
|
||||
"127.0.0.1"
|
||||
Ipv4Addr::LOCALHOST
|
||||
};
|
||||
|
||||
Ok(Some(PrometheusConfig::new_with_default_registry(
|
||||
parse_address(
|
||||
&format!("{}:{}", prometheus_interface, 9615),
|
||||
self.prometheus_port,
|
||||
)?,
|
||||
)))
|
||||
}
|
||||
Some(PrometheusConfig::new_with_default_registry(
|
||||
SocketAddr::new(interface.into(), self.prometheus_port.unwrap_or(9615))
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn disable_grandpa(&self) -> Result<bool> {
|
||||
@@ -409,23 +414,29 @@ impl CliConfiguration for RunCmd {
|
||||
}
|
||||
|
||||
fn rpc_http(&self) -> Result<Option<SocketAddr>> {
|
||||
let rpc_interface: &str =
|
||||
interface_str(self.rpc_external, self.unsafe_rpc_external, self.validator)?;
|
||||
let interface = rpc_interface(
|
||||
self.rpc_external,
|
||||
self.unsafe_rpc_external,
|
||||
self.unsafe_rpc_expose,
|
||||
self.validator
|
||||
)?;
|
||||
|
||||
Ok(Some(parse_address(
|
||||
&format!("{}:{}", rpc_interface, 9933),
|
||||
self.rpc_port,
|
||||
)?))
|
||||
Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(9933))))
|
||||
}
|
||||
|
||||
fn rpc_ws(&self) -> Result<Option<SocketAddr>> {
|
||||
let ws_interface: &str =
|
||||
interface_str(self.ws_external, self.unsafe_ws_external, self.validator)?;
|
||||
let interface = rpc_interface(
|
||||
self.ws_external,
|
||||
self.unsafe_ws_external,
|
||||
self.unsafe_rpc_expose,
|
||||
self.validator
|
||||
)?;
|
||||
|
||||
Ok(Some(parse_address(
|
||||
&format!("{}:{}", ws_interface, 9944),
|
||||
self.ws_port,
|
||||
)?))
|
||||
Ok(Some(SocketAddr::new(interface, self.ws_port.unwrap_or(9944))))
|
||||
}
|
||||
|
||||
fn unsafe_rpc_expose(&self) -> Result<bool> {
|
||||
Ok(self.unsafe_rpc_expose)
|
||||
}
|
||||
|
||||
fn offchain_worker(&self, role: &Role) -> Result<bool> {
|
||||
@@ -468,23 +479,13 @@ pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_address(address: &str, port: Option<u16>) -> std::result::Result<SocketAddr, String> {
|
||||
let mut address: SocketAddr = address
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid address: {}", address))?;
|
||||
if let Some(port) = port {
|
||||
address.set_port(port);
|
||||
}
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
fn interface_str(
|
||||
fn rpc_interface(
|
||||
is_external: bool,
|
||||
is_unsafe_external: bool,
|
||||
is_unsafe_rpc_expose: bool,
|
||||
is_validator: bool,
|
||||
) -> Result<&'static str> {
|
||||
if is_external && is_validator {
|
||||
) -> Result<IpAddr> {
|
||||
if is_external && is_validator && !is_unsafe_rpc_expose {
|
||||
return Err(Error::Input(
|
||||
"--rpc-external and --ws-external options shouldn't be \
|
||||
used if the node is running as a validator. Use `--unsafe-rpc-external` if you understand \
|
||||
@@ -499,9 +500,9 @@ fn interface_str(
|
||||
available set of RPC methods."
|
||||
);
|
||||
|
||||
Ok("0.0.0.0")
|
||||
Ok(Ipv4Addr::UNSPECIFIED.into())
|
||||
} else {
|
||||
Ok("127.0.0.1")
|
||||
Ok(Ipv4Addr::LOCALHOST.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,13 @@ pub trait CliConfiguration: Sized {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
/// Returns `Ok(true) if potentially unsafe RPC is to be exposed.
|
||||
///
|
||||
/// By default this is `false`.
|
||||
fn unsafe_rpc_expose(&self) -> Result<bool> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
/// Get the RPC websockets maximum connections (`None` if unlimited).
|
||||
///
|
||||
/// By default this is `None`.
|
||||
@@ -419,6 +426,7 @@ pub trait CliConfiguration: Sized {
|
||||
execution_strategies: self.execution_strategies(is_dev)?,
|
||||
rpc_http: self.rpc_http()?,
|
||||
rpc_ws: self.rpc_ws()?,
|
||||
unsafe_rpc_expose: self.unsafe_rpc_expose()?,
|
||||
rpc_ws_max_connections: self.rpc_ws_max_connections()?,
|
||||
rpc_cors: self.rpc_cors(is_dev)?,
|
||||
prometheus_config: self.prometheus_config()?,
|
||||
|
||||
@@ -57,6 +57,8 @@ pub enum Error {
|
||||
/// Invalid session keys encoding.
|
||||
#[display(fmt="Session keys are not encoded correctly")]
|
||||
InvalidSessionKeys,
|
||||
/// Call to an unsafe RPC was denied.
|
||||
UnsafeRpcCalled(crate::policy::UnsafeRpcError),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
@@ -65,6 +67,7 @@ impl std::error::Error for Error {
|
||||
Error::Client(ref err) => Some(&**err),
|
||||
Error::Pool(ref err) => Some(err),
|
||||
Error::Verification(ref err) => Some(&**err),
|
||||
Error::UnsafeRpcCalled(ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -152,6 +155,7 @@ impl From<Error> for rpc::Error {
|
||||
request to insert the key successfully.".into()
|
||||
),
|
||||
},
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
e => errors::internal(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
mod errors;
|
||||
mod helpers;
|
||||
mod policy;
|
||||
mod subscriptions;
|
||||
|
||||
pub use jsonrpc_core::IoHandlerExtension as RpcExtension;
|
||||
pub use subscriptions::{Subscriptions, TaskExecutor};
|
||||
pub use helpers::Receiver;
|
||||
pub use policy::DenyUnsafe;
|
||||
|
||||
pub mod author;
|
||||
pub mod chain;
|
||||
|
||||
@@ -27,11 +27,16 @@ pub enum Error {
|
||||
/// Unavailable storage kind error.
|
||||
#[display(fmt="This storage kind is not available yet.")]
|
||||
UnavailableStorageKind,
|
||||
/// Call to an unsafe RPC was denied.
|
||||
UnsafeRpcCalled(crate::policy::UnsafeRpcError),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
match self {
|
||||
Self::UnsafeRpcCalled(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +51,7 @@ impl From<Error> for rpc::Error {
|
||||
message: "This storage kind is not available yet" .into(),
|
||||
data: None,
|
||||
},
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Policy-related types.
|
||||
//!
|
||||
//! Contains a `DenyUnsafe` type that can be used to deny potentially unsafe
|
||||
//! RPC when accessed externally.
|
||||
|
||||
use jsonrpc_core as rpc;
|
||||
|
||||
/// Signifies whether a potentially unsafe RPC should be denied.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DenyUnsafe {
|
||||
/// Denies only potentially unsafe RPCs.
|
||||
Yes,
|
||||
/// Allows calling every RPCs.
|
||||
No
|
||||
}
|
||||
|
||||
impl DenyUnsafe {
|
||||
/// Returns `Ok(())` if the RPCs considered unsafe are safe to call,
|
||||
/// otherwise returns `Err(UnsafeRpcError)`.
|
||||
pub fn check_if_safe(self) -> Result<(), UnsafeRpcError> {
|
||||
match self {
|
||||
DenyUnsafe::Yes => Err(UnsafeRpcError),
|
||||
DenyUnsafe::No => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signifies whether an RPC considered unsafe is denied to be called externally.
|
||||
#[derive(Debug)]
|
||||
pub struct UnsafeRpcError;
|
||||
|
||||
impl std::fmt::Display for UnsafeRpcError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RPC call is unsafe to be called externally")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnsafeRpcError {}
|
||||
|
||||
impl From<UnsafeRpcError> for rpc::Error {
|
||||
fn from(_: UnsafeRpcError) -> rpc::Error {
|
||||
rpc::Error::method_not_found()
|
||||
}
|
||||
}
|
||||
@@ -72,14 +72,16 @@ pub trait SystemApi<Hash, Number> {
|
||||
|
||||
/// Returns currently connected peers
|
||||
#[rpc(name = "system_peers", returns = "Vec<PeerInfo<Hash, Number>>")]
|
||||
fn system_peers(&self) -> Receiver<Vec<PeerInfo<Hash, Number>>>;
|
||||
fn system_peers(&self)
|
||||
-> Compat<BoxFuture<'static, jsonrpc_core::Result<Vec<PeerInfo<Hash, Number>>>>>;
|
||||
|
||||
/// Returns current state of the network.
|
||||
///
|
||||
/// **Warning**: This API is not stable.
|
||||
// TODO: make this stable and move structs https://github.com/paritytech/substrate/issues/1890
|
||||
#[rpc(name = "system_networkState", returns = "jsonrpc_core::Value")]
|
||||
fn system_network_state(&self) -> Receiver<jsonrpc_core::Value>;
|
||||
fn system_network_state(&self)
|
||||
-> Compat<BoxFuture<'static, jsonrpc_core::Result<jsonrpc_core::Value>>>;
|
||||
|
||||
/// Adds a reserved peer. Returns the empty string or an error. The string
|
||||
/// parameter should encode a `p2p` multiaddr.
|
||||
|
||||
@@ -30,7 +30,7 @@ use rpc::futures::{
|
||||
};
|
||||
use futures::{StreamExt as _, compat::Compat};
|
||||
use futures::future::{ready, FutureExt, TryFutureExt};
|
||||
use sc_rpc_api::Subscriptions;
|
||||
use sc_rpc_api::{DenyUnsafe, Subscriptions};
|
||||
use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_core::{Bytes, traits::BareCryptoStorePtr};
|
||||
@@ -56,6 +56,8 @@ pub struct Author<P, Client> {
|
||||
subscriptions: Subscriptions,
|
||||
/// The key store.
|
||||
keystore: BareCryptoStorePtr,
|
||||
/// Whether to deny unsafe calls
|
||||
deny_unsafe: DenyUnsafe,
|
||||
}
|
||||
|
||||
impl<P, Client> Author<P, Client> {
|
||||
@@ -65,12 +67,14 @@ impl<P, Client> Author<P, Client> {
|
||||
pool: Arc<P>,
|
||||
subscriptions: Subscriptions,
|
||||
keystore: BareCryptoStorePtr,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
) -> Self {
|
||||
Author {
|
||||
client,
|
||||
pool,
|
||||
subscriptions,
|
||||
keystore,
|
||||
deny_unsafe,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +101,8 @@ impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
|
||||
suri: String,
|
||||
public: Bytes,
|
||||
) -> Result<()> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
|
||||
let mut keystore = self.keystore.write();
|
||||
keystore.insert_unknown(key_type, &suri, &public[..])
|
||||
@@ -105,6 +111,8 @@ impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
|
||||
}
|
||||
|
||||
fn rotate_keys(&self) -> Result<Bytes> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
self.client.runtime_api().generate_session_keys(
|
||||
&generic::BlockId::Hash(best_block_hash),
|
||||
@@ -113,6 +121,8 @@ impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
|
||||
}
|
||||
|
||||
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
let keys = self.client.runtime_api().decode_session_keys(
|
||||
&generic::BlockId::Hash(best_block_hash),
|
||||
@@ -124,6 +134,8 @@ impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
|
||||
}
|
||||
|
||||
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
|
||||
Ok(self.keystore.read().has_keys(&[(public_key.to_vec(), key_type)]))
|
||||
}
|
||||
@@ -151,6 +163,8 @@ impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
|
||||
&self,
|
||||
bytes_or_hash: Vec<hash::ExtrinsicOrHash<TxHash<P>>>,
|
||||
) -> Result<Vec<TxHash<P>>> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let hashes = bytes_or_hash.into_iter()
|
||||
.map(|x| match x {
|
||||
hash::ExtrinsicOrHash::Hash(h) => Ok(h),
|
||||
|
||||
@@ -83,6 +83,7 @@ impl TestSetup {
|
||||
pool: self.pool.clone(),
|
||||
subscriptions: Subscriptions::new(Arc::new(self.runtime.executor())),
|
||||
keystore: self.keystore.clone(),
|
||||
deny_unsafe: DenyUnsafe::No,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
mod metadata;
|
||||
|
||||
pub use sc_rpc_api::Subscriptions;
|
||||
pub use sc_rpc_api::{DenyUnsafe, Subscriptions};
|
||||
pub use self::metadata::Metadata;
|
||||
pub use rpc::IoHandlerExtension as RpcExtension;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ mod tests;
|
||||
|
||||
/// Re-export the API for backward compatibility.
|
||||
pub use sc_rpc_api::offchain::*;
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use self::error::{Error, Result};
|
||||
use sp_core::{
|
||||
Bytes,
|
||||
@@ -34,13 +35,15 @@ use std::sync::Arc;
|
||||
pub struct Offchain<T: OffchainStorage> {
|
||||
/// Offchain storage
|
||||
storage: Arc<RwLock<T>>,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
}
|
||||
|
||||
impl<T: OffchainStorage> Offchain<T> {
|
||||
/// Create new instance of Offchain API.
|
||||
pub fn new(storage: T) -> Self {
|
||||
pub fn new(storage: T, deny_unsafe: DenyUnsafe) -> Self {
|
||||
Offchain {
|
||||
storage: Arc::new(RwLock::new(storage)),
|
||||
deny_unsafe,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +51,8 @@ impl<T: OffchainStorage> Offchain<T> {
|
||||
impl<T: OffchainStorage + 'static> OffchainApi for Offchain<T> {
|
||||
/// Set offchain local storage under given key and prefix.
|
||||
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let prefix = match kind {
|
||||
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
|
||||
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
|
||||
@@ -58,6 +63,8 @@ impl<T: OffchainStorage + 'static> OffchainApi for Offchain<T> {
|
||||
|
||||
/// Get offchain local storage under given key and prefix.
|
||||
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let prefix = match kind {
|
||||
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
|
||||
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
|
||||
|
||||
@@ -21,7 +21,7 @@ use sp_core::{Bytes, offchain::storage::InMemOffchainStorage};
|
||||
#[test]
|
||||
fn local_storage_should_work() {
|
||||
let storage = InMemOffchainStorage::default();
|
||||
let offchain = Offchain::new(storage);
|
||||
let offchain = Offchain::new(storage, DenyUnsafe::No);
|
||||
let key = Bytes(b"offchain_storage".to_vec());
|
||||
let value = Bytes(b"offchain_value".to_vec());
|
||||
|
||||
@@ -34,3 +34,20 @@ fn local_storage_should_work() {
|
||||
Ok(Some(ref v)) if *v == value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_calls_considered_unsafe() {
|
||||
let storage = InMemOffchainStorage::default();
|
||||
let offchain = Offchain::new(storage, DenyUnsafe::Yes);
|
||||
let key = Bytes(b"offchain_storage".to_vec());
|
||||
let value = Bytes(b"offchain_value".to_vec());
|
||||
|
||||
assert_matches!(
|
||||
offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()),
|
||||
Err(Error::UnsafeRpcCalled(_))
|
||||
);
|
||||
assert_matches!(
|
||||
offchain.get_local_storage(StorageKind::PERSISTENT, key),
|
||||
Err(Error::UnsafeRpcCalled(_))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ mod tests;
|
||||
|
||||
use futures::{future::BoxFuture, FutureExt, TryFutureExt};
|
||||
use futures::{channel::oneshot, compat::Compat};
|
||||
use sc_rpc_api::Receiver;
|
||||
use sc_rpc_api::{DenyUnsafe, Receiver};
|
||||
use sp_utils::mpsc::TracingUnboundedSender;
|
||||
use sp_runtime::traits::{self, Header as HeaderT};
|
||||
|
||||
@@ -31,10 +31,19 @@ pub use sc_rpc_api::system::*;
|
||||
pub use self::helpers::{SystemInfo, Health, PeerInfo, NodeRole};
|
||||
pub use self::gen_client::Client as SystemClient;
|
||||
|
||||
macro_rules! bail_if_unsafe {
|
||||
($value: expr) => {
|
||||
if let Err(err) = $value.check_if_safe() {
|
||||
return async move { Err(err.into()) }.boxed().compat();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// System API implementation
|
||||
pub struct System<B: traits::Block> {
|
||||
info: SystemInfo,
|
||||
send_back: TracingUnboundedSender<Request<B>>,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
}
|
||||
|
||||
/// Request to be processed.
|
||||
@@ -66,10 +75,12 @@ impl<B: traits::Block> System<B> {
|
||||
pub fn new(
|
||||
info: SystemInfo,
|
||||
send_back: TracingUnboundedSender<Request<B>>,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
) -> Self {
|
||||
System {
|
||||
info,
|
||||
send_back,
|
||||
deny_unsafe,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,21 +124,37 @@ impl<B: traits::Block> SystemApi<B::Hash, <B::Header as HeaderT>::Number> for Sy
|
||||
Receiver(Compat::new(rx))
|
||||
}
|
||||
|
||||
fn system_peers(&self) -> Receiver<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>> {
|
||||
fn system_peers(&self)
|
||||
-> Compat<BoxFuture<'static, rpc::Result<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>>>>
|
||||
{
|
||||
bail_if_unsafe!(self.deny_unsafe);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::Peers(tx));
|
||||
Receiver(Compat::new(rx))
|
||||
|
||||
async move {
|
||||
rx.await.map_err(|_| rpc::Error::internal_error())
|
||||
}.boxed().compat()
|
||||
}
|
||||
|
||||
fn system_network_state(&self) -> Receiver<rpc::Value> {
|
||||
fn system_network_state(&self)
|
||||
-> Compat<BoxFuture<'static, rpc::Result<rpc::Value>>>
|
||||
{
|
||||
bail_if_unsafe!(self.deny_unsafe);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkState(tx));
|
||||
Receiver(Compat::new(rx))
|
||||
|
||||
async move {
|
||||
rx.await.map_err(|_| rpc::Error::internal_error())
|
||||
}.boxed().compat()
|
||||
}
|
||||
|
||||
fn system_add_reserved_peer(&self, peer: String)
|
||||
-> Compat<BoxFuture<'static, std::result::Result<(), rpc::Error>>>
|
||||
{
|
||||
bail_if_unsafe!(self.deny_unsafe);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx));
|
||||
async move {
|
||||
@@ -142,6 +169,8 @@ impl<B: traits::Block> SystemApi<B::Hash, <B::Header as HeaderT>::Number> for Sy
|
||||
fn system_remove_reserved_peer(&self, peer: String)
|
||||
-> Compat<BoxFuture<'static, std::result::Result<(), rpc::Error>>>
|
||||
{
|
||||
bail_if_unsafe!(self.deny_unsafe);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx));
|
||||
async move {
|
||||
|
||||
@@ -109,13 +109,17 @@ fn api<T: Into<Option<Status>>>(sync: T) -> System<Block> {
|
||||
future::ready(())
|
||||
}))
|
||||
});
|
||||
System::new(SystemInfo {
|
||||
impl_name: "testclient".into(),
|
||||
impl_version: "0.2.0".into(),
|
||||
chain_name: "testchain".into(),
|
||||
properties: Default::default(),
|
||||
chain_type: Default::default(),
|
||||
}, tx)
|
||||
System::new(
|
||||
SystemInfo {
|
||||
impl_name: "testclient".into(),
|
||||
impl_version: "0.2.0".into(),
|
||||
chain_name: "testchain".into(),
|
||||
properties: Default::default(),
|
||||
chain_type: Default::default(),
|
||||
},
|
||||
tx,
|
||||
sc_rpc_api::DenyUnsafe::No
|
||||
)
|
||||
}
|
||||
|
||||
fn wait_receiver<T>(rx: Receiver<T>) -> T {
|
||||
@@ -238,14 +242,19 @@ fn system_local_listen_addresses_works() {
|
||||
|
||||
#[test]
|
||||
fn system_peers() {
|
||||
let mut runtime = tokio::runtime::current_thread::Runtime::new().unwrap();
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let req = api(Status {
|
||||
peer_id: peer_id.clone(),
|
||||
peers: 1,
|
||||
is_syncing: false,
|
||||
is_dev: true,
|
||||
}).system_peers();
|
||||
let res = runtime.block_on(req).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wait_receiver(api(Status {
|
||||
peer_id: peer_id.clone(),
|
||||
peers: 1,
|
||||
is_syncing: false,
|
||||
is_dev: true,
|
||||
}).system_peers()),
|
||||
res,
|
||||
vec![PeerInfo {
|
||||
peer_id: peer_id.to_base58(),
|
||||
roles: "FULL".into(),
|
||||
@@ -258,7 +267,10 @@ fn system_peers() {
|
||||
|
||||
#[test]
|
||||
fn system_network_state() {
|
||||
let res = wait_receiver(api(None).system_network_state());
|
||||
let mut runtime = tokio::runtime::current_thread::Runtime::new().unwrap();
|
||||
let req = api(None).system_network_state();
|
||||
let res = runtime.block_on(req).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
serde_json::from_value::<sc_network::network_state::NetworkState>(res).unwrap(),
|
||||
sc_network::network_state::NetworkState {
|
||||
|
||||
@@ -46,6 +46,7 @@ use sp_runtime::traits::{
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sc_executor::{NativeExecutor, NativeExecutionDispatch};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write, Seek},
|
||||
marker::PhantomData, sync::Arc, pin::Pin
|
||||
};
|
||||
@@ -1001,7 +1002,7 @@ ServiceBuilder<
|
||||
|
||||
// RPC
|
||||
let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc");
|
||||
let gen_handler = || {
|
||||
let gen_handler = |deny_unsafe: sc_rpc::DenyUnsafe| {
|
||||
use sc_rpc::{chain, state, author, system, offchain};
|
||||
|
||||
let system_info = sc_rpc::system::SystemInfo {
|
||||
@@ -1043,32 +1044,31 @@ ServiceBuilder<
|
||||
transaction_pool.clone(),
|
||||
subscriptions,
|
||||
keystore.clone(),
|
||||
deny_unsafe,
|
||||
);
|
||||
let system = system::System::new(system_info, system_rpc_tx.clone());
|
||||
let system = system::System::new(system_info, system_rpc_tx.clone(), deny_unsafe);
|
||||
|
||||
match offchain_storage.clone() {
|
||||
Some(storage) => {
|
||||
let offchain = sc_rpc::offchain::Offchain::new(storage);
|
||||
sc_rpc_server::rpc_handler((
|
||||
state::StateApi::to_delegate(state),
|
||||
chain::ChainApi::to_delegate(chain),
|
||||
offchain::OffchainApi::to_delegate(offchain),
|
||||
author::AuthorApi::to_delegate(author),
|
||||
system::SystemApi::to_delegate(system),
|
||||
rpc_extensions.clone(),
|
||||
))
|
||||
},
|
||||
None => sc_rpc_server::rpc_handler((
|
||||
state::StateApi::to_delegate(state),
|
||||
chain::ChainApi::to_delegate(chain),
|
||||
author::AuthorApi::to_delegate(author),
|
||||
system::SystemApi::to_delegate(system),
|
||||
rpc_extensions.clone(),
|
||||
))
|
||||
}
|
||||
let maybe_offchain_rpc = offchain_storage.clone()
|
||||
.map(|storage| {
|
||||
let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe);
|
||||
// FIXME: Use plain Option (don't collect into HashMap) when we upgrade to jsonrpc 14.1
|
||||
// https://github.com/paritytech/jsonrpc/commit/20485387ed06a48f1a70bf4d609a7cde6cf0accf
|
||||
let delegate = offchain::OffchainApi::to_delegate(offchain);
|
||||
delegate.into_iter().collect::<HashMap<_, _>>()
|
||||
}).unwrap_or_default();
|
||||
|
||||
sc_rpc_server::rpc_handler((
|
||||
state::StateApi::to_delegate(state),
|
||||
chain::ChainApi::to_delegate(chain),
|
||||
maybe_offchain_rpc,
|
||||
author::AuthorApi::to_delegate(author),
|
||||
system::SystemApi::to_delegate(system),
|
||||
rpc_extensions.clone(),
|
||||
))
|
||||
};
|
||||
let rpc_handlers = gen_handler();
|
||||
let rpc = start_rpc_servers(&config, gen_handler)?;
|
||||
// This is used internally, so don't restrict access to unsafe RPC
|
||||
let rpc_handlers = gen_handler(sc_rpc::DenyUnsafe::No);
|
||||
|
||||
spawn_handle.spawn(
|
||||
"network-worker",
|
||||
|
||||
@@ -59,6 +59,8 @@ pub struct Configuration {
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
/// Execution strategies.
|
||||
pub execution_strategies: ExecutionStrategies,
|
||||
/// Whether potentially unsafe RPC is considered safe to be exposed.
|
||||
pub unsafe_rpc_expose: bool,
|
||||
/// RPC over HTTP binding address. `None` if disabled.
|
||||
pub rpc_http: Option<SocketAddr>,
|
||||
/// RPC over Websockets binding address. `None` if disabled.
|
||||
|
||||
@@ -511,7 +511,7 @@ mod waiting {
|
||||
|
||||
/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them alive.
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
fn start_rpc_servers<H: FnMut() -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
fn start_rpc_servers<H: FnMut(sc_rpc::DenyUnsafe) -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
config: &Configuration,
|
||||
mut gen_handler: H
|
||||
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error> {
|
||||
@@ -533,10 +533,23 @@ fn start_rpc_servers<H: FnMut() -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
})
|
||||
}
|
||||
|
||||
fn deny_unsafe(addr: &Option<SocketAddr>, unsafe_rpc_expose: bool) -> sc_rpc::DenyUnsafe {
|
||||
let is_exposed_addr = addr.map(|x| x.ip().is_loopback()).unwrap_or(false);
|
||||
if is_exposed_addr && !unsafe_rpc_expose {
|
||||
sc_rpc::DenyUnsafe::Yes
|
||||
} else {
|
||||
sc_rpc::DenyUnsafe::No
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new((
|
||||
maybe_start_server(
|
||||
config.rpc_http,
|
||||
|address| sc_rpc_server::start_http(address, config.rpc_cors.as_ref(), gen_handler()),
|
||||
|address| sc_rpc_server::start_http(
|
||||
address,
|
||||
config.rpc_cors.as_ref(),
|
||||
gen_handler(deny_unsafe(&config.rpc_http, config.unsafe_rpc_expose)),
|
||||
),
|
||||
)?.map(|s| waiting::HttpServer(Some(s))),
|
||||
maybe_start_server(
|
||||
config.rpc_ws,
|
||||
@@ -544,15 +557,15 @@ fn start_rpc_servers<H: FnMut() -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
address,
|
||||
config.rpc_ws_max_connections,
|
||||
config.rpc_cors.as_ref(),
|
||||
gen_handler(),
|
||||
gen_handler(deny_unsafe(&config.rpc_ws, config.unsafe_rpc_expose)),
|
||||
),
|
||||
)?.map(|s| waiting::WsServer(Some(s))).map(Mutex::new),
|
||||
)?.map(|s| waiting::WsServer(Some(s))),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them alive.
|
||||
#[cfg(target_os = "unknown")]
|
||||
fn start_rpc_servers<H: FnMut() -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
fn start_rpc_servers<H: FnMut(sc_rpc::DenyUnsafe) -> sc_rpc_server::RpcHandler<sc_rpc::Metadata>>(
|
||||
_: &Configuration,
|
||||
_: H
|
||||
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error> {
|
||||
|
||||
@@ -184,6 +184,7 @@ fn node_config<G: RuntimeGenesis + 'static, E: ChainSpecExtension + Clone + 'sta
|
||||
chain_spec: Box::new((*spec).clone()),
|
||||
wasm_method: sc_service::config::WasmExecutionMethod::Interpreted,
|
||||
execution_strategies: Default::default(),
|
||||
unsafe_rpc_expose: false,
|
||||
rpc_http: None,
|
||||
rpc_ws: None,
|
||||
rpc_ws_max_connections: None,
|
||||
|
||||
@@ -86,6 +86,7 @@ where
|
||||
rpc_cors: Default::default(),
|
||||
rpc_http: Default::default(),
|
||||
rpc_ws: Default::default(),
|
||||
unsafe_rpc_expose: false,
|
||||
rpc_ws_max_connections: Default::default(),
|
||||
state_cache_child_ratio: Default::default(),
|
||||
state_cache_size: Default::default(),
|
||||
|
||||
Reference in New Issue
Block a user