jsonrpsee integration (#8783)

* Add tokio

* No need to map CallError to CallError

* jsonrpsee proc macros (#9673)

* port error types to `JsonRpseeError`

* migrate chain module to proc macro api

* make it compile with proc macros

* update branch

* update branch

* update to jsonrpsee master

* port system rpc

* port state rpc

* port childstate & offchain

* frame system rpc

* frame transaction payment

* bring back CORS hack to work with polkadot UI

* port babe rpc

* port manual seal rpc

* port frame mmr rpc

* port frame contracts rpc

* port finality grandpa rpc

* port sync state rpc

* resolve a few TODO + no jsonrpc deps

* Update bin/node/rpc-client/src/main.rs

* Update bin/node/rpc-client/src/main.rs

* Update bin/node/rpc-client/src/main.rs

* Update bin/node/rpc-client/src/main.rs

* Port over system_ rpc tests

* Make it compile

* Use prost 0.8

* Use prost 0.8

* Make it compile

* Ignore more failing tests

* Comment out WIP tests

* fix nit in frame system api

* Update lockfile

* No more juggling tokio versions

* No more wait_for_stop ?

* Remove browser-testing

* Arguments must be arrays

* Use same argument names

* Resolve todo: no wait_for_stop for WS server
Add todo: is parse_rpc_result used?
Cleanup imports

* fmt

* log

* One test passes

* update jsonrpsee

* update jsonrpsee

* cleanup rpc-servers crate

* jsonrpsee: add host and origin filtering (#9787)

* add access control in the jsonrpsee servers

* use master

* fix nits

* rpc runtime_version safe

* fix nits

* fix grumbles

* remove unused files

* resolve some todos

* jsonrpsee more cleanup (#9803)

* more cleanup

* resolve TODOs

* fix some unwraps

* remove type hints

* update jsonrpsee

* downgrade zeroize

* pin jsonrpsee rev

* remove unwrap nit

* Comment out more tests that aren't ported

* Comment out more tests

* Fix tests after merge

* Subscription test

* Invalid nonce test

* Pending exts

* WIP removeExtrinsic test

* Test remove_extrinsic

* Make state test: should_return_storage work

* Uncomment/fix the other non-subscription related state tests

* test: author_insertKey

* test: author_rotateKeys

* Get rest of state tests passing

* asyncify a little more

* Add todo to note #msg change

* Crashing test for has_session_keys

* Fix error conversion to avoid stack overflows
Port author_hasSessionKeys test
fmt

* test author_hasKey

* Add two missing tests
Add a check on the return type
Add todos for James's concerns

* RPC tests for state, author and system (#9859)

* Fix test runner

* Impl Default for SubscriptionTaskExecutor

* Keep the minimul amount of code needed to compile tests

* Re-instate `RpcSession` (for now)

* cleanup

* Port over RPC tests

* Add tokio

* No need to map CallError to CallError

* Port over system_ rpc tests

* Make it compile

* Use prost 0.8

* Use prost 0.8

* Make it compile

* Ignore more failing tests

* Comment out WIP tests

* Update lockfile

* No more juggling tokio versions

* No more wait_for_stop ?

* Remove browser-testing

* Arguments must be arrays

* Use same argument names

* Resolve todo: no wait_for_stop for WS server
Add todo: is parse_rpc_result used?
Cleanup imports

* fmt

* log

* One test passes

* Comment out more tests that aren't ported

* Comment out more tests

* Fix tests after merge

* Subscription test

* Invalid nonce test

* Pending exts

* WIP removeExtrinsic test

* Test remove_extrinsic

* Make state test: should_return_storage work

* Uncomment/fix the other non-subscription related state tests

* test: author_insertKey

* test: author_rotateKeys

* Get rest of state tests passing

* asyncify a little more

* Add todo to note #msg change

* Crashing test for has_session_keys

* Fix error conversion to avoid stack overflows
Port author_hasSessionKeys test
fmt

* test author_hasKey

* Add two missing tests
Add a check on the return type
Add todos for James's concerns

* offchain rpc tests

* Address todos

* fmt

Co-authored-by: James Wilson <james@jsdw.me>

* fix drop in state test

* update jsonrpsee

* fix ignored system test

* fix chain tests

* remove some boiler plate

* Port BEEFY RPC (#9883)

* Merge master

* Port beefy RPC (ty @niklas!)

* trivial changes left over from merge

* Remove unused code

* Update jsonrpsee

* fix build

* make tests compile again

* beefy update jsonrpsee

* fix: respect rpc methods policy

* update cargo.lock

* update jsonrpsee

* update jsonrpsee

* downgrade error logs

* update jsonrpsee

* Fix typo

* remove unused file

* Better name

* Port Babe RPC tests

* Put docs back

* Resolve todo

* Port tests for System RPCs

* Resolve todo

* fix build

* Updated jsonrpsee to current master

* fix: port finality grandpa rpc tests

* Move .into() outside of the match

* more review grumbles

* jsonrpsee: add `rpc handlers` back (#10245)

* add back RpcHandlers

* cargo fmt

* fix docs

* fix grumble: remove needless alloc

* resolve TODO

* fmt

* Fix typo

* grumble: Use constants based on BASE_ERROR

* grumble: DRY whitelisted listening addresses
grumble: s/JSONRPC/JSON-RPC/

* cleanup

* grumbles: Making readers aware of the possibility of gaps

* review grumbles

* grumbles

* remove notes from niklasad1

* Update `jsonrpsee`

* fix: jsonrpsee features

* jsonrpsee: fallback to random port in case the specified port failed (#10304)

* jsonrpsee: fallback to random port

* better comment

* Update client/rpc-servers/src/lib.rs

Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com>

* Update client/rpc-servers/src/lib.rs

Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com>

* address grumbles

* cargo fmt

* addrs already slice

Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com>

* Update jsonrpsee to 092081a0a2b8904c6ebd2cd99e16c7bc13ffc3ae

* lockfile

* update jsonrpsee

* fix warning

* Don't fetch jsonrpsee from crates

* make tests compile again

* fix rpc tests

* remove unused deps

* update tokio

* fix rpc tests again

* fix: test runner

`HttpServerBuilder::builder` fails unless it's called within tokio runtime

* cargo fmt

* grumbles: fix subscription aliases

* make clippy happy

* update remaining subscriptions alias

* cleanup

* cleanup

* fix chain subscription: less boiler plate (#10285)

* fix chain subscription: less boiler plate

* fix bad merge

* cargo fmt

* Switch to jsonrpsee 0.5

* fix build

* add missing features

* fix nit: remove needless Box::pin

* Integrate jsonrpsee metrics (#10395)

* draft metrics impl

* Use latest api

* Add missing file

* Http server metrics

* cleanup

* bump jsonrpsee

* Remove `ServerMetrics` and use a single middleware for both connection counting (aka sessions) and call metrics.

* fix build

* remove needless Arc::clone

* Update to jsonrpsee 0.6

* lolz

* fix metrics

* Revert "lolz"

This reverts commit eed6c6a56e78d8e307b4950f4c52a1c3a2322ba1.

* fix: in-memory rpc support subscriptions

* commit Cargo.lock

* Update tests to 0.7

* fix TODOs

* ws server: generate subscriptionIDs as Strings

Some libraries seems to expect the subscription IDs to be Strings, let's not break
this in this PR.

* Increase timeout

* Port over tests

* cleanup

* Using error codes from the spec

* fix clippy

* cargo fmt

* update jsonrpsee

* fix nits

* fix: rpc_query

* enable custom subid gen through spawn_tasks

* remove unsed deps

* unify tokio deps

* Revert "enable custom subid gen through spawn_tasks"

This reverts commit 5c5eb70328fe39d154fdb55c56e637b4548cf470.

* fix bad merge of `test-utils`

* fix more nits

* downgrade wasm-instrument to 0.1.0

* [jsonrpsee]: enable custom RPC subscription ID generatation (#10731)

* enable custom subid gen through spawn_tasks

* fix nits

* Update client/service/src/builder.rs

Co-authored-by: David <dvdplm@gmail.com>

* add Poc; needs jsonrpsee pr

* update jsonrpsee

* add re-exports

* add docs

Co-authored-by: David <dvdplm@gmail.com>

* cargo fmt

* fmt

* port RPC-API dev

* Remove unused file

* fix nit: remove async trait

* fix doc links

* fix merge nit: remove jsonrpc deps

* kill namespace on rpc apis

* companion for jsonrpsee v0.10 (#11158)

* companion for jsonrpsee v0.10

* update versions v0.10.0

* add some fixes

* spelling

* fix spaces

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* send error before subs are closed

* fix unsubscribe method names: chain

* fix tests

* jsonrpc server: print binded local address

* grumbles: kill SubscriptionTaskExecutor

* Update client/sync-state-rpc/src/lib.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update client/rpc/src/chain/chain_full.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update client/rpc/src/chain/chain_full.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* sync-state-rpc: kill anyhow

* no more anyhow

* remove todo

* jsonrpsee:  fix bad params in subscriptions. (#11251)

* update jsonrpsee

* fix error responses

* revert error codes

* dont do weird stuff in drop impl

* rpc servers: remove needless clone

* Remove silly constants

* chore: update jsonrpsee v0.12

* commit Cargo.lock

* deps: downgrade git2

* feat: CLI flag max subscriptions per connection

* metrics: use old logging format

* fix: read WS address from substrate output (#11379)

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: Maciej Hirsz <hello@maciej.codes>
Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
David
2022-05-10 10:52:19 +02:00
committed by GitHub
parent e45d53552d
commit 29c0c6a4a8
93 changed files with 3813 additions and 5094 deletions
+509 -1194
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -42,7 +42,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" }
pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" }
# These dependencies are used for the node template's RPCs
jsonrpc-core = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server"] }
sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" }
sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" }
sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" }
+13 -10
View File
@@ -7,13 +7,15 @@
use std::sync::Arc;
use jsonrpsee::RpcModule;
use node_template_runtime::{opaque::Block, AccountId, Balance, Index};
pub use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::TransactionPool;
use sp_api::ProvideRuntimeApi;
use sp_block_builder::BlockBuilder;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
pub use sc_rpc_api::DenyUnsafe;
/// Full client dependencies.
pub struct FullDeps<C, P> {
/// The client instance to use.
@@ -25,7 +27,9 @@ pub struct FullDeps<C, P> {
}
/// Instantiate all full RPC extensions.
pub fn create_full<C, P>(deps: FullDeps<C, P>) -> jsonrpc_core::IoHandler<sc_rpc::Metadata>
pub fn create_full<C, P>(
deps: FullDeps<C, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
C: ProvideRuntimeApi<Block>,
C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static,
@@ -35,20 +39,19 @@ where
C::Api: BlockBuilder<Block>,
P: TransactionPool + 'static,
{
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi};
use substrate_frame_rpc_system::{FullSystem, SystemApi};
use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc};
use substrate_frame_rpc_system::{SystemApiServer, SystemRpc};
let mut io = jsonrpc_core::IoHandler::default();
let mut module = RpcModule::new(());
let FullDeps { client, pool, deny_unsafe } = deps;
io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe)));
io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client)));
module.merge(SystemRpc::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?;
module.merge(TransactionPaymentRpc::new(client).into_rpc())?;
// Extend this RPC with a custom API by using the following syntax.
// `YourRpcStruct` should have a reference to a client, which is needed
// to call into the runtime.
// `io.extend_with(YourRpcTrait::to_delegate(YourRpcStruct::new(ReferenceToClient, ...)));`
// `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;`
io
Ok(module)
}
@@ -228,8 +228,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
Box::new(move |deny_unsafe, _| {
let deps =
crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe };
Ok(crate::rpc::create_full(deps))
crate::rpc::create_full(deps).map_err(Into::into)
})
};
@@ -239,7 +238,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
keystore: keystore_container.sync_keystore(),
task_manager: &mut task_manager,
transaction_pool: transaction_pool.clone(),
rpc_extensions_builder,
rpc_builder: rpc_extensions_builder,
backend,
system_rpc_tx,
config,
+1
View File
@@ -37,6 +37,7 @@ crate-type = ["cdylib", "rlib"]
clap = { version = "3.1.6", features = ["derive"], optional = true }
codec = { package = "parity-scale-codec", version = "3.0.0" }
serde = { version = "1.0.136", features = ["derive"] }
jsonrpsee = { version = "0.12.0", features = ["server"] }
futures = "0.3.21"
hex-literal = "0.3.4"
log = "0.4.16"
@@ -92,6 +92,10 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
rpc_cors: None,
rpc_methods: Default::default(),
rpc_max_payload: None,
rpc_max_request_size: None,
rpc_max_response_size: None,
rpc_id_provider: None,
rpc_max_subs_per_conn: None,
ws_max_out_buffer_capacity: None,
prometheus_config: None,
telemetry_endpoints: None,
@@ -84,6 +84,10 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
rpc_cors: None,
rpc_methods: Default::default(),
rpc_max_payload: None,
rpc_max_request_size: None,
rpc_max_response_size: None,
rpc_id_provider: None,
rpc_max_subs_per_conn: None,
ws_max_out_buffer_capacity: None,
prometheus_config: None,
telemetry_endpoints: None,
+5 -5
View File
@@ -134,7 +134,7 @@ pub fn new_partial(
impl Fn(
node_rpc::DenyUnsafe,
sc_rpc::SubscriptionTaskExecutor,
) -> Result<node_rpc::IoHandler, sc_service::Error>,
) -> Result<jsonrpsee::RpcModule<()>, sc_service::Error>,
(
sc_consensus_babe::BabeBlockImport<Block, FullClient, FullGrandpaBlockImport>,
grandpa::LinkHalf<Block, FullClient, FullSelectChain>,
@@ -236,7 +236,7 @@ pub fn new_partial(
let justification_stream = grandpa_link.justification_stream();
let shared_authority_set = grandpa_link.shared_authority_set().clone();
let shared_voter_state = grandpa::SharedVoterState::empty();
let rpc_setup = shared_voter_state.clone();
let shared_voter_state2 = shared_voter_state.clone();
let finality_proof_provider = grandpa::FinalityProofProvider::new_for_service(
backend.clone(),
@@ -277,7 +277,7 @@ pub fn new_partial(
node_rpc::create_full(deps, rpc_backend.clone()).map_err(Into::into)
};
(rpc_extensions_builder, rpc_setup)
(rpc_extensions_builder, shared_voter_state2)
};
Ok(sc_service::PartialComponents {
@@ -332,7 +332,7 @@ pub fn new_full_base(
keystore_container,
select_chain,
transaction_pool,
other: (rpc_extensions_builder, import_setup, rpc_setup, mut telemetry),
other: (rpc_builder, import_setup, rpc_setup, mut telemetry),
} = new_partial(&config)?;
let shared_voter_state = rpc_setup;
@@ -386,7 +386,7 @@ pub fn new_full_base(
client: client.clone(),
keystore: keystore_container.sync_keystore(),
network: network.clone(),
rpc_extensions_builder: Box::new(rpc_extensions_builder),
rpc_builder: Box::new(rpc_builder),
transaction_pool: transaction_pool.clone(),
task_manager: &mut task_manager,
system_rpc_tx,
+45 -7
View File
@@ -26,15 +26,14 @@ use nix::{
use node_primitives::Block;
use remote_externalities::rpc_api;
use std::{
io::{BufRead, BufReader, Read},
ops::{Deref, DerefMut},
path::Path,
process::{Child, Command, ExitStatus},
process::{self, Child, Command, ExitStatus},
time::Duration,
};
use tokio::time::timeout;
static LOCALHOST_WS: &str = "ws://127.0.0.1:9944/";
/// Wait for the given `child` the given number of `secs`.
///
/// Returns the `Some(exit status)` or `None` if the process did not finish in the given time.
@@ -63,8 +62,9 @@ pub fn wait_for(child: &mut Child, secs: u64) -> Result<ExitStatus, ()> {
pub async fn wait_n_finalized_blocks(
n: usize,
timeout_secs: u64,
url: &str,
) -> Result<(), tokio::time::error::Elapsed> {
timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, LOCALHOST_WS)).await
timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, url)).await
}
/// Wait for at least n blocks to be finalized from a specified node
@@ -85,12 +85,23 @@ pub async fn wait_n_finalized_blocks_from(n: usize, url: &str) {
/// Run the node for a while (3 blocks)
pub async fn run_node_for_a_while(base_path: &Path, args: &[&str]) {
let mut cmd = Command::new(cargo_bin("substrate"));
let mut cmd = Command::new(cargo_bin("substrate"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(args)
.arg("-d")
.arg(base_path)
.spawn()
.unwrap();
let mut child = KillChildOnDrop(cmd.args(args).arg("-d").arg(base_path).spawn().unwrap());
let stderr = cmd.stderr.take().unwrap();
let mut child = KillChildOnDrop(cmd);
let (ws_url, _) = find_ws_url_from_output(stderr);
// Let it produce some blocks.
let _ = wait_n_finalized_blocks(3, 30).await;
let _ = wait_n_finalized_blocks(3, 30, &ws_url).await;
assert!(child.try_wait().unwrap().is_none(), "the process should still be running");
@@ -134,3 +145,30 @@ impl DerefMut for KillChildOnDrop {
&mut self.0
}
}
/// Read the WS address from the output.
///
/// This is hack to get the actual binded sockaddr because
/// substrate assigns a random port if the specified port was already binded.
pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) {
let mut data = String::new();
let ws_url = BufReader::new(read)
.lines()
.find_map(|line| {
let line =
line.expect("failed to obtain next line from stdout for WS address discovery");
data.push_str(&line);
// does the line contain our port (we expect this specific output from substrate).
let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") {
None => return None,
Some((_, after)) => after.split_once(",").unwrap().0,
};
Some(format!("ws://{}", sock_addr))
})
.expect("We should get a WebSocket address");
(ws_url, data)
}
@@ -25,7 +25,7 @@ use nix::{
},
unistd::Pid,
};
use std::process::{Child, Command};
use std::process::{self, Child, Command};
use tempfile::tempdir;
pub mod common;
@@ -36,6 +36,8 @@ async fn running_the_node_works_and_can_be_interrupted() {
let base_path = tempdir().expect("could not create a temp dir");
let mut cmd = common::KillChildOnDrop(
Command::new(cargo_bin("substrate"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(&["--dev", "-d"])
.arg(base_path.path())
.arg("--db=paritydb")
@@ -44,7 +46,13 @@ async fn running_the_node_works_and_can_be_interrupted() {
.unwrap(),
);
common::wait_n_finalized_blocks(3, 30).await.unwrap();
let stderr = cmd.stderr.take().unwrap();
let (ws_url, _) = common::find_ws_url_from_output(stderr);
common::wait_n_finalized_blocks(3, 30, &ws_url)
.await
.expect("Blocks are produced in time");
assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running");
kill(Pid::from_raw(cmd.id().try_into().unwrap()), signal).unwrap();
assert_eq!(
@@ -69,6 +77,8 @@ async fn running_the_node_works_and_can_be_interrupted() {
async fn running_two_nodes_with_the_same_ws_port_should_work() {
fn start_node() -> Child {
Command::new(cargo_bin("substrate"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(&["--dev", "--tmp", "--ws-port=45789", "--no-hardware-benchmarks"])
.spawn()
.unwrap()
@@ -77,7 +87,10 @@ async fn running_two_nodes_with_the_same_ws_port_should_work() {
let mut first_node = common::KillChildOnDrop(start_node());
let mut second_node = common::KillChildOnDrop(start_node());
let _ = common::wait_n_finalized_blocks(3, 30).await;
let stderr = first_node.stderr.take().unwrap();
let (ws_url, _) = common::find_ws_url_from_output(stderr);
common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap();
assert!(first_node.try_wait().unwrap().is_none(), "The first node should still be running");
assert!(second_node.try_wait().unwrap().is_none(), "The second node should still be running");
@@ -43,8 +43,11 @@ async fn temp_base_path_works() {
.unwrap(),
);
let mut stderr = child.stderr.take().unwrap();
let (ws_url, mut data) = common::find_ws_url_from_output(&mut stderr);
// Let it produce some blocks.
common::wait_n_finalized_blocks(3, 30).await.unwrap();
common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap();
assert!(child.try_wait().unwrap().is_none(), "the process should still be running");
// Stop the process
@@ -52,10 +55,9 @@ async fn temp_base_path_works() {
assert!(common::wait_for(&mut child, 40).map(|x| x.success()).unwrap_or_default());
// Ensure the database has been deleted
let mut stderr = String::new();
child.stderr.as_mut().unwrap().read_to_string(&mut stderr).unwrap();
stderr.read_to_string(&mut data).unwrap();
let re = Regex::new(r"Database: .+ at (\S+)").unwrap();
let db_path = PathBuf::from(re.captures(stderr.as_str()).unwrap().get(1).unwrap().as_str());
let db_path = PathBuf::from(re.captures(data.as_str()).unwrap().get(1).unwrap().as_str());
assert!(!db_path.exists());
}
+1 -1
View File
@@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
jsonrpc-core = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server"] }
node-primitives = { version = "2.0.0", path = "../primitives" }
pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" }
pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" }
+50 -42
View File
@@ -29,18 +29,20 @@
//! be placed here or imported from corresponding FRAME RPC definitions.
#![warn(missing_docs)]
#![warn(unused_crate_dependencies)]
use std::sync::Arc;
use jsonrpsee::RpcModule;
use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Index};
use sc_client_api::AuxStore;
use sc_consensus_babe::{Config, Epoch};
use sc_consensus_babe_rpc::BabeRpcHandler;
use sc_consensus_babe_rpc::BabeRpc;
use sc_consensus_epochs::SharedEpochChanges;
use sc_finality_grandpa::{
FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState,
};
use sc_finality_grandpa_rpc::GrandpaRpcHandler;
use sc_finality_grandpa_rpc::GrandpaRpc;
use sc_rpc::SubscriptionTaskExecutor;
pub use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::TransactionPool;
@@ -93,14 +95,11 @@ pub struct FullDeps<C, P, SC, B> {
pub grandpa: GrandpaDeps<B>,
}
/// A IO handler that uses all Full RPC extensions.
pub type IoHandler = jsonrpc_core::IoHandler<sc_rpc::Metadata>;
/// Instantiate all Full RPC extensions.
pub fn create_full<C, P, SC, B>(
deps: FullDeps<C, P, SC, B>,
backend: Arc<B>,
) -> Result<jsonrpc_core::IoHandler<sc_rpc_api::Metadata>, Box<dyn std::error::Error + Send + Sync>>
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
C: ProvideRuntimeApi<Block>
+ sc_client_api::BlockBackend<Block>
@@ -121,13 +120,17 @@ where
B: sc_client_api::Backend<Block> + Send + Sync + 'static,
B::State: sc_client_api::backend::StateBackend<sp_runtime::traits::HashFor<Block>>,
{
use pallet_contracts_rpc::{Contracts, ContractsApi};
use pallet_mmr_rpc::{Mmr, MmrApi};
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi};
use sc_rpc::dev::{Dev, DevApi};
use substrate_frame_rpc_system::{FullSystem, SystemApi};
use pallet_contracts_rpc::{ContractsApiServer, ContractsRpc};
use pallet_mmr_rpc::{MmrApiServer, MmrRpc};
use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc};
use sc_consensus_babe_rpc::BabeApiServer;
use sc_finality_grandpa_rpc::GrandpaApiServer;
use sc_rpc::dev::{Dev, DevApiServer};
use sc_sync_state_rpc::{SyncStateRpc, SyncStateRpcApiServer};
use substrate_frame_rpc_system::{SystemApiServer, SystemRpc};
use substrate_state_trie_migration_rpc::StateMigrationApiServer;
let mut io = jsonrpc_core::IoHandler::default();
let mut io = RpcModule::new(());
let FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa } = deps;
let BabeDeps { keystore, babe_config, shared_epoch_changes } = babe;
@@ -139,40 +142,45 @@ where
finality_provider,
} = grandpa;
io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe)));
io.merge(SystemRpc::new(client.clone(), pool, deny_unsafe).into_rpc())?;
// Making synchronous calls in light client freezes the browser currently,
// more context: https://github.com/paritytech/substrate/pull/3480
// These RPCs should use an asynchronous caller instead.
io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
io.extend_with(MmrApi::to_delegate(Mmr::new(client.clone())));
io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone())));
io.extend_with(sc_consensus_babe_rpc::BabeApi::to_delegate(BabeRpcHandler::new(
client.clone(),
shared_epoch_changes.clone(),
keystore,
babe_config,
select_chain,
deny_unsafe,
)));
io.extend_with(sc_finality_grandpa_rpc::GrandpaApi::to_delegate(GrandpaRpcHandler::new(
shared_authority_set.clone(),
shared_voter_state,
justification_stream,
subscription_executor,
finality_provider,
)));
io.extend_with(substrate_state_trie_migration_rpc::StateMigrationApi::to_delegate(
substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe),
));
io.extend_with(sc_sync_state_rpc::SyncStateRpcApi::to_delegate(
sc_sync_state_rpc::SyncStateRpcHandler::new(
chain_spec,
io.merge(ContractsRpc::new(client.clone()).into_rpc())?;
io.merge(MmrRpc::new(client.clone()).into_rpc())?;
io.merge(TransactionPaymentRpc::new(client.clone()).into_rpc())?;
io.merge(
BabeRpc::new(
client.clone(),
shared_authority_set,
shared_epoch_changes,
)?,
));
io.extend_with(DevApi::to_delegate(Dev::new(client, deny_unsafe)));
shared_epoch_changes.clone(),
keystore,
babe_config,
select_chain,
deny_unsafe,
)
.into_rpc(),
)?;
io.merge(
GrandpaRpc::new(
subscription_executor,
shared_authority_set.clone(),
shared_voter_state,
justification_stream,
finality_provider,
)
.into_rpc(),
)?;
io.merge(
SyncStateRpc::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)?
.into_rpc(),
)?;
io.merge(
substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe)
.into_rpc(),
)?;
io.merge(Dev::new(client, deny_unsafe).into_rpc())?;
Ok(io)
}
+1 -1
View File
@@ -41,7 +41,7 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" }
serde = "1.0.136"
strum = { version = "0.23", features = ["derive"] }
tempfile = "3.1.0"
tokio = "1.15"
tokio = "1.17.0"
sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" }
sc-network-test = { version = "0.8.0", path = "../network/test" }
sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" }
+2 -4
View File
@@ -11,10 +11,7 @@ homepage = "https://substrate.io"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] }
futures = "0.3.21"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-pubsub = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
log = "0.4"
parking_lot = "0.12.0"
serde = { version = "1.0.136", features = ["derive"] }
@@ -32,3 +29,4 @@ sc-rpc = { version = "4.0.0-dev", features = [
"test-helpers",
], path = "../../rpc" }
substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" }
tokio = { version = "1.17.0", features = ["macros"] }
+124 -186
View File
@@ -23,19 +23,22 @@
use parking_lot::RwLock;
use std::sync::Arc;
use sc_rpc::SubscriptionTaskExecutor;
use sp_runtime::traits::Block as BlockT;
use futures::{task::SpawnError, FutureExt, SinkExt, StreamExt, TryFutureExt};
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use futures::{task::SpawnError, FutureExt, StreamExt};
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::{error::CallError, ErrorObject},
PendingSubscription,
};
use log::warn;
use beefy_gadget::notification::{BeefyBestBlockStream, BeefySignedCommitmentStream};
mod notification;
type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T, jsonrpc_core::Error>>;
#[derive(Debug, thiserror::Error)]
/// Top-level error type for the RPC handler
pub enum Error {
@@ -64,195 +67,149 @@ impl From<Error> for ErrorCode {
}
}
impl From<Error> for jsonrpc_core::Error {
impl From<Error> for JsonRpseeError {
fn from(error: Error) -> Self {
let message = format!("{}", error);
let message = error.to_string();
let code = ErrorCode::from(error);
jsonrpc_core::Error {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
code as i32,
message,
code: jsonrpc_core::ErrorCode::ServerError(code as i64),
data: None,
}
None::<()>,
)))
}
}
/// Provides RPC methods for interacting with BEEFY.
#[rpc]
// Provides RPC methods for interacting with BEEFY.
#[rpc(client, server)]
pub trait BeefyApi<Notification, Hash> {
/// RPC Metadata
type Metadata;
/// Returns the block most recently finalized by BEEFY, alongside side its justification.
#[pubsub(
subscription = "beefy_justifications",
subscribe,
name = "beefy_subscribeJustifications"
#[subscription(
name = "beefy_subscribeJustifications" => "beefy_justifications",
unsubscribe = "beefy_unsubscribeJustifications",
item = Notification,
)]
fn subscribe_justifications(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<Notification>,
);
/// Unsubscribe from receiving notifications about recently finalized blocks.
#[pubsub(
subscription = "beefy_justifications",
unsubscribe,
name = "beefy_unsubscribeJustifications"
)]
fn unsubscribe_justifications(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> jsonrpc_core::Result<bool>;
fn subscribe_justifications(&self);
/// Returns hash of the latest BEEFY finalized block as seen by this client.
///
/// The latest BEEFY block might not be available if the BEEFY gadget is not running
/// in the network or if the client is still initializing or syncing with the network.
/// In such case an error would be returned.
#[rpc(name = "beefy_getFinalizedHead")]
fn latest_finalized(&self) -> FutureResult<Hash>;
#[method(name = "beefy_getFinalizedHead")]
async fn latest_finalized(&self) -> RpcResult<Hash>;
}
/// Implements the BeefyApi RPC trait for interacting with BEEFY.
pub struct BeefyRpcHandler<Block: BlockT> {
signed_commitment_stream: BeefySignedCommitmentStream<Block>,
beefy_best_block: Arc<RwLock<Option<Block::Hash>>>,
manager: SubscriptionManager,
executor: SubscriptionTaskExecutor,
}
impl<Block: BlockT> BeefyRpcHandler<Block> {
impl<Block> BeefyRpcHandler<Block>
where
Block: BlockT,
{
/// Creates a new BeefyRpcHandler instance.
pub fn new<E>(
pub fn new(
signed_commitment_stream: BeefySignedCommitmentStream<Block>,
best_block_stream: BeefyBestBlockStream<Block>,
executor: E,
) -> Result<Self, Error>
where
E: futures::task::Spawn + Send + Sync + 'static,
{
executor: SubscriptionTaskExecutor,
) -> Result<Self, Error> {
let beefy_best_block = Arc::new(RwLock::new(None));
let stream = best_block_stream.subscribe();
let closure_clone = beefy_best_block.clone();
let future = stream.for_each(move |best_beefy| {
let async_clone = closure_clone.clone();
async move {
*async_clone.write() = Some(best_beefy);
}
async move { *async_clone.write() = Some(best_beefy) }
});
executor
.spawn_obj(futures::task::FutureObj::new(Box::pin(future)))
.map_err(|e| {
log::error!("Failed to spawn BEEFY RPC background task; err: {}", e);
e
})?;
let manager = SubscriptionManager::new(Arc::new(executor));
Ok(Self { signed_commitment_stream, beefy_best_block, manager })
executor.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed());
Ok(Self { signed_commitment_stream, beefy_best_block, executor })
}
}
impl<Block> BeefyApi<notification::EncodedSignedCommitment, Block::Hash> for BeefyRpcHandler<Block>
#[async_trait]
impl<Block> BeefyApiServer<notification::EncodedSignedCommitment, Block::Hash>
for BeefyRpcHandler<Block>
where
Block: BlockT,
{
type Metadata = sc_rpc::Metadata;
fn subscribe_justifications(
&self,
_metadata: Self::Metadata,
subscriber: Subscriber<notification::EncodedSignedCommitment>,
) {
fn subscribe_justifications(&self, pending: PendingSubscription) {
let stream = self
.signed_commitment_stream
.subscribe()
.map(|x| Ok::<_, ()>(Ok(notification::EncodedSignedCommitment::new::<Block>(x))));
.map(|sc| notification::EncodedSignedCommitment::new::<Block>(sc));
self.manager.add(subscriber, |sink| {
stream
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
.map(|_| ())
});
let fut = async move {
if let Some(mut sink) = pending.accept() {
sink.pipe_from_stream(stream).await;
}
}
.boxed();
self.executor
.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
fn unsubscribe_justifications(
&self,
_metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> jsonrpc_core::Result<bool> {
Ok(self.manager.cancel(id))
}
fn latest_finalized(&self) -> FutureResult<Block::Hash> {
let result: Result<Block::Hash, jsonrpc_core::Error> = self
.beefy_best_block
async fn latest_finalized(&self) -> RpcResult<Block::Hash> {
self.beefy_best_block
.read()
.as_ref()
.cloned()
.ok_or_else(|| Error::EndpointNotReady.into());
let future = async move { result }.boxed();
future.map_err(jsonrpc_core::Error::from).boxed()
.ok_or(Error::EndpointNotReady)
.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::*;
use jsonrpc_core::{types::Params, Notification, Output};
use beefy_gadget::notification::{BeefySignedCommitment, BeefySignedCommitmentSender};
use beefy_gadget::notification::{
BeefyBestBlockStream, BeefySignedCommitment, BeefySignedCommitmentSender,
};
use beefy_primitives::{known_payload_ids, Payload};
use codec::{Decode, Encode};
use jsonrpsee::{types::EmptyParams, RpcModule};
use sp_runtime::traits::{BlakeTwo256, Hash};
use substrate_test_runtime_client::runtime::Block;
fn setup_io_handler(
) -> (jsonrpc_core::MetaIoHandler<sc_rpc::Metadata>, BeefySignedCommitmentSender<Block>) {
fn setup_io_handler() -> (RpcModule<BeefyRpcHandler<Block>>, BeefySignedCommitmentSender<Block>)
{
let (_, stream) = BeefyBestBlockStream::<Block>::channel();
setup_io_handler_with_best_block_stream(stream)
}
fn setup_io_handler_with_best_block_stream(
best_block_stream: BeefyBestBlockStream<Block>,
) -> (jsonrpc_core::MetaIoHandler<sc_rpc::Metadata>, BeefySignedCommitmentSender<Block>) {
) -> (RpcModule<BeefyRpcHandler<Block>>, BeefySignedCommitmentSender<Block>) {
let (commitment_sender, commitment_stream) =
BeefySignedCommitmentStream::<Block>::channel();
let handler: BeefyRpcHandler<Block> = BeefyRpcHandler::new(
let handler = BeefyRpcHandler::new(
commitment_stream,
best_block_stream,
sc_rpc::testing::TaskExecutor,
sc_rpc::testing::test_executor(),
)
.unwrap();
.expect("Setting up the BEEFY RPC handler works");
let mut io = jsonrpc_core::MetaIoHandler::default();
io.extend_with(BeefyApi::to_delegate(handler));
(io, commitment_sender)
(handler.into_rpc(), commitment_sender)
}
fn setup_session() -> (sc_rpc::Metadata, futures::channel::mpsc::UnboundedReceiver<String>) {
let (tx, rx) = futures::channel::mpsc::unbounded();
let meta = sc_rpc::Metadata::new(tx);
(meta, rx)
}
#[test]
fn uninitialized_rpc_handler() {
let (io, _) = setup_io_handler();
#[tokio::test]
async fn uninitialized_rpc_handler() {
let (rpc, _) = setup_io_handler();
let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#;
let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string();
let (result, _) = rpc.raw_json_request(&request).await.unwrap();
let meta = sc_rpc::Metadata::default();
assert_eq!(Some(response.into()), io.handle_request_sync(request, meta));
assert_eq!(expected_response, result,);
}
#[test]
fn latest_finalized_rpc() {
#[tokio::test]
async fn latest_finalized_rpc() {
let (sender, stream) = BeefyBestBlockStream::<Block>::channel();
let (io, _) = setup_io_handler_with_best_block_stream(stream);
@@ -266,83 +223,78 @@ mod tests {
\"jsonrpc\":\"2.0\",\
\"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\
\"id\":1\
}";
}"
.to_string();
let not_ready = "{\
\"jsonrpc\":\"2.0\",\
\"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\
\"id\":1\
}";
}"
.to_string();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
while std::time::Instant::now() < deadline {
let meta = sc_rpc::Metadata::default();
let response = io.handle_request_sync(request, meta);
// Retry "not ready" responses.
if response != Some(not_ready.into()) {
assert_eq!(response, Some(expected.into()));
let (response, _) = io.raw_json_request(request).await.expect("RPC requests work");
if response != not_ready {
assert_eq!(response, expected);
// Success
return
}
std::thread::sleep(std::time::Duration::from_millis(50));
std::thread::sleep(std::time::Duration::from_millis(50))
}
panic!(
"Deadline reached while waiting for best BEEFY block to update. Perhaps the background task is broken?"
);
}
#[test]
fn subscribe_and_unsubscribe_to_justifications() {
let (io, _) = setup_io_handler();
let (meta, _) = setup_session();
#[tokio::test]
async fn subscribe_and_unsubscribe_to_justifications() {
let (rpc, _) = setup_io_handler();
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap();
// Subscribe call.
let sub = rpc
.subscribe("beefy_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
let sub_id = match resp {
Output::Success(success) => success.result,
_ => panic!(),
};
let ser_id = serde_json::to_string(sub.subscription_id()).unwrap();
// Unsubscribe
let unsub_req = format!(
r#"{{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":[{}],"id":1}}"#,
sub_id
);
assert_eq!(
io.handle_request_sync(&unsub_req, meta.clone()),
Some(r#"{"jsonrpc":"2.0","result":true,"id":1}"#.into()),
"{{\"jsonrpc\":\"2.0\",\"method\":\"beefy_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}",
ser_id
);
let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap();
assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#);
// Unsubscribe again and fail
assert_eq!(
io.handle_request_sync(&unsub_req, meta),
Some(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid subscription id."},"id":1}"#.into()),
);
let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap();
let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#;
assert_eq!(response, expected);
}
#[test]
fn subscribe_and_unsubscribe_with_wrong_id() {
let (io, _) = setup_io_handler();
let (meta, _) = setup_session();
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap();
assert!(matches!(resp, Output::Success(_)));
#[tokio::test]
async fn subscribe_and_unsubscribe_with_wrong_id() {
let (rpc, _) = setup_io_handler();
// Subscribe call.
let _sub = rpc
.subscribe("beefy_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Unsubscribe with wrong ID
assert_eq!(
io.handle_request_sync(
let (response, _) = rpc
.raw_json_request(
r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#,
meta.clone()
),
Some(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid subscription id."},"id":1}"#.into())
);
)
.await
.unwrap();
let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#;
assert_eq!(response, expected);
}
fn create_commitment() -> BeefySignedCommitment<Block> {
@@ -357,18 +309,15 @@ mod tests {
}
}
#[test]
fn subscribe_and_listen_to_one_justification() {
let (io, commitment_sender) = setup_io_handler();
let (meta, receiver) = setup_session();
#[tokio::test]
async fn subscribe_and_listen_to_one_justification() {
let (rpc, commitment_sender) = setup_io_handler();
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap();
let sub_id: String = serde_json::from_value(resp["result"].take()).unwrap();
let mut sub = rpc
.subscribe("beefy_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Notify with commitment
let commitment = create_commitment();
@@ -376,21 +325,10 @@ mod tests {
r.unwrap();
// Inspect what we received
let recv = futures::executor::block_on(receiver.take(1).collect::<Vec<_>>());
let recv: Notification = serde_json::from_str(&recv[0]).unwrap();
let mut json_map = match recv.params {
Params::Map(json_map) => json_map,
_ => panic!(),
};
let recv_sub_id: String = serde_json::from_value(json_map["subscription"].take()).unwrap();
let recv_commitment: sp_core::Bytes =
serde_json::from_value(json_map["result"].take()).unwrap();
let (bytes, recv_sub_id) = sub.next::<sp_core::Bytes>().await.unwrap().unwrap();
let recv_commitment: BeefySignedCommitment<Block> =
Decode::decode(&mut &recv_commitment[..]).unwrap();
assert_eq!(recv.method, "beefy_justifications");
assert_eq!(recv_sub_id, sub_id);
Decode::decode(&mut &bytes[..]).unwrap();
assert_eq!(&recv_sub_id, sub.subscription_id());
assert_eq!(recv_commitment, commitment);
}
}
+14 -4
View File
@@ -100,18 +100,28 @@ pub struct RunCmd {
#[clap(long)]
pub unsafe_ws_external: bool,
/// Set the the maximum RPC payload size for both requests and responses (both http and ws), in
/// megabytes. Default is 15MiB.
/// DEPRECATED, this has no affect anymore. Use `rpc_max_request_size` or
/// `rpc_max_response_size` instead.
#[clap(long)]
pub rpc_max_payload: Option<usize>,
/// Set the the maximum RPC request payload size for both HTTP and WS in megabytes.
/// Default is 15MiB.
#[clap(long)]
pub rpc_max_request_size: Option<usize>,
/// Set the the maximum RPC response payload size for both HTTP and WS in megabytes.
/// Default is 15MiB.
#[clap(long)]
pub rpc_max_response_size: Option<usize>,
/// Expose Prometheus exporter on all interfaces.
///
/// Default is local.
#[clap(long)]
pub prometheus_external: bool,
/// Specify IPC RPC server path
/// DEPRECATED, IPC support has been removed.
#[clap(long, value_name = "PATH")]
pub ipc_path: Option<String>,
@@ -127,7 +137,7 @@ pub struct RunCmd {
#[clap(long, value_name = "COUNT")]
pub ws_max_connections: Option<usize>,
/// Set the the maximum WebSocket output buffer size in MiB. Default is 16.
/// DEPRECATED, this has no affect anymore. Use `rpc_max_response_size` instead.
#[clap(long)]
pub ws_max_out_buffer_capacity: Option<usize>,
+14
View File
@@ -359,6 +359,16 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
Ok(None)
}
/// Get maximum RPC request payload size.
fn rpc_max_request_size(&self) -> Result<Option<usize>> {
Ok(None)
}
/// Get maximum RPC response payload size.
fn rpc_max_response_size(&self) -> Result<Option<usize>> {
Ok(None)
}
/// Get maximum WS output buffer capacity.
fn ws_max_out_buffer_capacity(&self) -> Result<Option<usize>> {
Ok(None)
@@ -526,6 +536,10 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
rpc_ws_max_connections: self.rpc_ws_max_connections()?,
rpc_cors: self.rpc_cors(is_dev)?,
rpc_max_payload: self.rpc_max_payload()?,
rpc_max_request_size: self.rpc_max_request_size()?,
rpc_max_response_size: self.rpc_max_response_size()?,
rpc_id_provider: None,
rpc_max_subs_per_conn: None,
ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?,
prometheus_config: self
.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
@@ -13,10 +13,8 @@ readme = "README.md"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
futures = "0.3.21"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
serde = { version = "1.0.136", features = ["derive"] }
thiserror = "1.0"
sc-consensus-babe = { version = "0.10.0-dev", path = "../" }
@@ -34,6 +32,7 @@ sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" }
[dev-dependencies]
serde_json = "1.0.79"
tempfile = "3.1.0"
tokio = "1.17.0"
sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" }
sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" }
sp-keyring = { version = "6.0.0", path = "../../../../primitives/keyring" }
+91 -107
View File
@@ -18,9 +18,13 @@
//! RPC api for babe.
use futures::{FutureExt, TryFutureExt};
use jsonrpc_core::Error as RpcError;
use jsonrpc_derive::rpc;
use futures::TryFutureExt;
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::{error::CallError, ErrorObject},
};
use sc_consensus_babe::{authorship, Config, Epoch};
use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges};
use sc_rpc_api::DenyUnsafe;
@@ -35,19 +39,17 @@ use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::traits::{Block as BlockT, Header as _};
use std::{collections::HashMap, sync::Arc};
type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T, RpcError>>;
/// Provides rpc methods for interacting with Babe.
#[rpc]
#[rpc(client, server)]
pub trait BabeApi {
/// Returns data about which slots (primary or secondary) can be claimed in the current epoch
/// with the keys in the keystore.
#[rpc(name = "babe_epochAuthorship")]
fn epoch_authorship(&self) -> FutureResult<HashMap<AuthorityId, EpochAuthorship>>;
#[method(name = "babe_epochAuthorship")]
async fn epoch_authorship(&self) -> RpcResult<HashMap<AuthorityId, EpochAuthorship>>;
}
/// Implements the BabeRpc trait for interacting with Babe.
pub struct BabeRpcHandler<B: BlockT, C, SC> {
/// Provides RPC methods for interacting with Babe.
pub struct BabeRpc<B: BlockT, C, SC> {
/// shared reference to the client.
client: Arc<C>,
/// shared reference to EpochChanges
@@ -62,7 +64,7 @@ pub struct BabeRpcHandler<B: BlockT, C, SC> {
deny_unsafe: DenyUnsafe,
}
impl<B: BlockT, C, SC> BabeRpcHandler<B, C, SC> {
impl<B: BlockT, C, SC> BabeRpc<B, C, SC> {
/// Creates a new instance of the BabeRpc handler.
pub fn new(
client: Arc<C>,
@@ -76,7 +78,8 @@ impl<B: BlockT, C, SC> BabeRpcHandler<B, C, SC> {
}
}
impl<B, C, SC> BabeApi for BabeRpcHandler<B, C, SC>
#[async_trait]
impl<B: BlockT, C, SC> BabeApiServer for BabeRpc<B, C, SC>
where
B: BlockT,
C: ProvideRuntimeApi<B>
@@ -86,71 +89,63 @@ where
C::Api: BabeRuntimeApi<B>,
SC: SelectChain<B> + Clone + 'static,
{
fn epoch_authorship(&self) -> FutureResult<HashMap<AuthorityId, EpochAuthorship>> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return async move { Err(err.into()) }.boxed()
}
async fn epoch_authorship(&self) -> RpcResult<HashMap<AuthorityId, EpochAuthorship>> {
self.deny_unsafe.check_if_safe()?;
let header = self.select_chain.best_chain().map_err(Error::Consensus).await?;
let epoch_start = self
.client
.runtime_api()
.current_epoch_start(&BlockId::Hash(header.hash()))
.map_err(|err| Error::StringError(format!("{:?}", err)))?;
let (babe_config, keystore, shared_epoch, client, select_chain) = (
self.babe_config.clone(),
self.keystore.clone(),
self.shared_epoch_changes.clone(),
self.client.clone(),
self.select_chain.clone(),
);
let epoch = epoch_data(
&self.shared_epoch_changes,
&self.client,
&self.babe_config,
*epoch_start,
&self.select_chain,
)
.await?;
let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot());
let mut claims: HashMap<AuthorityId, EpochAuthorship> = HashMap::new();
async move {
let header = select_chain.best_chain().map_err(Error::Consensus).await?;
let epoch_start = client
.runtime_api()
.current_epoch_start(&BlockId::Hash(header.hash()))
.map_err(|err| Error::StringError(err.to_string()))?;
let epoch =
epoch_data(&shared_epoch, &client, &babe_config, *epoch_start, &select_chain)
.await?;
let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot());
let keys = {
epoch
.authorities
.iter()
.enumerate()
.filter_map(|(i, a)| {
if SyncCryptoStore::has_keys(
&*self.keystore,
&[(a.0.to_raw_vec(), AuthorityId::ID)],
) {
Some((a.0.clone(), i))
} else {
None
}
})
.collect::<Vec<_>>()
};
let mut claims: HashMap<AuthorityId, EpochAuthorship> = HashMap::new();
let keys = {
epoch
.authorities
.iter()
.enumerate()
.filter_map(|(i, a)| {
if SyncCryptoStore::has_keys(
&*keystore,
&[(a.0.to_raw_vec(), AuthorityId::ID)],
) {
Some((a.0.clone(), i))
} else {
None
}
})
.collect::<Vec<_>>()
};
for slot in *epoch_start..*epoch_end {
if let Some((claim, key)) =
authorship::claim_slot_using_keys(slot.into(), &epoch, &keystore, &keys)
{
match claim {
PreDigest::Primary { .. } => {
claims.entry(key).or_default().primary.push(slot);
},
PreDigest::SecondaryPlain { .. } => {
claims.entry(key).or_default().secondary.push(slot);
},
PreDigest::SecondaryVRF { .. } => {
claims.entry(key).or_default().secondary_vrf.push(slot);
},
};
}
for slot in *epoch_start..*epoch_end {
if let Some((claim, key)) =
authorship::claim_slot_using_keys(slot.into(), &epoch, &self.keystore, &keys)
{
match claim {
PreDigest::Primary { .. } => {
claims.entry(key).or_default().primary.push(slot);
},
PreDigest::SecondaryPlain { .. } => {
claims.entry(key).or_default().secondary.push(slot);
},
PreDigest::SecondaryVRF { .. } => {
claims.entry(key).or_default().secondary_vrf.push(slot.into());
},
};
}
Ok(claims)
}
.boxed()
Ok(claims)
}
}
@@ -176,13 +171,13 @@ pub enum Error {
StringError(String),
}
impl From<Error> for jsonrpc_core::Error {
impl From<Error> for JsonRpseeError {
fn from(error: Error) -> Self {
jsonrpc_core::Error {
message: format!("{}", error),
code: jsonrpc_core::ErrorCode::ServerError(1234),
data: None,
}
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
1234,
error.to_string(),
None::<()>,
)))
}
}
@@ -226,7 +221,6 @@ mod tests {
TestClientBuilderExt,
};
use jsonrpc_core::IoHandler;
use sc_consensus_babe::{block_import, AuthorityPair, Config};
use std::sync::Arc;
@@ -243,9 +237,9 @@ mod tests {
(keystore, keystore_path)
}
fn test_babe_rpc_handler(
fn test_babe_rpc_module(
deny_unsafe: DenyUnsafe,
) -> BabeRpcHandler<Block, TestClient, sc_consensus::LongestChain<Backend, Block>> {
) -> BabeRpc<Block, TestClient, sc_consensus::LongestChain<Backend, Block>> {
let builder = TestClientBuilder::new();
let (client, longest_chain) = builder.build_with_longest_chain();
let client = Arc::new(client);
@@ -256,40 +250,30 @@ mod tests {
let epoch_changes = link.epoch_changes().clone();
let keystore = create_temp_keystore::<AuthorityPair>(Sr25519Keyring::Alice).0;
BabeRpcHandler::new(
client.clone(),
epoch_changes,
keystore,
config,
longest_chain,
deny_unsafe,
)
BabeRpc::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe)
}
#[test]
fn epoch_authorship_works() {
let handler = test_babe_rpc_handler(DenyUnsafe::No);
let mut io = IoHandler::new();
#[tokio::test]
async fn epoch_authorship_works() {
let babe_rpc = test_babe_rpc_module(DenyUnsafe::No);
let api = babe_rpc.into_rpc();
io.extend_with(BabeApi::to_delegate(handler));
let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#;
let (response, _) = api.raw_json_request(request).await.unwrap();
let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#;
assert_eq!(Some(response.into()), io.handle_request_sync(request));
assert_eq!(&response, expected);
}
#[test]
fn epoch_authorship_is_unsafe() {
let handler = test_babe_rpc_handler(DenyUnsafe::Yes);
let mut io = IoHandler::new();
#[tokio::test]
async fn epoch_authorship_is_unsafe() {
let babe_rpc = test_babe_rpc_module(DenyUnsafe::Yes);
let api = babe_rpc.into_rpc();
io.extend_with(BabeApi::to_delegate(handler));
let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#;
let (response, _) = api.raw_json_request(request).await.unwrap();
let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#;
let response = io.handle_request_sync(request).unwrap();
let mut response: serde_json::Value = serde_json::from_str(&response).unwrap();
let error: RpcError = serde_json::from_value(response["error"].take()).unwrap();
assert_eq!(error, sc_rpc_api::UnsafeRpcError.into())
assert_eq!(&response, expected);
}
}
@@ -13,13 +13,11 @@ readme = "README.md"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
assert_matches = "1.3.0"
async-trait = "0.1.50"
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.21"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
log = "0.4.16"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
@@ -20,6 +20,10 @@
//! This is suitable for a testing environment.
use futures::channel::{mpsc::SendError, oneshot};
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
use sc_consensus::ImportResult;
use sp_blockchain::Error as BlockchainError;
use sp_consensus::Error as ConsensusError;
@@ -27,14 +31,14 @@ use sp_inherents::Error as InherentsError;
/// Error code for rpc
mod codes {
pub const SERVER_SHUTTING_DOWN: i64 = 10_000;
pub const BLOCK_IMPORT_FAILED: i64 = 11_000;
pub const EMPTY_TRANSACTION_POOL: i64 = 12_000;
pub const BLOCK_NOT_FOUND: i64 = 13_000;
pub const CONSENSUS_ERROR: i64 = 14_000;
pub const INHERENTS_ERROR: i64 = 15_000;
pub const BLOCKCHAIN_ERROR: i64 = 16_000;
pub const UNKNOWN_ERROR: i64 = 20_000;
pub const SERVER_SHUTTING_DOWN: i32 = 10_000;
pub const BLOCK_IMPORT_FAILED: i32 = 11_000;
pub const EMPTY_TRANSACTION_POOL: i32 = 12_000;
pub const BLOCK_NOT_FOUND: i32 = 13_000;
pub const CONSENSUS_ERROR: i32 = 14_000;
pub const INHERENTS_ERROR: i32 = 15_000;
pub const BLOCKCHAIN_ERROR: i32 = 16_000;
pub const UNKNOWN_ERROR: i32 = 20_000;
}
/// errors encountered by background block authorship task
@@ -71,7 +75,7 @@ pub enum Error {
SendError(#[from] SendError),
/// Some other error.
#[error("Other error: {0}")]
Other(#[from] Box<dyn std::error::Error + Send>),
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl From<ImportResult> for Error {
@@ -87,7 +91,7 @@ impl From<String> for Error {
}
impl Error {
fn to_code(&self) -> i64 {
fn to_code(&self) -> i32 {
use Error::*;
match self {
BlockImportError(_) => codes::BLOCK_IMPORT_FAILED,
@@ -102,12 +106,8 @@ impl Error {
}
}
impl From<Error> for jsonrpc_core::Error {
fn from(error: Error) -> Self {
jsonrpc_core::Error {
code: jsonrpc_core::ErrorCode::ServerError(error.to_code()),
message: format!("{}", error),
data: None,
}
impl From<Error> for JsonRpseeError {
fn from(err: Error) -> Self {
CallError::Custom(ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)).into()
}
}
@@ -18,21 +18,21 @@
//! RPC interface for the `ManualSeal` Engine.
pub use self::gen_client::Client as ManualSealClient;
use crate::error::Error;
use futures::{
channel::{mpsc, oneshot},
FutureExt, SinkExt, TryFutureExt,
SinkExt,
};
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
};
use jsonrpc_core::Error;
use jsonrpc_derive::rpc;
use sc_consensus::ImportedAux;
use serde::{Deserialize, Serialize};
use sp_runtime::EncodedJustification;
/// Future's type for jsonrpc
type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T, Error>>;
/// sender passed to the authorship task to report errors or successes.
pub type Sender<T> = Option<oneshot::Sender<std::result::Result<T, crate::Error>>>;
/// Sender passed to the authorship task to report errors or successes.
pub type Sender<T> = Option<oneshot::Sender<std::result::Result<T, Error>>>;
/// Message sent to the background authorship task, usually by RPC.
pub enum EngineCommand<Hash> {
@@ -65,27 +65,27 @@ pub enum EngineCommand<Hash> {
}
/// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc.
#[rpc]
#[rpc(client, server)]
pub trait ManualSealApi<Hash> {
/// Instructs the manual-seal authorship task to create a new block
#[rpc(name = "engine_createBlock")]
fn create_block(
#[method(name = "engine_createBlock")]
async fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>,
) -> FutureResult<CreatedBlock<Hash>>;
) -> RpcResult<CreatedBlock<Hash>>;
/// Instructs the manual-seal authorship task to finalize a block
#[rpc(name = "engine_finalizeBlock")]
fn finalize_block(
#[method(name = "engine_finalizeBlock")]
async fn finalize_block(
&self,
hash: Hash,
justification: Option<EncodedJustification>,
) -> FutureResult<bool>;
) -> RpcResult<bool>;
}
/// A struct that implements the [`ManualSealApi`].
/// A struct that implements the [`ManualSealApiServer`].
pub struct ManualSeal<Hash> {
import_block_channel: mpsc::Sender<EngineCommand<Hash>>,
}
@@ -106,44 +106,43 @@ impl<Hash> ManualSeal<Hash> {
}
}
impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
fn create_block(
#[async_trait]
impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
async fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>,
) -> FutureResult<CreatedBlock<Hash>> {
) -> RpcResult<CreatedBlock<Hash>> {
let mut sink = self.import_block_channel.clone();
async move {
let (sender, receiver) = oneshot::channel();
let command = EngineCommand::SealNewBlock {
create_empty,
finalize,
parent_hash,
sender: Some(sender),
};
sink.send(command).await?;
receiver.await?
let (sender, receiver) = oneshot::channel();
// NOTE: this sends a Result over the channel.
let command = EngineCommand::SealNewBlock {
create_empty,
finalize,
parent_hash,
sender: Some(sender),
};
sink.send(command).await?;
match receiver.await {
Ok(Ok(rx)) => Ok(rx),
Ok(Err(e)) => Err(e.into()),
Err(e) => Err(JsonRpseeError::to_call_error(e)),
}
.map_err(Error::from)
.boxed()
}
fn finalize_block(
async fn finalize_block(
&self,
hash: Hash,
justification: Option<EncodedJustification>,
) -> FutureResult<bool> {
) -> RpcResult<bool> {
let mut sink = self.import_block_channel.clone();
async move {
let (sender, receiver) = oneshot::channel();
sink.send(EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification })
.await?;
receiver.await?.map(|_| true)
}
.map_err(Error::from)
.boxed()
let (sender, receiver) = oneshot::channel();
let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification };
sink.send(command).await?;
receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e))
}
}
@@ -12,10 +12,7 @@ homepage = "https://substrate.io"
[dependencies]
finality-grandpa = { version = "0.15.0", features = ["derive-codec"] }
futures = "0.3.16"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-pubsub = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
log = "0.4.8"
parity-scale-codec = { version = "3.0.0", features = ["derive"] }
serde = { version = "1.0.105", features = ["derive"] }
@@ -37,3 +34,4 @@ sp-core = { version = "6.0.0", path = "../../../primitives/core" }
sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" }
sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" }
substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" }
tokio = { version = "1.17.0", features = ["macros"] }
@@ -16,6 +16,11 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
#[derive(Debug, thiserror::Error)]
/// Top-level error type for the RPC handler
pub enum Error {
@@ -56,15 +61,15 @@ impl From<Error> for ErrorCode {
}
}
impl From<Error> for jsonrpc_core::Error {
impl From<Error> for JsonRpseeError {
fn from(error: Error) -> Self {
let message = format!("{}", error);
let message = error.to_string();
let code = ErrorCode::from(error);
jsonrpc_core::Error {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
code as i32,
message,
code: jsonrpc_core::ErrorCode::ServerError(code as i64),
data: None,
}
None::<()>,
)))
}
}
+143 -202
View File
@@ -19,167 +19,137 @@
//! RPC API for GRANDPA.
#![warn(missing_docs)]
use futures::{task::Spawn, FutureExt, SinkExt, StreamExt, TryFutureExt};
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use futures::{FutureExt, StreamExt};
use log::warn;
use std::sync::Arc;
use jsonrpsee::{
core::{async_trait, RpcResult},
proc_macros::rpc,
PendingSubscription,
};
mod error;
mod finality;
mod notification;
mod report;
use sc_finality_grandpa::GrandpaJustificationStream;
use sc_rpc::SubscriptionTaskExecutor;
use sp_runtime::traits::{Block as BlockT, NumberFor};
use finality::{EncodedFinalityProof, RpcFinalityProofProvider};
use notification::JustificationNotification;
use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates};
type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T, jsonrpc_core::Error>>;
/// Provides RPC methods for interacting with GRANDPA.
#[rpc]
#[rpc(client, server)]
pub trait GrandpaApi<Notification, Hash, Number> {
/// RPC Metadata
type Metadata;
/// Returns the state of the current best round state as well as the
/// ongoing background rounds.
#[rpc(name = "grandpa_roundState")]
fn round_state(&self) -> FutureResult<ReportedRoundStates>;
#[method(name = "grandpa_roundState")]
async fn round_state(&self) -> RpcResult<ReportedRoundStates>;
/// Returns the block most recently finalized by Grandpa, alongside
/// side its justification.
#[pubsub(
subscription = "grandpa_justifications",
subscribe,
name = "grandpa_subscribeJustifications"
#[subscription(
name = "grandpa_subscribeJustifications" => "grandpa_justifications",
unsubscribe = "grandpa_unsubscribeJustifications",
item = Notification
)]
fn subscribe_justifications(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<Notification>,
);
/// Unsubscribe from receiving notifications about recently finalized blocks.
#[pubsub(
subscription = "grandpa_justifications",
unsubscribe,
name = "grandpa_unsubscribeJustifications"
)]
fn unsubscribe_justifications(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> jsonrpc_core::Result<bool>;
fn subscribe_justifications(&self);
/// Prove finality for the given block number by returning the Justification for the last block
/// in the set and all the intermediary headers to link them together.
#[rpc(name = "grandpa_proveFinality")]
fn prove_finality(&self, block: Number) -> FutureResult<Option<EncodedFinalityProof>>;
#[method(name = "grandpa_proveFinality")]
async fn prove_finality(&self, block: Number) -> RpcResult<Option<EncodedFinalityProof>>;
}
/// Implements the GrandpaApi RPC trait for interacting with GRANDPA.
pub struct GrandpaRpcHandler<AuthoritySet, VoterState, Block: BlockT, ProofProvider> {
/// Provides RPC methods for interacting with GRANDPA.
pub struct GrandpaRpc<AuthoritySet, VoterState, Block: BlockT, ProofProvider> {
executor: SubscriptionTaskExecutor,
authority_set: AuthoritySet,
voter_state: VoterState,
justification_stream: GrandpaJustificationStream<Block>,
manager: SubscriptionManager,
finality_proof_provider: Arc<ProofProvider>,
}
impl<AuthoritySet, VoterState, Block: BlockT, ProofProvider>
GrandpaRpcHandler<AuthoritySet, VoterState, Block, ProofProvider>
GrandpaRpc<AuthoritySet, VoterState, Block, ProofProvider>
{
/// Creates a new GrandpaRpcHandler instance.
pub fn new<E>(
/// Prepare a new [`GrandpaRpc`]
pub fn new(
executor: SubscriptionTaskExecutor,
authority_set: AuthoritySet,
voter_state: VoterState,
justification_stream: GrandpaJustificationStream<Block>,
executor: E,
finality_proof_provider: Arc<ProofProvider>,
) -> Self
where
E: Spawn + Sync + Send + 'static,
{
let manager = SubscriptionManager::new(Arc::new(executor));
Self { authority_set, voter_state, justification_stream, manager, finality_proof_provider }
) -> Self {
Self { executor, authority_set, voter_state, justification_stream, finality_proof_provider }
}
}
#[async_trait]
impl<AuthoritySet, VoterState, Block, ProofProvider>
GrandpaApi<JustificationNotification, Block::Hash, NumberFor<Block>>
for GrandpaRpcHandler<AuthoritySet, VoterState, Block, ProofProvider>
GrandpaApiServer<JustificationNotification, Block::Hash, NumberFor<Block>>
for GrandpaRpc<AuthoritySet, VoterState, Block, ProofProvider>
where
VoterState: ReportVoterState + Send + Sync + 'static,
AuthoritySet: ReportAuthoritySet + Send + Sync + 'static,
Block: BlockT,
ProofProvider: RpcFinalityProofProvider<Block> + Send + Sync + 'static,
{
type Metadata = sc_rpc::Metadata;
fn round_state(&self) -> FutureResult<ReportedRoundStates> {
let round_states = ReportedRoundStates::from(&self.authority_set, &self.voter_state);
let future = async move { round_states }.boxed();
future.map_err(jsonrpc_core::Error::from).boxed()
async fn round_state(&self) -> RpcResult<ReportedRoundStates> {
ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into)
}
fn subscribe_justifications(
&self,
_metadata: Self::Metadata,
subscriber: Subscriber<JustificationNotification>,
) {
let stream = self
.justification_stream
.subscribe()
.map(|x| Ok(Ok::<_, jsonrpc_core::Error>(JustificationNotification::from(x))));
fn subscribe_justifications(&self, pending: PendingSubscription) {
let stream = self.justification_stream.subscribe().map(
|x: sc_finality_grandpa::GrandpaJustification<Block>| {
JustificationNotification::from(x)
},
);
self.manager.add(subscriber, |sink| {
stream
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
.map(|_| ())
});
let fut = async move {
if let Some(mut sink) = pending.accept() {
sink.pipe_from_stream(stream).await;
}
}
.boxed();
self.executor
.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
fn unsubscribe_justifications(
&self,
_metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> jsonrpc_core::Result<bool> {
Ok(self.manager.cancel(id))
}
fn prove_finality(
async fn prove_finality(
&self,
block: NumberFor<Block>,
) -> FutureResult<Option<EncodedFinalityProof>> {
let result = self.finality_proof_provider.rpc_prove_finality(block);
let future = async move { result }.boxed();
future
) -> RpcResult<Option<EncodedFinalityProof>> {
self.finality_proof_provider
.rpc_prove_finality(block)
.map_err(|e| {
warn!("Error proving finality: {}", e);
error::Error::ProveFinalityFailed(e)
})
.map_err(jsonrpc_core::Error::from)
.boxed()
.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::*;
use jsonrpc_core::{types::Params, Notification, Output};
use std::{collections::HashSet, sync::Arc};
use std::{collections::HashSet, convert::TryInto, sync::Arc};
use jsonrpsee::{
types::{EmptyParams, SubscriptionId},
RpcModule,
};
use parity_scale_codec::{Decode, Encode};
use sc_block_builder::{BlockBuilder, RecordProof};
use sc_finality_grandpa::{
report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender,
};
use sp_blockchain::HeaderBackend;
use sp_core::crypto::ByteArray;
use sp_core::{crypto::ByteArray, testing::TaskExecutor};
use sp_keyring::Ed25519Keyring;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use substrate_test_runtime_client::{
@@ -274,7 +244,10 @@ mod tests {
fn setup_io_handler<VoterState>(
voter_state: VoterState,
) -> (jsonrpc_core::MetaIoHandler<sc_rpc::Metadata>, GrandpaJustificationSender<Block>)
) -> (
RpcModule<GrandpaRpc<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
GrandpaJustificationSender<Block>,
)
where
VoterState: ReportVoterState + Send + Sync + 'static,
{
@@ -284,120 +257,107 @@ mod tests {
fn setup_io_handler_with_finality_proofs<VoterState>(
voter_state: VoterState,
finality_proof: Option<FinalityProof<Header>>,
) -> (jsonrpc_core::MetaIoHandler<sc_rpc::Metadata>, GrandpaJustificationSender<Block>)
) -> (
RpcModule<GrandpaRpc<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
GrandpaJustificationSender<Block>,
)
where
VoterState: ReportVoterState + Send + Sync + 'static,
{
let (justification_sender, justification_stream) = GrandpaJustificationStream::channel();
let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof });
let executor = Arc::new(TaskExecutor::default());
let handler = GrandpaRpcHandler::new(
let rpc = GrandpaRpc::new(
executor,
TestAuthoritySet,
voter_state,
justification_stream,
sc_rpc::testing::TaskExecutor,
finality_proof_provider,
);
)
.into_rpc();
let mut io = jsonrpc_core::MetaIoHandler::default();
io.extend_with(GrandpaApi::to_delegate(handler));
(io, justification_sender)
(rpc, justification_sender)
}
#[test]
fn uninitialized_rpc_handler() {
let (io, _) = setup_io_handler(EmptyVoterState);
#[tokio::test]
async fn uninitialized_rpc_handler() {
let (rpc, _) = setup_io_handler(EmptyVoterState);
let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string();
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
let (result, _) = rpc.raw_json_request(&request).await.unwrap();
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":1}"#;
let meta = sc_rpc::Metadata::default();
assert_eq!(Some(response.into()), io.handle_request_sync(request, meta));
assert_eq!(expected_response, result,);
}
#[test]
fn working_rpc_handler() {
let (io, _) = setup_io_handler(TestVoterState);
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#;
let response = "{\"jsonrpc\":\"2.0\",\"result\":{\
\"background\":[{\
\"precommits\":{\"currentWeight\":100,\"missing\":[]},\
\"prevotes\":{\"currentWeight\":100,\"missing\":[]},\
\"round\":1,\"thresholdWeight\":67,\"totalWeight\":100\
}],\
#[tokio::test]
async fn working_rpc_handler() {
let (rpc, _) = setup_io_handler(TestVoterState);
let expected_response = "{\"jsonrpc\":\"2.0\",\"result\":{\
\"setId\":1,\
\"best\":{\
\"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
\"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\
\"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
\"round\":2,\"thresholdWeight\":67,\"totalWeight\":100\
\"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]}\
},\
\"setId\":1\
},\"id\":1}";
\"background\":[{\
\"round\":1,\"totalWeight\":100,\"thresholdWeight\":67,\
\"prevotes\":{\"currentWeight\":100,\"missing\":[]},\
\"precommits\":{\"currentWeight\":100,\"missing\":[]}\
}]\
},\"id\":0}".to_string();
let meta = sc_rpc::Metadata::default();
assert_eq!(io.handle_request_sync(request, meta), Some(response.into()));
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
let (result, _) = rpc.raw_json_request(&request).await.unwrap();
assert_eq!(expected_response, result);
}
fn setup_session() -> (sc_rpc::Metadata, futures::channel::mpsc::UnboundedReceiver<String>) {
let (tx, rx) = futures::channel::mpsc::unbounded();
let meta = sc_rpc::Metadata::new(tx);
(meta, rx)
}
#[tokio::test]
async fn subscribe_and_unsubscribe_to_justifications() {
let (rpc, _) = setup_io_handler(TestVoterState);
// Subscribe call.
let sub = rpc
.subscribe("grandpa_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
#[test]
fn subscribe_and_unsubscribe_to_justifications() {
let (io, _) = setup_io_handler(TestVoterState);
let (meta, _) = setup_session();
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap();
let sub_id = match resp {
Output::Success(success) => success.result,
_ => panic!(),
};
let ser_id = serde_json::to_string(sub.subscription_id()).unwrap();
// Unsubscribe
let unsub_req = format!(
"{{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}",
sub_id
);
assert_eq!(
io.handle_request_sync(&unsub_req, meta.clone()),
Some(r#"{"jsonrpc":"2.0","result":true,"id":1}"#.into()),
ser_id
);
let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap();
assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#);
// Unsubscribe again and fail
assert_eq!(
io.handle_request_sync(&unsub_req, meta),
Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into()),
);
let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap();
let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#;
assert_eq!(response, expected);
}
#[test]
fn subscribe_and_unsubscribe_with_wrong_id() {
let (io, _) = setup_io_handler(TestVoterState);
let (meta, _) = setup_session();
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap();
assert!(matches!(resp, Output::Success(_)));
#[tokio::test]
async fn subscribe_and_unsubscribe_with_wrong_id() {
let (rpc, _) = setup_io_handler(TestVoterState);
// Subscribe call.
let _sub = rpc
.subscribe("grandpa_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Unsubscribe with wrong ID
assert_eq!(
io.handle_request_sync(
let (response, _) = rpc
.raw_json_request(
r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#,
meta.clone()
),
Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into())
);
)
.await
.unwrap();
let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#;
assert_eq!(response, expected);
}
fn create_justification() -> GrandpaJustification<Block> {
@@ -454,60 +414,41 @@ mod tests {
justification
}
#[test]
fn subscribe_and_listen_to_one_justification() {
let (io, justification_sender) = setup_io_handler(TestVoterState);
let (meta, receiver) = setup_session();
#[tokio::test]
async fn subscribe_and_listen_to_one_justification() {
let (rpc, justification_sender) = setup_io_handler(TestVoterState);
// Subscribe
let sub_request =
r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#;
let resp = io.handle_request_sync(sub_request, meta.clone());
let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap();
let sub_id: String = serde_json::from_value(resp["result"].take()).unwrap();
let mut sub = rpc
.subscribe("grandpa_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Notify with a header and justification
let justification = create_justification();
justification_sender.notify(|| Ok::<_, ()>(justification.clone())).unwrap();
// Inspect what we received
let recv = futures::executor::block_on(receiver.take(1).collect::<Vec<_>>());
let recv: Notification = serde_json::from_str(&recv[0]).unwrap();
let mut json_map = match recv.params {
Params::Map(json_map) => json_map,
_ => panic!(),
};
let recv_sub_id: String = serde_json::from_value(json_map["subscription"].take()).unwrap();
let recv_justification: sp_core::Bytes =
serde_json::from_value(json_map["result"].take()).unwrap();
let (recv_justification, recv_sub_id): (sp_core::Bytes, SubscriptionId) =
sub.next().await.unwrap().unwrap();
let recv_justification: GrandpaJustification<Block> =
Decode::decode(&mut &recv_justification[..]).unwrap();
assert_eq!(recv.method, "grandpa_justifications");
assert_eq!(recv_sub_id, sub_id);
assert_eq!(&recv_sub_id, sub.subscription_id());
assert_eq!(recv_justification, justification);
}
#[test]
fn prove_finality_with_test_finality_proof_provider() {
#[tokio::test]
async fn prove_finality_with_test_finality_proof_provider() {
let finality_proof = FinalityProof {
block: header(42).hash(),
justification: create_justification().encode(),
unknown_headers: vec![header(2)],
};
let (io, _) =
let (rpc, _) =
setup_io_handler_with_finality_proofs(TestVoterState, Some(finality_proof.clone()));
let request =
"{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_proveFinality\",\"params\":[42],\"id\":1}";
let meta = sc_rpc::Metadata::default();
let resp = io.handle_request_sync(request, meta);
let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap();
let result: sp_core::Bytes = serde_json::from_value(resp["result"].take()).unwrap();
let finality_proof_rpc: FinalityProof<Header> = Decode::decode(&mut &result[..]).unwrap();
let bytes: sp_core::Bytes = rpc.call("grandpa_proveFinality", [42]).await.unwrap();
let finality_proof_rpc: FinalityProof<Header> = Decode::decode(&mut &bytes[..]).unwrap();
assert_eq!(finality_proof_rpc, finality_proof);
}
}
+1 -4
View File
@@ -15,10 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.21"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-pubsub = "18.0.0"
log = "0.4.16"
parking_lot = "0.12.0"
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
@@ -32,3 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" }
sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" }
sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" }
sp-version = { version = "5.0.0", path = "../../primitives/version" }
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
+104 -76
View File
@@ -18,28 +18,27 @@
//! Authoring RPC module errors.
use crate::errors;
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
use sp_runtime::transaction_validity::InvalidTransaction;
/// Author RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Author RPC future Result type.
pub type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T>>;
/// Author RPC errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Client error.
#[error("Client error: {}", .0)]
Client(Box<dyn std::error::Error + Send>),
Client(Box<dyn std::error::Error + Send + Sync>),
/// Transaction pool error,
#[error("Transaction pool error: {}", .0)]
Pool(#[from] sc_transaction_pool_api::error::Error),
/// Verification error
#[error("Extrinsic verification error: {}", .0)]
Verification(Box<dyn std::error::Error + Send>),
Verification(Box<dyn std::error::Error + Send + Sync>),
/// Incorrect extrinsic format.
#[error("Invalid extrinsic format: {}", .0)]
BadFormat(#[from] codec::Error),
@@ -58,98 +57,127 @@ pub enum Error {
}
/// Base code for all authorship errors.
const BASE_ERROR: i64 = 1000;
const BASE_ERROR: i32 = 1000;
/// Extrinsic has an invalid format.
const BAD_FORMAT: i64 = BASE_ERROR + 1;
const BAD_FORMAT: i32 = BASE_ERROR + 1;
/// Error during transaction verification in runtime.
const VERIFICATION_ERROR: i64 = BASE_ERROR + 2;
const VERIFICATION_ERROR: i32 = BASE_ERROR + 2;
/// Pool rejected the transaction as invalid
const POOL_INVALID_TX: i64 = BASE_ERROR + 10;
const POOL_INVALID_TX: i32 = BASE_ERROR + 10;
/// Cannot determine transaction validity.
const POOL_UNKNOWN_VALIDITY: i64 = POOL_INVALID_TX + 1;
const POOL_UNKNOWN_VALIDITY: i32 = POOL_INVALID_TX + 1;
/// The transaction is temporarily banned.
const POOL_TEMPORARILY_BANNED: i64 = POOL_INVALID_TX + 2;
const POOL_TEMPORARILY_BANNED: i32 = POOL_INVALID_TX + 2;
/// The transaction is already in the pool
const POOL_ALREADY_IMPORTED: i64 = POOL_INVALID_TX + 3;
const POOL_ALREADY_IMPORTED: i32 = POOL_INVALID_TX + 3;
/// Transaction has too low priority to replace existing one in the pool.
const POOL_TOO_LOW_PRIORITY: i64 = POOL_INVALID_TX + 4;
const POOL_TOO_LOW_PRIORITY: i32 = POOL_INVALID_TX + 4;
/// Including this transaction would cause a dependency cycle.
const POOL_CYCLE_DETECTED: i64 = POOL_INVALID_TX + 5;
const POOL_CYCLE_DETECTED: i32 = POOL_INVALID_TX + 5;
/// The transaction was not included to the pool because of the limits.
const POOL_IMMEDIATELY_DROPPED: i64 = POOL_INVALID_TX + 6;
const POOL_IMMEDIATELY_DROPPED: i32 = POOL_INVALID_TX + 6;
/// The transaction was not included to the pool since it is unactionable,
/// it is not propagable and the local node does not author blocks.
const POOL_UNACTIONABLE: i64 = POOL_INVALID_TX + 8;
const POOL_UNACTIONABLE: i32 = POOL_INVALID_TX + 8;
/// Transaction does not provide any tags, so the pool can't identify it.
const POOL_NO_TAGS: i32 = POOL_INVALID_TX + 9;
/// Invalid block ID.
const POOL_INVALID_BLOCK_ID: i32 = POOL_INVALID_TX + 10;
/// The pool is not accepting future transactions.
const POOL_FUTURE_TX: i32 = POOL_INVALID_TX + 11;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
use sc_transaction_pool_api::error::Error as PoolError;
match e {
Error::BadFormat(e) => rpc::Error {
code: rpc::ErrorCode::ServerError(BAD_FORMAT),
message: format!("Extrinsic has invalid format: {}", e),
data: None,
},
Error::Verification(e) => rpc::Error {
code: rpc::ErrorCode::ServerError(VERIFICATION_ERROR),
message: format!("Verification Error: {}", e),
data: Some(e.to_string().into()),
},
Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_INVALID_TX),
message: "Invalid Transaction".into(),
data: Some(format!("Custom error: {}", e).into()),
Error::BadFormat(e) => CallError::Custom(ErrorObject::owned(
BAD_FORMAT,
format!("Extrinsic has invalid format: {}", e),
None::<()>,
)),
Error::Verification(e) => CallError::Custom(ErrorObject::owned(
VERIFICATION_ERROR,
format!("Verification Error: {}", e),
Some(format!("{:?}", e)),
)),
Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => {
CallError::Custom(ErrorObject::owned(
POOL_INVALID_TX,
"Invalid Transaction",
Some(format!("Custom error: {}", e)),
))
},
Error::Pool(PoolError::InvalidTransaction(e)) => {
let msg: &str = e.into();
rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_INVALID_TX),
message: "Invalid Transaction".into(),
data: Some(msg.into()),
}
CallError::Custom(ErrorObject::owned(
POOL_INVALID_TX,
"Invalid Transaction",
Some(msg),
))
},
Error::Pool(PoolError::UnknownTransaction(e)) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_UNKNOWN_VALIDITY),
message: "Unknown Transaction Validity".into(),
data: serde_json::to_value(e).ok(),
Error::Pool(PoolError::UnknownTransaction(e)) => {
CallError::Custom(ErrorObject::owned(
POOL_UNKNOWN_VALIDITY,
"Unknown Transaction Validity",
Some(format!("{:?}", e)),
))
},
Error::Pool(PoolError::TemporarilyBanned) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_TEMPORARILY_BANNED),
message: "Transaction is temporarily banned".into(),
data: None,
},
Error::Pool(PoolError::AlreadyImported(hash)) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_ALREADY_IMPORTED),
message: "Transaction Already Imported".into(),
data: Some(format!("{:?}", hash).into()),
},
Error::Pool(PoolError::TooLowPriority { old, new }) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_TOO_LOW_PRIORITY),
message: format!("Priority is too low: ({} vs {})", old, new),
data: Some("The transaction has too low priority to replace another transaction already in the pool.".into()),
},
Error::Pool(PoolError::CycleDetected) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_CYCLE_DETECTED),
message: "Cycle Detected".into(),
data: None,
},
Error::Pool(PoolError::ImmediatelyDropped) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_IMMEDIATELY_DROPPED),
message: "Immediately Dropped".into(),
data: Some("The transaction couldn't enter the pool because of the limit".into()),
},
Error::Pool(PoolError::Unactionable) => rpc::Error {
code: rpc::ErrorCode::ServerError(POOL_UNACTIONABLE),
message: "Unactionable".into(),
data: Some(
"The transaction is unactionable since it is not propagable and \
the local node does not author blocks".into(),
),
Error::Pool(PoolError::TemporarilyBanned) =>
CallError::Custom(ErrorObject::owned(
POOL_TEMPORARILY_BANNED,
"Transaction is temporarily banned",
None::<()>,
)),
Error::Pool(PoolError::AlreadyImported(hash)) =>
CallError::Custom(ErrorObject::owned(
POOL_ALREADY_IMPORTED,
"Transaction Already Imported",
Some(format!("{:?}", hash)),
)),
Error::Pool(PoolError::TooLowPriority { old, new }) => CallError::Custom(ErrorObject::owned(
POOL_TOO_LOW_PRIORITY,
format!("Priority is too low: ({} vs {})", old, new),
Some("The transaction has too low priority to replace another transaction already in the pool.")
)),
Error::Pool(PoolError::CycleDetected) =>
CallError::Custom(ErrorObject::owned(
POOL_CYCLE_DETECTED,
"Cycle Detected",
None::<()>
)),
Error::Pool(PoolError::ImmediatelyDropped) => CallError::Custom(ErrorObject::owned(
POOL_IMMEDIATELY_DROPPED,
"Immediately Dropped",
Some("The transaction couldn't enter the pool because of the limit"),
)),
Error::Pool(PoolError::Unactionable) => CallError::Custom(ErrorObject::owned(
POOL_UNACTIONABLE,
"Unactionable",
Some("The transaction is unactionable since it is not propagable and \
the local node does not author blocks")
)),
Error::Pool(PoolError::NoTagsProvided) => CallError::Custom(ErrorObject::owned(
POOL_NO_TAGS,
"No tags provided",
Some("Transaction does not provide any tags, so the pool can't identify it")
)),
Error::Pool(PoolError::InvalidBlockId(_)) =>
CallError::Custom(ErrorObject::owned(
POOL_INVALID_BLOCK_ID,
"The provided block ID is not valid",
None::<()>
)),
Error::Pool(PoolError::RejectedFutureTransaction) => {
CallError::Custom(ErrorObject::owned(
POOL_FUTURE_TX,
"The pool is not accepting future transactions",
None::<()>,
))
},
Error::UnsafeRpcCalled(e) => e.into(),
e => errors::internal(e),
}
e => CallError::Failed(e.into()),
}.into()
}
}
+23 -47
View File
@@ -18,85 +18,61 @@
//! Substrate block-author/full-node API.
pub mod error;
pub mod hash;
use self::error::{FutureResult, Result};
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId};
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sc_transaction_pool_api::TransactionStatus;
use sp_core::Bytes;
pub use self::gen_client::Client as AuthorClient;
pub mod error;
pub mod hash;
/// Substrate authoring RPC API
#[rpc]
#[rpc(client, server)]
pub trait AuthorApi<Hash, BlockHash> {
/// RPC metadata
type Metadata;
/// Submit hex-encoded extrinsic for inclusion in block.
#[rpc(name = "author_submitExtrinsic")]
fn submit_extrinsic(&self, extrinsic: Bytes) -> FutureResult<Hash>;
#[method(name = "author_submitExtrinsic")]
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<Hash>;
/// Insert a key into the keystore.
#[rpc(name = "author_insertKey")]
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()>;
#[method(name = "author_insertKey")]
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()>;
/// Generate new session keys and returns the corresponding public keys.
#[rpc(name = "author_rotateKeys")]
fn rotate_keys(&self) -> Result<Bytes>;
#[method(name = "author_rotateKeys")]
fn rotate_keys(&self) -> RpcResult<Bytes>;
/// Checks if the keystore has private keys for the given session public keys.
///
/// `session_keys` is the SCALE encoded session keys object from the runtime.
///
/// Returns `true` iff all private keys could be found.
#[rpc(name = "author_hasSessionKeys")]
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool>;
#[method(name = "author_hasSessionKeys")]
fn has_session_keys(&self, session_keys: Bytes) -> RpcResult<bool>;
/// Checks if the keystore has private keys for the given public key and key type.
///
/// Returns `true` if a private key could be found.
#[rpc(name = "author_hasKey")]
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool>;
#[method(name = "author_hasKey")]
fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult<bool>;
/// Returns all pending extrinsics, potentially grouped by sender.
#[rpc(name = "author_pendingExtrinsics")]
fn pending_extrinsics(&self) -> Result<Vec<Bytes>>;
#[method(name = "author_pendingExtrinsics")]
fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>>;
/// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting.
#[rpc(name = "author_removeExtrinsic")]
#[method(name = "author_removeExtrinsic")]
fn remove_extrinsic(
&self,
bytes_or_hash: Vec<hash::ExtrinsicOrHash<Hash>>,
) -> Result<Vec<Hash>>;
) -> RpcResult<Vec<Hash>>;
/// Submit an extrinsic to watch.
///
/// See [`TransactionStatus`](sc_transaction_pool_api::TransactionStatus) for details on
/// transaction life cycle.
#[pubsub(
subscription = "author_extrinsicUpdate",
subscribe,
name = "author_submitAndWatchExtrinsic"
#[subscription(
name = "author_submitAndWatchExtrinsic" => "author_extrinsicUpdate",
unsubscribe = "author_unwatchExtrinsic",
item = TransactionStatus<Hash, BlockHash>,
)]
fn watch_extrinsic(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<TransactionStatus<Hash, BlockHash>>,
bytes: Bytes,
);
/// Unsubscribe from extrinsic watching.
#[pubsub(
subscription = "author_extrinsicUpdate",
unsubscribe,
name = "author_unwatchExtrinsic"
)]
fn unwatch_extrinsic(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> Result<bool>;
fn watch_extrinsic(&self, bytes: Bytes);
}
+10 -15
View File
@@ -18,38 +18,33 @@
//! Error helpers for Chain RPC module.
use crate::errors;
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
/// Chain RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Chain RPC future Result type.
pub type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T>>;
/// Chain RPC errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Client error.
#[error("Client error: {}", .0)]
Client(#[from] Box<dyn std::error::Error + Send>),
Client(#[from] Box<dyn std::error::Error + Send + Sync>),
/// Other error type.
#[error("{0}")]
Other(String),
}
/// Base error code for all chain errors.
const BASE_ERROR: i64 = 3000;
const BASE_ERROR: i32 = 3000;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
match e {
Error::Other(message) => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 1),
message,
data: None,
},
e => errors::internal(e),
Error::Other(message) =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, message, None::<()>)).into(),
e => e.into(),
}
}
}
+34 -71
View File
@@ -18,96 +18,59 @@
//! Substrate blockchain API.
pub mod error;
use self::error::{FutureResult, Result};
use jsonrpc_core::Result as RpcResult;
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId};
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
pub use self::gen_client::Client as ChainClient;
pub mod error;
/// Substrate blockchain API
#[rpc]
#[rpc(client, server)]
pub trait ChainApi<Number, Hash, Header, SignedBlock> {
/// RPC metadata
type Metadata;
/// Get header of a relay chain block.
#[rpc(name = "chain_getHeader")]
fn header(&self, hash: Option<Hash>) -> FutureResult<Option<Header>>;
/// Get header.
#[method(name = "chain_getHeader")]
async fn header(&self, hash: Option<Hash>) -> RpcResult<Option<Header>>;
/// Get header and body of a relay chain block.
#[rpc(name = "chain_getBlock")]
fn block(&self, hash: Option<Hash>) -> FutureResult<Option<SignedBlock>>;
#[method(name = "chain_getBlock")]
async fn block(&self, hash: Option<Hash>) -> RpcResult<Option<SignedBlock>>;
/// Get hash of the n-th block in the canon chain.
///
/// By default returns latest block hash.
#[rpc(name = "chain_getBlockHash", alias("chain_getHead"))]
#[method(name = "chain_getBlockHash", aliases = ["chain_getHead"])]
fn block_hash(
&self,
hash: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Hash>>>;
) -> RpcResult<ListOrValue<Option<Hash>>>;
/// Get hash of the last finalized block in the canon chain.
#[rpc(name = "chain_getFinalizedHead", alias("chain_getFinalisedHead"))]
fn finalized_head(&self) -> Result<Hash>;
#[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"])]
fn finalized_head(&self) -> RpcResult<Hash>;
/// All head subscription
#[pubsub(subscription = "chain_allHead", subscribe, name = "chain_subscribeAllHeads")]
fn subscribe_all_heads(&self, metadata: Self::Metadata, subscriber: Subscriber<Header>);
/// Unsubscribe from all head subscription.
#[pubsub(subscription = "chain_allHead", unsubscribe, name = "chain_unsubscribeAllHeads")]
fn unsubscribe_all_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
/// New head subscription
#[pubsub(
subscription = "chain_newHead",
subscribe,
name = "chain_subscribeNewHeads",
alias("subscribe_newHead", "chain_subscribeNewHead")
/// All head subscription.
#[subscription(
name = "chain_subscribeAllHeads" => "chain_allHead",
unsubscribe = "chain_unsubscribeAllHeads",
item = Header
)]
fn subscribe_new_heads(&self, metadata: Self::Metadata, subscriber: Subscriber<Header>);
fn subscribe_all_heads(&self);
/// Unsubscribe from new head subscription.
#[pubsub(
subscription = "chain_newHead",
unsubscribe,
name = "chain_unsubscribeNewHeads",
alias("unsubscribe_newHead", "chain_unsubscribeNewHead")
/// New head subscription.
#[subscription(
name = "chain_subscribeNewHeads" => "chain_newHead",
aliases = ["subscribe_newHead", "chain_subscribeNewHead"],
unsubscribe = "chain_unsubscribeNewHeads",
unsubscribe_aliases = ["unsubscribe_newHead", "chain_unsubscribeNewHead"],
item = Header
)]
fn unsubscribe_new_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
fn subscribe_new_heads(&self);
/// Finalized head subscription
#[pubsub(
subscription = "chain_finalizedHead",
subscribe,
name = "chain_subscribeFinalizedHeads",
alias("chain_subscribeFinalisedHeads")
/// Finalized head subscription.
#[subscription(
name = "chain_subscribeFinalizedHeads" => "chain_finalizedHead",
aliases = ["chain_subscribeFinalisedHeads"],
unsubscribe = "chain_unsubscribeFinalizedHeads",
unsubscribe_aliases = ["chain_unsubscribeFinalisedHeads"],
item = Header
)]
fn subscribe_finalized_heads(&self, metadata: Self::Metadata, subscriber: Subscriber<Header>);
/// Unsubscribe from finalized head subscription.
#[pubsub(
subscription = "chain_finalizedHead",
unsubscribe,
name = "chain_unsubscribeFinalizedHeads",
alias("chain_unsubscribeFinalisedHeads")
)]
fn unsubscribe_finalized_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
fn subscribe_finalized_heads(&self);
}
+26 -33
View File
@@ -16,89 +16,82 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Substrate state API.
use crate::state::error::FutureResult;
use jsonrpc_derive::rpc;
use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey};
pub use self::gen_client::Client as ChildStateClient;
//! Substrate child state API
use crate::state::ReadProof;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey};
/// Substrate child state API
///
/// Note that all `PrefixedStorageKey` are deserialized
/// from json and not guaranteed valid.
#[rpc]
#[rpc(client, server)]
pub trait ChildStateApi<Hash> {
/// RPC Metadata
type Metadata;
/// DEPRECATED: Please use `childstate_getKeysPaged` with proper paging support.
/// Returns the keys with prefix from a child storage, leave empty to get all the keys
#[rpc(name = "childstate_getKeys")]
fn storage_keys(
#[method(name = "childstate_getKeys")]
#[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")]
async fn storage_keys(
&self,
child_storage_key: PrefixedStorageKey,
prefix: StorageKey,
hash: Option<Hash>,
) -> FutureResult<Vec<StorageKey>>;
) -> RpcResult<Vec<StorageKey>>;
/// Returns the keys with prefix from a child storage with pagination support.
/// Up to `count` keys will be returned.
/// If `start_key` is passed, return next keys in storage in lexicographic order.
#[rpc(name = "childstate_getKeysPaged", alias("childstate_getKeysPagedAt"))]
fn storage_keys_paged(
#[method(name = "childstate_getKeysPaged", aliases = ["childstate_getKeysPagedAt"])]
async fn storage_keys_paged(
&self,
child_storage_key: PrefixedStorageKey,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
hash: Option<Hash>,
) -> FutureResult<Vec<StorageKey>>;
) -> RpcResult<Vec<StorageKey>>;
/// Returns a child storage entry at a specific block's state.
#[rpc(name = "childstate_getStorage")]
fn storage(
#[method(name = "childstate_getStorage")]
async fn storage(
&self,
child_storage_key: PrefixedStorageKey,
key: StorageKey,
hash: Option<Hash>,
) -> FutureResult<Option<StorageData>>;
) -> RpcResult<Option<StorageData>>;
/// Returns child storage entries for multiple keys at a specific block's state.
#[rpc(name = "childstate_getStorageEntries")]
fn storage_entries(
#[method(name = "childstate_getStorageEntries")]
async fn storage_entries(
&self,
child_storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
hash: Option<Hash>,
) -> FutureResult<Vec<Option<StorageData>>>;
) -> RpcResult<Vec<Option<StorageData>>>;
/// Returns the hash of a child storage entry at a block's state.
#[rpc(name = "childstate_getStorageHash")]
fn storage_hash(
#[method(name = "childstate_getStorageHash")]
async fn storage_hash(
&self,
child_storage_key: PrefixedStorageKey,
key: StorageKey,
hash: Option<Hash>,
) -> FutureResult<Option<Hash>>;
) -> RpcResult<Option<Hash>>;
/// Returns the size of a child storage entry at a block's state.
#[rpc(name = "childstate_getStorageSize")]
fn storage_size(
#[method(name = "childstate_getStorageSize")]
async fn storage_size(
&self,
child_storage_key: PrefixedStorageKey,
key: StorageKey,
hash: Option<Hash>,
) -> FutureResult<Option<u64>>;
) -> RpcResult<Option<u64>>;
/// Returns proof of storage for child key entries at a specific block's state.
#[rpc(name = "state_getChildReadProof")]
fn read_child_proof(
#[method(name = "state_getChildReadProof")]
async fn read_child_proof(
&self,
child_storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
hash: Option<Hash>,
) -> FutureResult<ReadProof<Hash>>;
) -> RpcResult<ReadProof<Hash>>;
}
+16 -26
View File
@@ -18,14 +18,10 @@
//! Error helpers for Dev RPC module.
use crate::errors;
use jsonrpc_core as rpc;
/// Dev RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Dev RPC future Result type.
pub type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T>>;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
/// Dev RPC errors.
#[derive(Debug, thiserror::Error)]
@@ -45,27 +41,21 @@ pub enum Error {
}
/// Base error code for all dev errors.
const BASE_ERROR: i64 = 6000;
const BASE_ERROR: i32 = 6000;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
let msg = e.to_string();
match e {
Error::BlockQueryError(_) => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 1),
message: e.to_string(),
data: None,
},
Error::BlockExecutionFailed => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 3),
message: e.to_string(),
data: None,
},
Error::WitnessCompactionFailed => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 4),
message: e.to_string(),
data: None,
},
e => errors::internal(e),
Error::BlockQueryError(_) =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, msg, None::<()>)),
Error::BlockExecutionFailed =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 3, msg, None::<()>)),
Error::WitnessCompactionFailed =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 4, msg, None::<()>)),
Error::UnsafeRpcCalled(e) => e.into(),
}
.into()
}
}
+4 -5
View File
@@ -22,9 +22,8 @@
pub mod error;
use self::error::Result;
use codec::{Decode, Encode};
use jsonrpc_derive::rpc;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
@@ -52,13 +51,13 @@ pub struct BlockStats {
///
/// This API contains unstable and unsafe methods only meant for development nodes. They
/// are all flagged as unsafe for this reason.
#[rpc]
#[rpc(client, server)]
pub trait DevApi<Hash> {
/// Reexecute the specified `block_hash` and gather statistics while doing so.
///
/// This function requires the specified block and its parent to be available
/// at the queried node. If either the specified block or the parent is pruned,
/// this function will return `None`.
#[rpc(name = "dev_getBlockStats")]
fn block_stats(&self, block_hash: Hash) -> Result<Option<BlockStats>>;
#[method(name = "dev_getBlockStats")]
fn block_stats(&self, block_hash: Hash) -> RpcResult<Option<BlockStats>>;
}
-28
View File
@@ -1,28 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-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/>.
use log::warn;
pub fn internal<E: std::fmt::Display>(e: E) -> jsonrpc_core::Error {
warn!("Unknown error: {}", e);
jsonrpc_core::Error {
code: jsonrpc_core::ErrorCode::InternalError,
message: "Unknown error occurred".into(),
data: Some(e.to_string().into()),
}
}
-41
View File
@@ -1,41 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-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/>.
use futures::{channel::oneshot, Future};
use std::pin::Pin;
/// Wraps around `oneshot::Receiver` and adjusts the error type to produce an internal error if the
/// sender gets dropped.
pub struct Receiver<T>(pub oneshot::Receiver<T>);
impl<T> Future for Receiver<T> {
type Output = Result<T, jsonrpc_core::Error>;
fn poll(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
Future::poll(Pin::new(&mut self.0), cx).map_err(|_| jsonrpc_core::Error::internal_error())
}
}
impl<T: Send + 'static> jsonrpc_core::WrapFuture<T, jsonrpc_core::Error> for Receiver<T> {
fn into_future(self) -> jsonrpc_core::BoxFuture<Result<T, jsonrpc_core::Error>> {
Box::pin(async { self.await })
}
}
+1 -7
View File
@@ -22,15 +22,9 @@
#![warn(missing_docs)]
mod errors;
mod helpers;
mod metadata;
mod policy;
pub use helpers::Receiver;
pub use jsonrpc_core::IoHandlerExtension as RpcExtension;
pub use metadata::Metadata;
pub use policy::{DenyUnsafe, UnsafeRpcError};
pub use policy::DenyUnsafe;
pub mod author;
pub mod chain;
-60
View File
@@ -1,60 +0,0 @@
// 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/>.
//! RPC Metadata
use std::sync::Arc;
use futures::channel::mpsc;
use jsonrpc_pubsub::{PubSubMetadata, Session};
/// RPC Metadata.
///
/// Manages persistent session for transports that support it
/// and may contain some additional info extracted from specific transports
/// (like remote client IP address, request headers, etc)
#[derive(Default, Clone)]
pub struct Metadata {
session: Option<Arc<Session>>,
}
impl jsonrpc_core::Metadata for Metadata {}
impl PubSubMetadata for Metadata {
fn session(&self) -> Option<Arc<Session>> {
self.session.clone()
}
}
impl Metadata {
/// Create new `Metadata` with session (Pub/Sub) support.
pub fn new(transport: mpsc::UnboundedSender<String>) -> Self {
Metadata { session: Some(Arc::new(Session::new(transport))) }
}
/// Create new `Metadata` for tests.
#[cfg(test)]
pub fn new_test() -> (mpsc::UnboundedReceiver<String>, Self) {
let (tx, rx) = mpsc::unbounded();
(rx, Self::new(tx))
}
}
impl From<mpsc::UnboundedSender<String>> for Metadata {
fn from(sender: mpsc::UnboundedSender<String>) -> Self {
Self::new(sender)
}
}
+12 -8
View File
@@ -18,7 +18,10 @@
//! Offchain RPC errors.
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
/// Offchain RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
@@ -35,16 +38,17 @@ pub enum Error {
}
/// Base error code for all offchain errors.
const BASE_ERROR: i64 = 5000;
const BASE_ERROR: i32 = 5000;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
match e {
Error::UnavailableStorageKind => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 1),
message: "This storage kind is not available yet".into(),
data: None,
},
Error::UnavailableStorageKind => CallError::Custom(ErrorObject::owned(
BASE_ERROR + 1,
"This storage kind is not available yet",
None::<()>,
))
.into(),
Error::UnsafeRpcCalled(e) => e.into(),
}
}
+7 -10
View File
@@ -18,22 +18,19 @@
//! Substrate offchain API.
pub mod error;
use self::error::Result;
use jsonrpc_derive::rpc;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sp_core::{offchain::StorageKind, Bytes};
pub use self::gen_client::Client as OffchainClient;
pub mod error;
/// Substrate offchain RPC API
#[rpc]
#[rpc(client, server)]
pub trait OffchainApi {
/// Set offchain local storage under given key and prefix.
#[rpc(name = "offchain_localStorageSet")]
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()>;
#[method(name = "offchain_localStorageSet")]
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()>;
/// Get offchain local storage under given key and prefix.
#[rpc(name = "offchain_localStorageGet")]
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>>;
#[method(name = "offchain_localStorageGet")]
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult<Option<Bytes>>;
}
+20 -4
View File
@@ -21,7 +21,13 @@
//! Contains a `DenyUnsafe` type that can be used to deny potentially unsafe
//! RPC when accessed externally.
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::{
error::{CallError, ErrorCode},
ErrorObject,
},
};
/// Signifies whether a potentially unsafe RPC should be denied.
#[derive(Clone, Copy, Debug)]
@@ -55,8 +61,18 @@ impl std::fmt::Display for UnsafeRpcError {
impl std::error::Error for UnsafeRpcError {}
impl From<UnsafeRpcError> for rpc::Error {
fn from(error: UnsafeRpcError) -> rpc::Error {
rpc::Error { code: rpc::ErrorCode::MethodNotFound, message: error.to_string(), data: None }
impl From<UnsafeRpcError> for CallError {
fn from(e: UnsafeRpcError) -> CallError {
CallError::Custom(ErrorObject::owned(
ErrorCode::MethodNotFound.code(),
e.to_string(),
None::<()>,
))
}
}
impl From<UnsafeRpcError> for JsonRpseeError {
fn from(e: UnsafeRpcError) -> JsonRpseeError {
JsonRpseeError::Call(e.into())
}
}
+14 -20
View File
@@ -18,21 +18,19 @@
//! State RPC errors.
use crate::errors;
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
/// State RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
/// State RPC future Result type.
pub type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T>>;
/// State RPC errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Client error.
#[error("Client error: {}", .0)]
Client(#[from] Box<dyn std::error::Error + Send>),
Client(#[from] Box<dyn std::error::Error + Send + Sync>),
/// Provided block range couldn't be resolved to a list of blocks.
#[error("Cannot resolve a block range ['{:?}' ... '{:?}]. {}", .from, .to, .details)]
InvalidBlockRange {
@@ -57,22 +55,18 @@ pub enum Error {
}
/// Base code for all state errors.
const BASE_ERROR: i64 = 4000;
const BASE_ERROR: i32 = 4000;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
match e {
Error::InvalidBlockRange { .. } => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 1),
message: format!("{}", e),
data: None,
},
Error::InvalidCount { .. } => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 2),
message: format!("{}", e),
data: None,
},
e => errors::internal(e),
Error::InvalidBlockRange { .. } =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, e.to_string(), None::<()>))
.into(),
Error::InvalidCount { .. } =>
CallError::Custom(ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>))
.into(),
e => Self::to_call_error(e),
}
}
}
+60 -99
View File
@@ -18,161 +18,122 @@
//! Substrate state API.
pub mod error;
pub mod helpers;
use self::error::FutureResult;
use jsonrpc_core::Result as RpcResult;
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId};
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sp_core::{
storage::{StorageChangeSet, StorageData, StorageKey},
Bytes,
};
use sp_version::RuntimeVersion;
pub use self::{gen_client::Client as StateClient, helpers::ReadProof};
pub mod error;
pub mod helpers;
pub use self::helpers::ReadProof;
/// Substrate state API
#[rpc]
#[rpc(client, server)]
pub trait StateApi<Hash> {
/// RPC Metadata
type Metadata;
/// Call a contract at a block's state.
#[rpc(name = "state_call", alias("state_callAt"))]
fn call(&self, name: String, bytes: Bytes, hash: Option<Hash>) -> FutureResult<Bytes>;
#[method(name = "state_call", aliases = ["state_callAt"])]
async fn call(&self, name: String, bytes: Bytes, hash: Option<Hash>) -> RpcResult<Bytes>;
/// DEPRECATED: Please use `state_getKeysPaged` with proper paging support.
/// Returns the keys with prefix, leave empty to get all the keys.
#[rpc(name = "state_getKeys")]
fn storage_keys(&self, prefix: StorageKey, hash: Option<Hash>)
-> FutureResult<Vec<StorageKey>>;
/// Returns the keys with prefix, leave empty to get all the keys
#[rpc(name = "state_getPairs")]
fn storage_pairs(
#[method(name = "state_getKeys")]
#[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")]
async fn storage_keys(
&self,
prefix: StorageKey,
hash: Option<Hash>,
) -> FutureResult<Vec<(StorageKey, StorageData)>>;
) -> RpcResult<Vec<StorageKey>>;
/// Returns the keys with prefix, leave empty to get all the keys
#[method(name = "state_getPairs")]
async fn storage_pairs(
&self,
prefix: StorageKey,
hash: Option<Hash>,
) -> RpcResult<Vec<(StorageKey, StorageData)>>;
/// Returns the keys with prefix with pagination support.
/// Up to `count` keys will be returned.
/// If `start_key` is passed, return next keys in storage in lexicographic order.
#[rpc(name = "state_getKeysPaged", alias("state_getKeysPagedAt"))]
fn storage_keys_paged(
#[method(name = "state_getKeysPaged", aliases = ["state_getKeysPagedAt"])]
async fn storage_keys_paged(
&self,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
hash: Option<Hash>,
) -> FutureResult<Vec<StorageKey>>;
) -> RpcResult<Vec<StorageKey>>;
/// Returns a storage entry at a specific block's state.
#[rpc(name = "state_getStorage", alias("state_getStorageAt"))]
fn storage(&self, key: StorageKey, hash: Option<Hash>) -> FutureResult<Option<StorageData>>;
#[method(name = "state_getStorage", aliases = ["state_getStorageAt"])]
async fn storage(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<StorageData>>;
/// Returns the hash of a storage entry at a block's state.
#[rpc(name = "state_getStorageHash", alias("state_getStorageHashAt"))]
fn storage_hash(&self, key: StorageKey, hash: Option<Hash>) -> FutureResult<Option<Hash>>;
#[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"])]
async fn storage_hash(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<Hash>>;
/// Returns the size of a storage entry at a block's state.
#[rpc(name = "state_getStorageSize", alias("state_getStorageSizeAt"))]
fn storage_size(&self, key: StorageKey, hash: Option<Hash>) -> FutureResult<Option<u64>>;
#[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])]
async fn storage_size(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<u64>>;
/// Returns the runtime metadata as an opaque blob.
#[rpc(name = "state_getMetadata")]
fn metadata(&self, hash: Option<Hash>) -> FutureResult<Bytes>;
#[method(name = "state_getMetadata")]
async fn metadata(&self, hash: Option<Hash>) -> RpcResult<Bytes>;
/// Get the runtime version.
#[rpc(name = "state_getRuntimeVersion", alias("chain_getRuntimeVersion"))]
fn runtime_version(&self, hash: Option<Hash>) -> FutureResult<RuntimeVersion>;
#[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"])]
async fn runtime_version(&self, hash: Option<Hash>) -> RpcResult<RuntimeVersion>;
/// Query historical storage entries (by key) starting from a block given as the second
/// parameter.
///
/// NOTE This first returned result contains the initial state of storage for all keys.
/// Subsequent values in the vector represent changes to the previous state (diffs).
#[rpc(name = "state_queryStorage")]
fn query_storage(
#[method(name = "state_queryStorage")]
async fn query_storage(
&self,
keys: Vec<StorageKey>,
block: Hash,
hash: Option<Hash>,
) -> FutureResult<Vec<StorageChangeSet<Hash>>>;
) -> RpcResult<Vec<StorageChangeSet<Hash>>>;
/// Query storage entries (by key) starting at block hash given as the second parameter.
#[rpc(name = "state_queryStorageAt")]
fn query_storage_at(
#[method(name = "state_queryStorageAt")]
async fn query_storage_at(
&self,
keys: Vec<StorageKey>,
at: Option<Hash>,
) -> FutureResult<Vec<StorageChangeSet<Hash>>>;
) -> RpcResult<Vec<StorageChangeSet<Hash>>>;
/// Returns proof of storage entries at a specific block's state.
#[rpc(name = "state_getReadProof")]
fn read_proof(
#[method(name = "state_getReadProof")]
async fn read_proof(
&self,
keys: Vec<StorageKey>,
hash: Option<Hash>,
) -> FutureResult<ReadProof<Hash>>;
) -> RpcResult<ReadProof<Hash>>;
/// New runtime version subscription
#[pubsub(
subscription = "state_runtimeVersion",
subscribe,
name = "state_subscribeRuntimeVersion",
alias("chain_subscribeRuntimeVersion")
#[subscription(
name = "state_subscribeRuntimeVersion" => "state_runtimeVersion",
unsubscribe = "state_unsubscribeRuntimeVersion",
aliases = ["chain_subscribeRuntimeVersion"],
unsubscribe_aliases = ["chain_unsubscribeRuntimeVersion"],
item = RuntimeVersion,
)]
fn subscribe_runtime_version(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<RuntimeVersion>,
);
fn subscribe_runtime_version(&self);
/// Unsubscribe from runtime version subscription
#[pubsub(
subscription = "state_runtimeVersion",
unsubscribe,
name = "state_unsubscribeRuntimeVersion",
alias("chain_unsubscribeRuntimeVersion")
/// New storage subscription
#[subscription(
name = "state_subscribeStorage" => "state_storage",
unsubscribe = "state_unsubscribeStorage",
item = StorageChangeSet<Hash>,
)]
fn unsubscribe_runtime_version(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
fn subscribe_storage(&self, keys: Option<Vec<StorageKey>>);
/// Subscribe to the changes in the storage.
///
/// This RPC endpoint has two modes of operation:
/// 1) When `keys` is not `None` you'll only be informed about the changes
/// done to the specified keys; this is RPC-safe.
/// 2) When `keys` is `None` you'll be informed of *all* of the changes;
/// **this is RPC-unsafe**.
///
/// When subscribed to all of the changes this API will emit every storage
/// change for every block that is imported. These changes will only be sent
/// after a block is imported. If you require a consistent view across all changes
/// of every block, you need to take this into account.
#[pubsub(subscription = "state_storage", subscribe, name = "state_subscribeStorage")]
fn subscribe_storage(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<StorageChangeSet<Hash>>,
keys: Option<Vec<StorageKey>>,
);
/// Unsubscribe from storage subscription
#[pubsub(subscription = "state_storage", unsubscribe, name = "state_unsubscribeStorage")]
fn unsubscribe_storage(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
/// The `state_traceBlock` RPC provides a way to trace the re-execution of a single
/// The `traceBlock` RPC provides a way to trace the re-execution of a single
/// block, collecting Spans and Events from both the client and the relevant WASM runtime.
/// The Spans and Events are conceptually equivalent to those from the [Tracing][1] crate.
///
@@ -323,13 +284,13 @@ pub trait StateApi<Hash> {
/// narrow down the traces using a smaller set of targets and/or storage keys.
///
/// If you are having issues with maximum payload size you can use the flag
/// `-lstate_tracing=trace` to get some logging during tracing.
#[rpc(name = "state_traceBlock")]
fn trace_block(
/// `-ltracing=trace` to get some logging during tracing.
#[method(name = "state_traceBlock")]
async fn trace_block(
&self,
block: Hash,
targets: Option<String>,
storage_keys: Option<String>,
methods: Option<String>,
) -> FutureResult<sp_rpc::tracing::TraceBlockResponse>;
) -> RpcResult<sp_rpc::tracing::TraceBlockResponse>;
}
+19 -14
View File
@@ -19,7 +19,10 @@
//! System RPC module errors.
use crate::system::helpers::Health;
use jsonrpc_core as rpc;
use jsonrpsee::{
core::Error as JsonRpseeError,
types::error::{CallError, ErrorObject},
};
/// System RPC Result type.
pub type Result<T> = std::result::Result<T, Error>;
@@ -35,22 +38,24 @@ pub enum Error {
MalformattedPeerArg(String),
}
/// Base code for all system errors.
const BASE_ERROR: i64 = 2000;
// Base code for all system errors.
const BASE_ERROR: i32 = 2000;
// Provided block range couldn't be resolved to a list of blocks.
const NOT_HEALTHY_ERROR: i32 = BASE_ERROR + 1;
// Peer argument is malformatted.
const MALFORMATTED_PEER_ARG_ERROR: i32 = BASE_ERROR + 2;
impl From<Error> for rpc::Error {
impl From<Error> for JsonRpseeError {
fn from(e: Error) -> Self {
match e {
Error::NotHealthy(ref h) => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 1),
message: format!("{}", e),
data: serde_json::to_value(h).ok(),
},
Error::MalformattedPeerArg(ref e) => rpc::Error {
code: rpc::ErrorCode::ServerError(BASE_ERROR + 2),
message: e.clone(),
data: None,
},
Error::NotHealthy(ref h) =>
CallError::Custom(ErrorObject::owned(NOT_HEALTHY_ERROR, e.to_string(), Some(h))),
Error::MalformattedPeerArg(e) => CallError::Custom(ErrorObject::owned(
MALFORMATTED_PEER_ARG_ERROR + 2,
e,
None::<()>,
)),
}
.into()
}
}
+44 -51
View File
@@ -18,65 +18,61 @@
//! Substrate system API.
use jsonrpsee::{
core::{JsonValue, RpcResult},
proc_macros::rpc,
};
pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo};
pub mod error;
pub mod helpers;
use crate::helpers::Receiver;
use jsonrpc_core::BoxFuture;
use jsonrpc_derive::rpc;
use self::error::Result as SystemResult;
pub use self::{
gen_client::Client as SystemClient,
helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo},
};
/// Substrate system RPC API
#[rpc]
#[rpc(client, server)]
pub trait SystemApi<Hash, Number> {
/// Get the node's implementation name. Plain old string.
#[rpc(name = "system_name")]
fn system_name(&self) -> SystemResult<String>;
#[method(name = "system_name")]
fn system_name(&self) -> RpcResult<String>;
/// Get the node implementation's version. Should be a semver string.
#[rpc(name = "system_version")]
fn system_version(&self) -> SystemResult<String>;
#[method(name = "system_version")]
fn system_version(&self) -> RpcResult<String>;
/// Get the chain's name. Given as a string identifier.
#[rpc(name = "system_chain")]
fn system_chain(&self) -> SystemResult<String>;
#[method(name = "system_chain")]
fn system_chain(&self) -> RpcResult<String>;
/// Get the chain's type.
#[rpc(name = "system_chainType")]
fn system_type(&self) -> SystemResult<sc_chain_spec::ChainType>;
#[method(name = "system_chainType")]
fn system_type(&self) -> RpcResult<sc_chain_spec::ChainType>;
/// Get a custom set of properties as a JSON object, defined in the chain spec.
#[rpc(name = "system_properties")]
fn system_properties(&self) -> SystemResult<sc_chain_spec::Properties>;
#[method(name = "system_properties")]
fn system_properties(&self) -> RpcResult<sc_chain_spec::Properties>;
/// Return health status of the node.
///
/// Node is considered healthy if it is:
/// - connected to some peers (unless running in dev mode)
/// - not performing a major sync
#[rpc(name = "system_health", returns = "Health")]
fn system_health(&self) -> Receiver<Health>;
#[method(name = "system_health")]
async fn system_health(&self) -> RpcResult<Health>;
/// Returns the base58-encoded PeerId of the node.
#[rpc(name = "system_localPeerId", returns = "String")]
fn system_local_peer_id(&self) -> Receiver<String>;
#[method(name = "system_localPeerId")]
async fn system_local_peer_id(&self) -> RpcResult<String>;
/// Returns the multiaddresses that the local node is listening on
/// Returns the multi-addresses that the local node is listening on
///
/// The addresses include a trailing `/p2p/` with the local PeerId, and are thus suitable to
/// be passed to `system_addReservedPeer` or as a bootnode address for example.
#[rpc(name = "system_localListenAddresses", returns = "Vec<String>")]
fn system_local_listen_addresses(&self) -> Receiver<Vec<String>>;
/// be passed to `addReservedPeer` or as a bootnode address for example.
#[method(name = "system_localListenAddresses")]
async fn system_local_listen_addresses(&self) -> RpcResult<Vec<String>>;
/// Returns currently connected peers
#[rpc(name = "system_peers", returns = "Vec<PeerInfo<Hash, Number>>")]
fn system_peers(&self) -> BoxFuture<jsonrpc_core::Result<Vec<PeerInfo<Hash, Number>>>>;
#[method(name = "system_peers")]
async fn system_peers(&self) -> RpcResult<Vec<PeerInfo<Hash, Number>>>;
/// Returns current state of the network.
///
@@ -84,47 +80,44 @@ pub trait SystemApi<Hash, Number> {
/// as its format might change at any time.
// TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890
// https://github.com/paritytech/substrate/issues/5541
#[rpc(name = "system_unstable_networkState", returns = "jsonrpc_core::Value")]
fn system_network_state(&self) -> BoxFuture<jsonrpc_core::Result<jsonrpc_core::Value>>;
#[method(name = "system_unstable_networkState")]
async fn system_network_state(&self) -> RpcResult<JsonValue>;
/// Adds a reserved peer. Returns the empty string or an error. The string
/// parameter should encode a `p2p` multiaddr.
///
/// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`
/// is an example of a valid, passing multiaddr with PeerId attached.
#[rpc(name = "system_addReservedPeer", returns = "()")]
fn system_add_reserved_peer(&self, peer: String) -> BoxFuture<Result<(), jsonrpc_core::Error>>;
#[method(name = "system_addReservedPeer")]
async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()>;
/// Remove a reserved peer. Returns the empty string or an error. The string
/// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`.
#[rpc(name = "system_removeReservedPeer", returns = "()")]
fn system_remove_reserved_peer(
&self,
peer_id: String,
) -> BoxFuture<Result<(), jsonrpc_core::Error>>;
#[method(name = "system_removeReservedPeer")]
async fn system_remove_reserved_peer(&self, peer_id: String) -> RpcResult<()>;
/// Returns the list of reserved peers
#[rpc(name = "system_reservedPeers", returns = "Vec<String>")]
fn system_reserved_peers(&self) -> Receiver<Vec<String>>;
#[method(name = "system_reservedPeers")]
async fn system_reserved_peers(&self) -> RpcResult<Vec<String>>;
/// Returns the roles the node is running as.
#[rpc(name = "system_nodeRoles", returns = "Vec<NodeRole>")]
fn system_node_roles(&self) -> Receiver<Vec<NodeRole>>;
#[method(name = "system_nodeRoles")]
async fn system_node_roles(&self) -> RpcResult<Vec<NodeRole>>;
/// Returns the state of the syncing of the node: starting block, current best block, highest
/// known block.
#[rpc(name = "system_syncState", returns = "SyncState<Number>")]
fn system_sync_state(&self) -> Receiver<SyncState<Number>>;
#[method(name = "system_syncState")]
async fn system_sync_state(&self) -> RpcResult<SyncState<Number>>;
/// Adds the supplied directives to the current log filter
///
/// The syntax is identical to the CLI `<target>=<level>`:
///
/// `sync=debug,state=trace`
#[rpc(name = "system_addLogFilter", returns = "()")]
fn system_add_log_filter(&self, directives: String) -> Result<(), jsonrpc_core::Error>;
#[method(name = "system_addLogFilter")]
fn system_add_log_filter(&self, directives: String) -> RpcResult<()>;
/// Resets the log filter to Substrate defaults
#[rpc(name = "system_resetLogFilter", returns = "()")]
fn system_reset_log_filter(&self) -> Result<(), jsonrpc_core::Error>;
#[method(name = "system_resetLogFilter")]
fn system_reset_log_filter(&self) -> RpcResult<()>;
}
+1 -5
View File
@@ -14,12 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
futures = "0.3.21"
http = { package = "jsonrpc-http-server", version = "18.0.0" }
ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" }
jsonrpc-core = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server"] }
log = "0.4.16"
pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" }
serde_json = "1.0.79"
tokio = { version = "1.17.0", features = ["parking_lot"] }
ws = { package = "jsonrpc-ws-server", version = "18.0.0" }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" }
+158 -178
View File
@@ -20,213 +20,193 @@
#![warn(missing_docs)]
mod middleware;
use jsonrpsee::{
http_server::{AccessControlBuilder, HttpServerBuilder, HttpServerHandle},
ws_server::{WsServerBuilder, WsServerHandle},
RpcModule,
};
use std::{error::Error as StdError, net::SocketAddr};
use jsonrpc_core::{IoHandlerExtension, MetaIoHandler};
use log::error;
use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64};
use pubsub::PubSubMetadata;
use std::io;
pub use crate::middleware::{RpcMetrics, RpcMiddleware};
pub use jsonrpsee::core::{
id_providers::{RandomIntegerIdProvider, RandomStringIdProvider},
traits::IdProvider,
};
const MEGABYTE: usize = 1024 * 1024;
/// Maximal payload accepted by RPC servers.
pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE;
/// Maximal buffer size in WS server.
pub const WS_MAX_BUFFER_CAPACITY_DEFAULT: usize = 16 * MEGABYTE;
/// Default maximum number of connections for WS RPC servers.
const WS_MAX_CONNECTIONS: usize = 100;
/// The RPC IoHandler containing all requested APIs.
pub type RpcHandler<T> = pubsub::PubSubHandler<T, RpcMiddleware>;
/// Default maximum number subscriptions per connection for WS RPC servers.
const WS_MAX_SUBS_PER_CONN: usize = 1024;
pub use middleware::{method_names, RpcMetrics, RpcMiddleware};
pub mod middleware;
/// Construct rpc `IoHandler`
pub fn rpc_handler<M: PubSubMetadata>(
extension: impl IoHandlerExtension<M>,
rpc_middleware: RpcMiddleware,
) -> RpcHandler<M> {
let io_handler = MetaIoHandler::with_middleware(rpc_middleware);
let mut io = pubsub::PubSubHandler::new(io_handler);
extension.augment(&mut io);
// add an endpoint to list all available methods.
let mut methods = io.iter().map(|x| x.0.clone()).collect::<Vec<String>>();
io.add_method("rpc_methods", {
methods.sort();
let methods = serde_json::to_value(&methods)
.expect("Serialization of Vec<String> is infallible; qed");
move |_| {
let methods = methods.clone();
async move {
Ok(serde_json::json!({
"version": 1,
"methods": methods,
}))
}
}
});
io
}
/// RPC server-specific prometheus metrics.
#[derive(Debug, Clone, Default)]
pub struct ServerMetrics {
/// Number of sessions opened.
session_opened: Option<Counter<U64>>,
/// Number of sessions closed.
session_closed: Option<Counter<U64>>,
}
impl ServerMetrics {
/// Create new WebSocket RPC server metrics.
pub fn new(registry: Option<&Registry>) -> Result<Self, PrometheusError> {
registry
.map(|r| {
Ok(Self {
session_opened: register(
Counter::new(
"substrate_rpc_sessions_opened",
"Number of persistent RPC sessions opened",
)?,
r,
)?
.into(),
session_closed: register(
Counter::new(
"substrate_rpc_sessions_closed",
"Number of persistent RPC sessions closed",
)?,
r,
)?
.into(),
})
})
.unwrap_or_else(|| Ok(Default::default()))
}
}
/// Type alias for ipc server
pub type IpcServer = ipc::Server;
/// Type alias for http server
pub type HttpServer = http::Server;
pub type HttpServer = HttpServerHandle;
/// Type alias for ws server
pub type WsServer = ws::Server;
pub type WsServer = WsServerHandle;
impl ws::SessionStats for ServerMetrics {
fn open_session(&self, _id: ws::SessionId) {
self.session_opened.as_ref().map(|m| m.inc());
}
/// WebSocket specific settings on the server.
pub struct WsConfig {
/// Maximum connections.
pub max_connections: Option<usize>,
/// Maximum subscriptions per connection.
pub max_subs_per_conn: Option<usize>,
/// Maximum rpc request payload size.
pub max_payload_in_mb: Option<usize>,
/// Maximum rpc response payload size.
pub max_payload_out_mb: Option<usize>,
}
fn close_session(&self, _id: ws::SessionId) {
self.session_closed.as_ref().map(|m| m.inc());
impl WsConfig {
// Deconstructs the config to get the finalized inner values.
//
// `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated.
fn deconstruct(self) -> (u32, u32, u64, u32) {
let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u64;
let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32;
let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32;
let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32;
(max_payload_in_mb, max_payload_out_mb, max_conns, max_subs_per_conn)
}
}
/// Start HTTP server listening on given address.
pub fn start_http<M: pubsub::PubSubMetadata + Default + Unpin>(
addr: &std::net::SocketAddr,
pub async fn start_http<M: Send + Sync + 'static>(
addrs: [SocketAddr; 2],
cors: Option<&Vec<String>>,
io: RpcHandler<M>,
maybe_max_payload_mb: Option<usize>,
tokio_handle: tokio::runtime::Handle,
) -> io::Result<http::Server> {
let max_request_body_size = maybe_max_payload_mb
.map(|mb| mb.saturating_mul(MEGABYTE))
.unwrap_or(RPC_MAX_PAYLOAD_DEFAULT);
max_payload_in_mb: Option<usize>,
max_payload_out_mb: Option<usize>,
metrics: Option<RpcMetrics>,
rpc_api: RpcModule<M>,
rt: tokio::runtime::Handle,
) -> Result<HttpServerHandle, Box<dyn StdError + Send + Sync>> {
let max_payload_in = payload_size_or_default(max_payload_in_mb);
let max_payload_out = payload_size_or_default(max_payload_out_mb);
http::ServerBuilder::new(io)
.threads(1)
.event_loop_executor(tokio_handle)
.health_api(("/health", "system_health"))
.allowed_hosts(hosts_filtering(cors.is_some()))
.rest_api(if cors.is_some() { http::RestApi::Secure } else { http::RestApi::Unsecure })
.cors(map_cors::<http::AccessControlAllowOrigin>(cors))
.max_request_body_size(max_request_body_size)
.start_http(addr)
}
let mut acl = AccessControlBuilder::new();
/// Start IPC server listening on given path.
pub fn start_ipc<M: pubsub::PubSubMetadata + Default>(
addr: &str,
io: RpcHandler<M>,
server_metrics: ServerMetrics,
) -> io::Result<ipc::Server> {
let builder = ipc::ServerBuilder::new(io);
#[cfg(target_os = "unix")]
builder.set_security_attributes({
let security_attributes = ipc::SecurityAttributes::empty();
security_attributes.set_mode(0o600)?;
security_attributes
});
builder.session_stats(server_metrics).start(addr)
if let Some(cors) = cors {
// Whitelist listening address.
// NOTE: set_allowed_hosts will whitelist both ports but only one will used.
acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?;
acl = acl.set_allowed_origins(cors)?;
};
let builder = HttpServerBuilder::new()
.max_request_body_size(max_payload_in as u32)
.max_response_body_size(max_payload_out as u32)
.set_access_control(acl.build())
.custom_tokio_runtime(rt);
let rpc_api = build_rpc_api(rpc_api);
let (handle, addr) = if let Some(metrics) = metrics {
let middleware = RpcMiddleware::new(metrics, "http".into());
let builder = builder.set_middleware(middleware);
let server = builder.build(&addrs[..]).await?;
let addr = server.local_addr();
(server.start(rpc_api)?, addr)
} else {
let server = builder.build(&addrs[..]).await?;
let addr = server.local_addr();
(server.start(rpc_api)?, addr)
};
log::info!(
"Running JSON-RPC HTTP server: addr={}, allowed origins={:?}",
addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
cors
);
Ok(handle)
}
/// Start WS server listening on given address.
pub fn start_ws<
M: pubsub::PubSubMetadata + From<futures::channel::mpsc::UnboundedSender<String>>,
>(
addr: &std::net::SocketAddr,
max_connections: Option<usize>,
pub async fn start_ws<M: Send + Sync + 'static>(
addrs: [SocketAddr; 2],
cors: Option<&Vec<String>>,
io: RpcHandler<M>,
maybe_max_payload_mb: Option<usize>,
maybe_max_out_buffer_capacity_mb: Option<usize>,
server_metrics: ServerMetrics,
tokio_handle: tokio::runtime::Handle,
) -> io::Result<ws::Server> {
let max_payload = maybe_max_payload_mb
.map(|mb| mb.saturating_mul(MEGABYTE))
.unwrap_or(RPC_MAX_PAYLOAD_DEFAULT);
let max_out_buffer_capacity = maybe_max_out_buffer_capacity_mb
.map(|mb| mb.saturating_mul(MEGABYTE))
.unwrap_or(WS_MAX_BUFFER_CAPACITY_DEFAULT);
ws_config: WsConfig,
metrics: Option<RpcMetrics>,
rpc_api: RpcModule<M>,
rt: tokio::runtime::Handle,
id_provider: Option<Box<dyn IdProvider>>,
) -> Result<WsServerHandle, Box<dyn StdError + Send + Sync>> {
let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) =
ws_config.deconstruct();
if max_payload > max_out_buffer_capacity {
log::warn!(
"maximum payload ({}) is more than maximum output buffer ({}) size in ws server, the payload will actually be limited by the buffer size",
max_payload,
max_out_buffer_capacity,
)
}
let mut builder = WsServerBuilder::new()
.max_request_body_size(max_payload_in)
.max_response_body_size(max_payload_out)
.max_connections(max_connections)
.max_subscriptions_per_connection(max_subs_per_conn)
.custom_tokio_runtime(rt);
ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| {
context.sender().into()
})
.event_loop_executor(tokio_handle)
.max_payload(max_payload)
.max_connections(max_connections.unwrap_or(WS_MAX_CONNECTIONS))
.max_out_buffer_capacity(max_out_buffer_capacity)
.allowed_origins(map_cors(cors))
.allowed_hosts(hosts_filtering(cors.is_some()))
.session_stats(server_metrics)
.start(addr)
.map_err(|err| match err {
ws::Error::Io(io) => io,
ws::Error::ConnectionClosed => io::ErrorKind::BrokenPipe.into(),
e => {
error!("{}", e);
io::ErrorKind::Other.into()
},
})
}
fn map_cors<T: for<'a> From<&'a str>>(cors: Option<&Vec<String>>) -> http::DomainsValidation<T> {
cors.map(|x| x.iter().map(AsRef::as_ref).map(Into::into).collect::<Vec<_>>())
.into()
}
fn hosts_filtering(enable: bool) -> http::DomainsValidation<http::Host> {
if enable {
// NOTE The listening address is whitelisted by default.
// Setting an empty vector here enables the validation
// and allows only the listening address.
http::DomainsValidation::AllowOnly(vec![])
if let Some(provider) = id_provider {
builder = builder.set_id_provider(provider);
} else {
http::DomainsValidation::Disabled
builder = builder.set_id_provider(RandomStringIdProvider::new(16));
};
if let Some(cors) = cors {
// Whitelist listening address.
// NOTE: set_allowed_hosts will whitelist both ports but only one will used.
builder = builder.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?;
builder = builder.set_allowed_origins(cors)?;
}
let rpc_api = build_rpc_api(rpc_api);
let (handle, addr) = if let Some(metrics) = metrics {
let middleware = RpcMiddleware::new(metrics, "ws".into());
let builder = builder.set_middleware(middleware);
let server = builder.build(&addrs[..]).await?;
let addr = server.local_addr();
(server.start(rpc_api)?, addr)
} else {
let server = builder.build(&addrs[..]).await?;
let addr = server.local_addr();
(server.start(rpc_api)?, addr)
};
log::info!(
"Running JSON-RPC WS server: addr={}, allowed origins={:?}",
addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
cors
);
Ok(handle)
}
fn format_allowed_hosts(addrs: &[SocketAddr]) -> Vec<String> {
let mut hosts = Vec::with_capacity(addrs.len() * 2);
for addr in addrs {
hosts.push(format!("localhost:{}", addr.port()));
hosts.push(format!("127.0.0.1:{}", addr.port()));
}
hosts
}
fn build_rpc_api<M: Send + Sync + 'static>(mut rpc_api: RpcModule<M>) -> RpcModule<M> {
let mut available_methods = rpc_api.method_names().collect::<Vec<_>>();
available_methods.sort_unstable();
rpc_api
.register_method("rpc_methods", move |_, _| {
Ok(serde_json::json!({
"version": 1,
"methods": available_methods,
}))
})
.expect("infallible all other methods have their own address space; qed");
rpc_api
}
fn payload_size_or_default(size_mb: Option<usize>) -> usize {
size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE))
}
+92 -141
View File
@@ -16,34 +16,38 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Middleware for RPC requests.
//! RPC middlware to collect prometheus metrics on RPC calls.
use std::collections::HashSet;
use jsonrpc_core::{FutureOutput, FutureResponse, Metadata, Middleware as RequestMiddleware};
use jsonrpsee::core::middleware::Middleware;
use prometheus_endpoint::{
register, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64,
register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry,
U64,
};
use futures::{future::Either, Future, FutureExt};
use pubsub::PubSubMetadata;
use crate::RpcHandler;
/// Metrics for RPC middleware
/// Metrics for RPC middleware storing information about the number of requests started/completed,
/// calls started/completed and their timings.
#[derive(Debug, Clone)]
pub struct RpcMetrics {
/// Number of RPC requests received since the server started.
requests_started: CounterVec<U64>,
/// Number of RPC requests completed since the server started.
requests_finished: CounterVec<U64>,
/// Histogram over RPC execution times.
calls_time: HistogramVec,
/// Number of calls started.
calls_started: CounterVec<U64>,
/// Number of calls completed.
calls_finished: CounterVec<U64>,
/// Number of Websocket sessions opened (Websocket only).
ws_sessions_opened: Option<Counter<U64>>,
/// Number of Websocket sessions closed (Websocket only).
ws_sessions_closed: Option<Counter<U64>>,
}
impl RpcMetrics {
/// Create an instance of metrics
pub fn new(metrics_registry: Option<&Registry>) -> Result<Option<Self>, PrometheusError> {
if let Some(r) = metrics_registry {
if let Some(metrics_registry) = metrics_registry {
Ok(Some(Self {
requests_started: register(
CounterVec::new(
@@ -53,7 +57,7 @@ impl RpcMetrics {
),
&["protocol"],
)?,
r,
metrics_registry,
)?,
requests_finished: register(
CounterVec::new(
@@ -63,7 +67,7 @@ impl RpcMetrics {
),
&["protocol"],
)?,
r,
metrics_registry,
)?,
calls_time: register(
HistogramVec::new(
@@ -73,7 +77,7 @@ impl RpcMetrics {
),
&["protocol", "method"],
)?,
r,
metrics_registry,
)?,
calls_started: register(
CounterVec::new(
@@ -83,7 +87,7 @@ impl RpcMetrics {
),
&["protocol", "method"],
)?,
r,
metrics_registry,
)?,
calls_finished: register(
CounterVec::new(
@@ -93,8 +97,24 @@ impl RpcMetrics {
),
&["protocol", "method", "is_error"],
)?,
r,
metrics_registry,
)?,
ws_sessions_opened: register(
Counter::new(
"substrate_rpc_sessions_opened",
"Number of persistent RPC sessions opened",
)?,
metrics_registry,
)?
.into(),
ws_sessions_closed: register(
Counter::new(
"substrate_rpc_sessions_closed",
"Number of persistent RPC sessions closed",
)?,
metrics_registry,
)?
.into(),
}))
} else {
Ok(None)
@@ -102,140 +122,71 @@ impl RpcMetrics {
}
}
/// Instantiates a dummy `IoHandler` given a builder function to extract supported method names.
pub fn method_names<F, M, E>(gen_handler: F) -> Result<HashSet<String>, E>
where
F: FnOnce(RpcMiddleware) -> Result<RpcHandler<M>, E>,
M: PubSubMetadata,
{
let io = gen_handler(RpcMiddleware::new(None, HashSet::new(), "dummy"))?;
Ok(io.iter().map(|x| x.0.clone()).collect())
}
#[derive(Clone)]
/// Middleware for RPC calls
pub struct RpcMiddleware {
metrics: Option<RpcMetrics>,
known_rpc_method_names: HashSet<String>,
transport_label: String,
metrics: RpcMetrics,
transport_label: &'static str,
}
impl RpcMiddleware {
/// Create an instance of middleware.
///
/// - `metrics`: Will be used to report statistics.
/// - `transport_label`: The label that is used when reporting the statistics.
pub fn new(
metrics: Option<RpcMetrics>,
known_rpc_method_names: HashSet<String>,
transport_label: &str,
) -> Self {
RpcMiddleware { metrics, known_rpc_method_names, transport_label: transport_label.into() }
/// Create a new [`RpcMiddleware`] with the provided [`RpcMetrics`].
pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self {
Self { metrics, transport_label }
}
}
impl<M: Metadata> RequestMiddleware<M> for RpcMiddleware {
type Future = FutureResponse;
type CallFuture = FutureOutput;
impl Middleware for RpcMiddleware {
type Instant = std::time::Instant;
fn on_request<F, X>(
&self,
request: jsonrpc_core::Request,
meta: M,
next: F,
) -> Either<Self::Future, X>
where
F: Fn(jsonrpc_core::Request, M) -> X + Send + Sync,
X: Future<Output = Option<jsonrpc_core::Response>> + Send + 'static,
{
let metrics = self.metrics.clone();
let transport_label = self.transport_label.clone();
if let Some(ref metrics) = metrics {
metrics.requests_started.with_label_values(&[transport_label.as_str()]).inc();
}
let r = next(request, meta);
Either::Left(
async move {
let r = r.await;
if let Some(ref metrics) = metrics {
metrics.requests_finished.with_label_values(&[transport_label.as_str()]).inc();
}
r
}
.boxed(),
)
fn on_connect(&self) {
self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc());
}
fn on_call<F, X>(
&self,
call: jsonrpc_core::Call,
meta: M,
next: F,
) -> Either<Self::CallFuture, X>
where
F: Fn(jsonrpc_core::Call, M) -> X + Send + Sync,
X: Future<Output = Option<jsonrpc_core::Output>> + Send + 'static,
{
let start = std::time::Instant::now();
let name = call_name(&call, &self.known_rpc_method_names).to_owned();
let metrics = self.metrics.clone();
let transport_label = self.transport_label.clone();
log::trace!(target: "rpc_metrics", "[{}] {} call: {:?}", transport_label, name, &call);
if let Some(ref metrics) = metrics {
metrics
.calls_started
.with_label_values(&[transport_label.as_str(), name.as_str()])
.inc();
}
let r = next(call, meta);
Either::Left(
async move {
let r = r.await;
let micros = start.elapsed().as_micros();
if let Some(ref metrics) = metrics {
metrics
.calls_time
.with_label_values(&[transport_label.as_str(), name.as_str()])
.observe(micros as _);
metrics
.calls_finished
.with_label_values(&[
transport_label.as_str(),
name.as_str(),
if is_success(&r) { "true" } else { "false" },
])
.inc();
}
log::debug!(
target: "rpc_metrics",
"[{}] {} call took {} μs",
transport_label,
name,
micros,
);
r
}
.boxed(),
)
fn on_request(&self) -> Self::Instant {
let now = std::time::Instant::now();
self.metrics.requests_started.with_label_values(&[self.transport_label]).inc();
now
}
fn on_call(&self, name: &str) {
log::trace!(target: "rpc_metrics", "[{}] on_call name={}", self.transport_label, name);
self.metrics
.calls_started
.with_label_values(&[self.transport_label, name])
.inc();
}
fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) {
let micros = started_at.elapsed().as_micros();
log::debug!(
target: "rpc_metrics",
"[{}] {} call took {} μs",
self.transport_label,
name,
micros,
);
self.metrics
.calls_time
.with_label_values(&[self.transport_label, name])
.observe(micros as _);
self.metrics
.calls_finished
.with_label_values(&[
self.transport_label,
name,
if success { "true" } else { "false" },
])
.inc();
}
fn on_response(&self, started_at: Self::Instant) {
log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at);
self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc();
}
fn on_disconnect(&self) {
self.metrics.ws_sessions_closed.as_ref().map(|counter| counter.inc());
}
}
fn call_name<'a>(call: &'a jsonrpc_core::Call, known_methods: &HashSet<String>) -> &'a str {
// To prevent bloating metric with all invalid method names we filter them out here.
let only_known = |method: &'a String| {
if known_methods.contains(method) {
method.as_str()
} else {
"invalid method"
}
};
match call {
jsonrpc_core::Call::Invalid { .. } => "invalid call",
jsonrpc_core::Call::MethodCall(ref call) => only_known(&call.method),
jsonrpc_core::Call::Notification(ref notification) => only_known(&notification.method),
}
}
fn is_success(output: &Option<jsonrpc_core::Output>) -> bool {
matches!(output, Some(jsonrpc_core::Output::Success(..)))
}
+7 -3
View File
@@ -16,11 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"]
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.21"
hash-db = { version = "0.15.2", default-features = false }
jsonrpc-pubsub = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server"] }
lazy_static = { version = "1.4.0", optional = true }
log = "0.4.16"
parking_lot = "0.12.0"
rpc = { package = "jsonrpc-core", version = "18.0.0" }
serde_json = "1.0.79"
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" }
@@ -39,14 +38,19 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" }
sp-session = { version = "4.0.0-dev", path = "../../primitives/session" }
sp-version = { version = "5.0.0", path = "../../primitives/version" }
tokio = { version = "1.17.0", optional = true }
[dev-dependencies]
env_logger = "0.9"
assert_matches = "1.3.0"
lazy_static = "1.4.0"
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
sc-network = { version = "0.10.0-dev", path = "../network" }
sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" }
sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" }
tokio = "1.17.0"
sp-io = { version = "6.0.0", path = "../../primitives/io" }
substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" }
[features]
test-helpers = ["lazy_static"]
test-helpers = ["lazy_static", "tokio"]
+54 -79
View File
@@ -23,26 +23,27 @@ mod tests;
use std::sync::Arc;
use sp_blockchain::HeaderBackend;
use crate::SubscriptionTaskExecutor;
use codec::{Decode, Encode};
use futures::{
future::{FutureExt, TryFutureExt},
SinkExt, StreamExt as _,
use futures::{FutureExt, TryFutureExt};
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
PendingSubscription,
};
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::{
error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool,
TransactionSource, TransactionStatus, TxHash,
TransactionSource, TxHash,
};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_core::Bytes;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{generic, traits::Block as BlockT};
use sp_session::SessionKeys;
use self::error::{Error, FutureResult, Result};
use self::error::{Error, Result};
/// Re-export the API for backward compatibility.
pub use sc_rpc_api::author::*;
@@ -52,12 +53,12 @@ pub struct Author<P, Client> {
client: Arc<Client>,
/// Transactions pool
pool: Arc<P>,
/// Subscriptions manager
subscriptions: SubscriptionManager,
/// The key store.
keystore: SyncCryptoStorePtr,
/// Whether to deny unsafe calls
deny_unsafe: DenyUnsafe,
/// Executor to spawn subscriptions.
executor: SubscriptionTaskExecutor,
}
impl<P, Client> Author<P, Client> {
@@ -65,11 +66,11 @@ impl<P, Client> Author<P, Client> {
pub fn new(
client: Arc<Client>,
pool: Arc<P>,
subscriptions: SubscriptionManager,
keystore: SyncCryptoStorePtr,
deny_unsafe: DenyUnsafe,
executor: SubscriptionTaskExecutor,
) -> Self {
Author { client, pool, subscriptions, keystore, deny_unsafe }
Author { client, pool, keystore, deny_unsafe, executor }
}
}
@@ -80,7 +81,8 @@ impl<P, Client> Author<P, Client> {
/// some unique transactions via RPC and have them included in the pool.
const TX_SOURCE: TransactionSource = TransactionSource::External;
impl<P, Client> AuthorApi<TxHash<P>, BlockHash<P>> for Author<P, Client>
#[async_trait]
impl<P, Client> AuthorApiServer<TxHash<P>, BlockHash<P>> for Author<P, Client>
where
P: TransactionPool + Sync + Send + 'static,
Client: HeaderBackend<P::Block> + ProvideRuntimeApi<P::Block> + Send + Sync + 'static,
@@ -88,9 +90,24 @@ where
P::Hash: Unpin,
<P::Block as BlockT>::Hash: Unpin,
{
type Metadata = crate::Metadata;
async fn submit_extrinsic(&self, ext: Bytes) -> RpcResult<TxHash<P>> {
let xt = match Decode::decode(&mut &ext[..]) {
Ok(xt) => xt,
Err(err) => return Err(Error::Client(Box::new(err)).into()),
};
let best_block_hash = self.client.info().best_hash;
self.pool
.submit_one(&generic::BlockId::hash(best_block_hash), TX_SOURCE, xt)
.await
.map_err(|e| {
e.into_pool_error()
.map(|e| Error::Pool(e))
.unwrap_or_else(|e| Error::Verification(Box::new(e)))
.into()
})
}
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> {
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
@@ -99,7 +116,7 @@ where
Ok(())
}
fn rotate_keys(&self) -> Result<Bytes> {
fn rotate_keys(&self) -> RpcResult<Bytes> {
self.deny_unsafe.check_if_safe()?;
let best_block_hash = self.client.info().best_hash;
@@ -107,10 +124,10 @@ where
.runtime_api()
.generate_session_keys(&generic::BlockId::Hash(best_block_hash), None)
.map(Into::into)
.map_err(|e| Error::Client(Box::new(e)))
.map_err(|api_err| Error::Client(Box::new(api_err)).into())
}
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool> {
fn has_session_keys(&self, session_keys: Bytes) -> RpcResult<bool> {
self.deny_unsafe.check_if_safe()?;
let best_block_hash = self.client.info().best_hash;
@@ -124,40 +141,22 @@ where
Ok(SyncCryptoStore::has_keys(&*self.keystore, &keys))
}
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool> {
fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult<bool> {
self.deny_unsafe.check_if_safe()?;
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
Ok(SyncCryptoStore::has_keys(&*self.keystore, &[(public_key.to_vec(), key_type)]))
}
fn submit_extrinsic(&self, ext: Bytes) -> FutureResult<TxHash<P>> {
let xt = match Decode::decode(&mut &ext[..]) {
Ok(xt) => xt,
Err(err) => return async move { Err(err.into()) }.boxed(),
};
let best_block_hash = self.client.info().best_hash;
self.pool
.submit_one(&generic::BlockId::hash(best_block_hash), TX_SOURCE, xt)
.map_err(|e| {
e.into_pool_error()
.map(Into::into)
.unwrap_or_else(|e| error::Error::Verification(Box::new(e)))
})
.boxed()
}
fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>> {
Ok(self.pool.ready().map(|tx| tx.data().encode().into()).collect())
}
fn remove_extrinsic(
&self,
bytes_or_hash: Vec<hash::ExtrinsicOrHash<TxHash<P>>>,
) -> Result<Vec<TxHash<P>>> {
) -> RpcResult<Vec<TxHash<P>>> {
self.deny_unsafe.check_if_safe()?;
let hashes = bytes_or_hash
.into_iter()
.map(|x| match x {
@@ -177,20 +176,12 @@ where
.collect())
}
fn watch_extrinsic(
&self,
_metadata: Self::Metadata,
subscriber: Subscriber<TransactionStatus<TxHash<P>, BlockHash<P>>>,
xt: Bytes,
) {
fn watch_extrinsic(&self, pending: PendingSubscription, xt: Bytes) {
let best_block_hash = self.client.info().best_hash;
let dxt = match TransactionFor::<P>::decode(&mut &xt[..]).map_err(error::Error::from) {
Ok(tx) => tx,
Err(err) => {
log::debug!("Failed to submit extrinsic: {}", err);
// reject the subscriber (ignore errors - we don't care if subscriber is no longer
// there).
let _ = subscriber.reject(err.into());
let dxt = match TransactionFor::<P>::decode(&mut &xt[..]).map_err(|e| Error::from(e)) {
Ok(dxt) => dxt,
Err(e) => {
pending.reject(JsonRpseeError::from(e));
return
},
};
@@ -204,41 +195,25 @@ where
.unwrap_or_else(|e| error::Error::Verification(Box::new(e)))
});
let subscriptions = self.subscriptions.clone();
let future = async move {
let tx_stream = match submit.await {
Ok(s) => s,
let fut = async move {
let stream = match submit.await {
Ok(stream) => stream,
Err(err) => {
log::debug!("Failed to submit extrinsic: {}", err);
// reject the subscriber (ignore errors - we don't care if subscriber is no
// longer there).
let _ = subscriber.reject(err.into());
pending.reject(JsonRpseeError::from(err));
return
},
};
subscriptions.add(subscriber, move |sink| {
tx_stream
.map(|v| Ok(Ok(v)))
.forward(
sink.sink_map_err(|e| log::debug!("Error sending notifications: {:?}", e)),
)
.map(drop)
});
};
let mut sink = match pending.accept() {
Some(sink) => sink,
_ => return,
};
let res = self.subscriptions.executor().spawn_obj(future.boxed().into());
if res.is_err() {
log::warn!("Error spawning subscription RPC task.");
sink.pipe_from_stream(stream).await;
}
}
.boxed();
fn unwatch_extrinsic(
&self,
_metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> Result<bool> {
Ok(self.subscriptions.cancel(id))
self.executor
.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
}
+196 -178
View File
@@ -18,21 +18,26 @@
use super::*;
use crate::testing::{test_executor, timeout_secs};
use assert_matches::assert_matches;
use codec::Encode;
use futures::executor;
use jsonrpsee::{
core::Error as RpcError,
types::{error::CallError, EmptyParams},
RpcModule,
};
use sc_transaction_pool::{BasicPool, FullChainApi};
use sc_transaction_pool_api::TransactionStatus;
use sp_core::{
blake2_256,
bytes::to_hex,
crypto::{ByteArray, CryptoTypePublicPair, Pair},
ed25519,
hexdisplay::HexDisplay,
sr25519,
ed25519, sr25519,
testing::{ED25519, SR25519},
H256,
};
use sp_keystore::testing::KeyStore;
use std::{mem, sync::Arc};
use std::sync::Arc;
use substrate_test_runtime_client::{
self,
runtime::{Block, Extrinsic, SessionKeys, Transfer},
@@ -75,240 +80,253 @@ impl TestSetup {
Author {
client: self.client.clone(),
pool: self.pool.clone(),
subscriptions: SubscriptionManager::new(Arc::new(crate::testing::TaskExecutor)),
keystore: self.keystore.clone(),
deny_unsafe: DenyUnsafe::No,
executor: test_executor(),
}
}
fn into_rpc() -> RpcModule<Author<FullTransactionPool, Client<Backend>>> {
Self::default().author().into_rpc()
}
}
#[test]
fn submit_transaction_should_not_cause_error() {
let p = TestSetup::default().author();
let xt = uxt(AccountKeyring::Alice, 1).encode();
let h: H256 = blake2_256(&xt).into();
#[tokio::test]
async fn author_submit_transaction_should_not_cause_error() {
let _ = env_logger::try_init();
let author = TestSetup::default().author();
let api = author.into_rpc();
let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into();
let extrinsic_hash: H256 = blake2_256(&xt).into();
let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap();
assert_eq!(response, extrinsic_hash);
assert_matches!(
executor::block_on(AuthorApi::submit_extrinsic(&p, xt.clone().into())),
Ok(h2) if h == h2
api.call::<_, H256>("author_submitExtrinsic", [xt]).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Already Imported") && err.code() == 1013
);
assert!(executor::block_on(AuthorApi::submit_extrinsic(&p, xt.into())).is_err());
}
#[test]
fn submit_rich_transaction_should_not_cause_error() {
let p = TestSetup::default().author();
let xt = uxt(AccountKeyring::Alice, 0).encode();
let h: H256 = blake2_256(&xt).into();
#[tokio::test]
async fn author_should_watch_extrinsic() {
let api = TestSetup::into_rpc();
let xt = to_hex(&uxt(AccountKeyring::Alice, 0).encode(), true);
assert_matches!(
executor::block_on(AuthorApi::submit_extrinsic(&p, xt.clone().into())),
Ok(h2) if h == h2
);
assert!(executor::block_on(AuthorApi::submit_extrinsic(&p, xt.into())).is_err());
}
let mut sub = api.subscribe("author_submitAndWatchExtrinsic", [xt]).await.unwrap();
let (tx, sub_id) = timeout_secs(10, sub.next::<TransactionStatus<H256, Block>>())
.await
.unwrap()
.unwrap()
.unwrap();
#[test]
fn should_watch_extrinsic() {
// given
let setup = TestSetup::default();
let p = setup.author();
assert_matches!(tx, TransactionStatus::Ready);
assert_eq!(&sub_id, sub.subscription_id());
let (subscriber, id_rx, data) = jsonrpc_pubsub::typed::Subscriber::new_test("test");
// when
p.watch_extrinsic(
Default::default(),
subscriber,
uxt(AccountKeyring::Alice, 0).encode().into(),
);
let id = executor::block_on(id_rx).unwrap().unwrap();
assert_matches!(id, SubscriptionId::String(_));
let id = match id {
SubscriptionId::String(id) => id,
_ => unreachable!(),
};
// check notifications
let replacement = {
// Replace the extrinsic and observe the subscription is notified.
let (xt_replacement, xt_hash) = {
let tx = Transfer {
amount: 5,
nonce: 0,
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Bob.into(),
};
tx.into_signed_tx()
let tx = tx.into_signed_tx().encode();
let hash = blake2_256(&tx);
(to_hex(&tx, true), hash)
};
executor::block_on(AuthorApi::submit_extrinsic(&p, replacement.encode().into())).unwrap();
let (res, data) = executor::block_on(data.into_future());
let expected = Some(format!(
r#"{{"jsonrpc":"2.0","method":"test","params":{{"result":"ready","subscription":"{}"}}}}"#,
id,
));
assert_eq!(res, expected);
let _ = api.call::<_, H256>("author_submitExtrinsic", [xt_replacement]).await.unwrap();
let h = blake2_256(&replacement.encode());
let expected = Some(format!(
r#"{{"jsonrpc":"2.0","method":"test","params":{{"result":{{"usurped":"0x{}"}},"subscription":"{}"}}}}"#,
HexDisplay::from(&h),
id,
));
let res = executor::block_on(data.into_future()).0;
assert_eq!(res, expected);
let (tx, sub_id) = timeout_secs(10, sub.next::<TransactionStatus<H256, Block>>())
.await
.unwrap()
.unwrap()
.unwrap();
assert_eq!(tx, TransactionStatus::Usurped(xt_hash.into()));
assert_eq!(&sub_id, sub.subscription_id());
}
#[test]
fn should_return_watch_validation_error() {
// given
let setup = TestSetup::default();
let p = setup.author();
#[tokio::test]
async fn author_should_return_watch_validation_error() {
const METHOD: &'static str = "author_submitAndWatchExtrinsic";
let (subscriber, id_rx, _data) = jsonrpc_pubsub::typed::Subscriber::new_test("test");
let api = TestSetup::into_rpc();
let failed_sub = api
.subscribe(METHOD, [to_hex(&uxt(AccountKeyring::Alice, 179).encode(), true)])
.await;
// when
p.watch_extrinsic(
Default::default(),
subscriber,
uxt(AccountKeyring::Alice, 179).encode().into(),
);
// then
let res = executor::block_on(id_rx).unwrap();
assert!(res.is_err(), "Expected the transaction to be rejected as invalid.");
}
#[test]
fn should_return_pending_extrinsics() {
let p = TestSetup::default().author();
let ex = uxt(AccountKeyring::Alice, 0);
executor::block_on(AuthorApi::submit_extrinsic(&p, ex.encode().into())).unwrap();
assert_matches!(
p.pending_extrinsics(),
Ok(ref expected) if *expected == vec![Bytes(ex.encode())]
failed_sub,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Invalid Transaction") && err.code() == 1010
);
}
#[test]
fn should_remove_extrinsics() {
let setup = TestSetup::default();
let p = setup.author();
#[tokio::test]
async fn author_should_return_pending_extrinsics() {
let api = TestSetup::into_rpc();
let ex1 = uxt(AccountKeyring::Alice, 0);
executor::block_on(p.submit_extrinsic(ex1.encode().into())).unwrap();
let ex2 = uxt(AccountKeyring::Alice, 1);
executor::block_on(p.submit_extrinsic(ex2.encode().into())).unwrap();
let ex3 = uxt(AccountKeyring::Bob, 0);
let hash3 = executor::block_on(p.submit_extrinsic(ex3.encode().into())).unwrap();
assert_eq!(setup.pool.status().ready, 3);
// now remove all 3
let removed = p
.remove_extrinsic(vec![
hash::ExtrinsicOrHash::Hash(hash3),
// Removing this one will also remove ex2
hash::ExtrinsicOrHash::Extrinsic(ex1.encode().into()),
])
let xt_bytes: Bytes = uxt(AccountKeyring::Alice, 0).encode().into();
api.call::<_, H256>("author_submitExtrinsic", [to_hex(&xt_bytes, true)])
.await
.unwrap();
assert_eq!(removed.len(), 3);
let pending: Vec<Bytes> =
api.call("author_pendingExtrinsics", EmptyParams::new()).await.unwrap();
assert_eq!(pending, vec![xt_bytes]);
}
#[test]
fn should_insert_key() {
#[tokio::test]
async fn author_should_remove_extrinsics() {
const METHOD: &'static str = "author_removeExtrinsic";
let setup = TestSetup::default();
let p = setup.author();
let api = setup.author().into_rpc();
// Submit three extrinsics, then remove two of them (will cause the third to be removed as well,
// having a higher nonce)
let xt1_bytes = uxt(AccountKeyring::Alice, 0).encode();
let xt1 = to_hex(&xt1_bytes, true);
let xt1_hash: H256 = api.call("author_submitExtrinsic", [xt1]).await.unwrap();
let xt2 = to_hex(&uxt(AccountKeyring::Alice, 1).encode(), true);
let xt2_hash: H256 = api.call("author_submitExtrinsic", [xt2]).await.unwrap();
let xt3 = to_hex(&uxt(AccountKeyring::Bob, 0).encode(), true);
let xt3_hash: H256 = api.call("author_submitExtrinsic", [xt3]).await.unwrap();
assert_eq!(setup.pool.status().ready, 3);
// Now remove all three.
// Notice how we need an extra `Vec` wrapping the `Vec` we want to submit as params.
let removed: Vec<H256> = api
.call(
METHOD,
vec![vec![
hash::ExtrinsicOrHash::Hash(xt3_hash),
// Removing this one will also remove xt2
hash::ExtrinsicOrHash::Extrinsic(xt1_bytes.into()),
]],
)
.await
.unwrap();
assert_eq!(removed, vec![xt1_hash, xt2_hash, xt3_hash]);
}
#[tokio::test]
async fn author_should_insert_key() {
let setup = TestSetup::default();
let api = setup.author().into_rpc();
let suri = "//Alice";
let key_pair = ed25519::Pair::from_string(suri, None).expect("Generates keypair");
p.insert_key(
let keypair = ed25519::Pair::from_string(suri, None).expect("generates keypair");
let params: (String, String, Bytes) = (
String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"),
suri.to_string(),
key_pair.public().0.to_vec().into(),
)
.expect("Insert key");
keypair.public().0.to_vec().into(),
);
api.call::<_, ()>("author_insertKey", params).await.unwrap();
let pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap();
let public_keys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap();
assert!(public_keys
.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, key_pair.public().to_raw_vec())));
assert!(
pubkeys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, keypair.public().to_raw_vec()))
);
}
#[test]
fn should_rotate_keys() {
#[tokio::test]
async fn author_should_rotate_keys() {
let setup = TestSetup::default();
let p = setup.author();
let new_public_keys = p.rotate_keys().expect("Rotates the keys");
let api = setup.author().into_rpc();
let new_pubkeys: Bytes = api.call("author_rotateKeys", EmptyParams::new()).await.unwrap();
let session_keys =
SessionKeys::decode(&mut &new_public_keys[..]).expect("SessionKeys decode successfully");
let ed25519_public_keys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap();
let sr25519_public_keys = SyncCryptoStore::keys(&*setup.keystore, SR25519).unwrap();
assert!(ed25519_public_keys
SessionKeys::decode(&mut &new_pubkeys[..]).expect("SessionKeys decode successfully");
let ed25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap();
let sr25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, SR25519).unwrap();
assert!(ed25519_pubkeys
.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, session_keys.ed25519.to_raw_vec())));
assert!(sr25519_public_keys
assert!(sr25519_pubkeys
.contains(&CryptoTypePublicPair(sr25519::CRYPTO_ID, session_keys.sr25519.to_raw_vec())));
}
#[test]
fn test_has_session_keys() {
let setup = TestSetup::default();
let p = setup.author();
#[tokio::test]
async fn author_has_session_keys() {
// Setup
let api = TestSetup::into_rpc();
let non_existent_public_keys =
TestSetup::default().author().rotate_keys().expect("Rotates the keys");
// Add a valid session key
let pubkeys: Bytes = api
.call("author_rotateKeys", EmptyParams::new())
.await
.expect("Rotates the keys");
let public_keys = p.rotate_keys().expect("Rotates the keys");
let test_vectors = vec![
(public_keys, Ok(true)),
(vec![1, 2, 3].into(), Err(Error::InvalidSessionKeys)),
(non_existent_public_keys, Ok(false)),
];
// Add a session key in a different keystore
let non_existent_pubkeys: Bytes = {
let api2 = TestSetup::default().author().into_rpc();
api2.call("author_rotateKeys", EmptyParams::new())
.await
.expect("Rotates the keys")
};
for (keys, result) in test_vectors {
assert_eq!(
result.map_err(|e| mem::discriminant(&e)),
p.has_session_keys(keys).map_err(|e| mem::discriminant(&e)),
);
}
// Then…
let existing = api.call::<_, bool>("author_hasSessionKeys", vec![pubkeys]).await.unwrap();
assert!(existing, "Existing key is in the session keys");
let inexistent = api
.call::<_, bool>("author_hasSessionKeys", vec![non_existent_pubkeys])
.await
.unwrap();
assert_eq!(inexistent, false, "Inexistent key is not in the session keys");
assert_matches!(
api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Session keys are not encoded correctly")
);
}
#[test]
fn test_has_key() {
let setup = TestSetup::default();
let p = setup.author();
#[tokio::test]
async fn author_has_key() {
let _ = env_logger::try_init();
let api = TestSetup::into_rpc();
let suri = "//Alice";
let alice_key_pair = ed25519::Pair::from_string(suri, None).expect("Generates keypair");
p.insert_key(
let alice_keypair = ed25519::Pair::from_string(suri, None).expect("Generates keypair");
let params = (
String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"),
suri.to_string(),
alice_key_pair.public().0.to_vec().into(),
)
.expect("Insert key");
let bob_key_pair = ed25519::Pair::from_string("//Bob", None).expect("Generates keypair");
Bytes::from(alice_keypair.public().0.to_vec()),
);
let test_vectors = vec![
(alice_key_pair.public().to_raw_vec().into(), ED25519, Ok(true)),
(alice_key_pair.public().to_raw_vec().into(), SR25519, Ok(false)),
(bob_key_pair.public().to_raw_vec().into(), ED25519, Ok(false)),
];
api.call::<_, ()>("author_insertKey", params).await.expect("insertKey works");
for (key, key_type, result) in test_vectors {
assert_eq!(
result.map_err(|e| mem::discriminant(&e)),
p.has_key(
key,
String::from_utf8(key_type.0.to_vec()).expect("Keytype is a valid string"),
)
.map_err(|e| mem::discriminant(&e)),
let bob_keypair = ed25519::Pair::from_string("//Bob", None).expect("Generates keypair");
// Alice's ED25519 key is there
let has_alice_ed: bool = {
let params = (
Bytes::from(alice_keypair.public().to_raw_vec()),
String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"),
);
}
api.call("author_hasKey", params).await.unwrap()
};
assert!(has_alice_ed);
// Alice's SR25519 key is not there
let has_alice_sr: bool = {
let params = (
Bytes::from(alice_keypair.public().to_raw_vec()),
String::from_utf8(SR25519.0.to_vec()).expect("Keytype is a valid string"),
);
api.call("author_hasKey", params).await.unwrap()
};
assert!(!has_alice_sr);
// Bob's ED25519 key is not there
let has_bob_ed: bool = {
let params = (
Bytes::from(bob_keypair.public().to_raw_vec()),
String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"),
);
api.call("author_hasKey", params).await.unwrap()
};
assert!(!has_bob_ed);
}
+98 -16
View File
@@ -18,34 +18,40 @@
//! Blockchain API backend for full nodes.
use super::{client_err, error::FutureResult, ChainBackend};
use futures::FutureExt;
use jsonrpc_pubsub::manager::SubscriptionManager;
use super::{client_err, ChainBackend, Error};
use crate::SubscriptionTaskExecutor;
use std::{marker::PhantomData, sync::Arc};
use futures::{
future::{self, FutureExt},
stream::{self, Stream, StreamExt},
};
use jsonrpsee::{core::async_trait, PendingSubscription};
use sc_client_api::{BlockBackend, BlockchainEvents};
use sp_blockchain::HeaderBackend;
use sp_runtime::{
generic::{BlockId, SignedBlock},
traits::Block as BlockT,
};
use std::{marker::PhantomData, sync::Arc};
/// Blockchain API backend for full nodes. Reads all the data from local database.
pub struct FullChain<Block: BlockT, Client> {
/// Substrate client.
client: Arc<Client>,
/// Current subscriptions.
subscriptions: SubscriptionManager,
/// phantom member to pin the block type
_phantom: PhantomData<Block>,
/// Subscription executor.
executor: SubscriptionTaskExecutor,
}
impl<Block: BlockT, Client> FullChain<Block, Client> {
/// Create new Chain API RPC handler.
pub fn new(client: Arc<Client>, subscriptions: SubscriptionManager) -> Self {
Self { client, subscriptions, _phantom: PhantomData }
pub fn new(client: Arc<Client>, executor: SubscriptionTaskExecutor) -> Self {
Self { client, executor, _phantom: PhantomData }
}
}
#[async_trait]
impl<Block, Client> ChainBackend<Client, Block> for FullChain<Block, Client>
where
Block: BlockT + 'static,
@@ -56,17 +62,93 @@ where
&self.client
}
fn subscriptions(&self) -> &SubscriptionManager {
&self.subscriptions
async fn header(&self, hash: Option<Block::Hash>) -> Result<Option<Block::Header>, Error> {
self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err)
}
fn header(&self, hash: Option<Block::Hash>) -> FutureResult<Option<Block::Header>> {
let res = self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err);
async move { res }.boxed()
async fn block(&self, hash: Option<Block::Hash>) -> Result<Option<SignedBlock<Block>>, Error> {
self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err)
}
fn block(&self, hash: Option<Block::Hash>) -> FutureResult<Option<SignedBlock<Block>>> {
let res = self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err);
async move { res }.boxed()
fn subscribe_all_heads(&self, sink: PendingSubscription) {
subscribe_headers(
&self.client,
&self.executor,
sink,
|| self.client().info().best_hash,
|| {
self.client()
.import_notification_stream()
.map(|notification| notification.header)
},
)
}
fn subscribe_new_heads(&self, sink: PendingSubscription) {
subscribe_headers(
&self.client,
&self.executor,
sink,
|| self.client().info().best_hash,
|| {
self.client()
.import_notification_stream()
.filter(|notification| future::ready(notification.is_new_best))
.map(|notification| notification.header)
},
)
}
fn subscribe_finalized_heads(&self, sink: PendingSubscription) {
subscribe_headers(
&self.client,
&self.executor,
sink,
|| self.client().info().finalized_hash,
|| {
self.client()
.finality_notification_stream()
.map(|notification| notification.header)
},
)
}
}
/// Subscribe to new headers.
fn subscribe_headers<Block, Client, F, G, S>(
client: &Arc<Client>,
executor: &SubscriptionTaskExecutor,
pending: PendingSubscription,
best_block_hash: G,
stream: F,
) where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: HeaderBackend<Block> + 'static,
F: FnOnce() -> S,
G: FnOnce() -> Block::Hash,
S: Stream<Item = Block::Header> + Send + Unpin + 'static,
{
// send current head right at the start.
let maybe_header = client
.header(BlockId::Hash(best_block_hash()))
.map_err(client_err)
.and_then(|header| header.ok_or_else(|| Error::Other("Best header missing.".into())))
.map_err(|e| log::warn!("Best header error {:?}", e))
.ok();
// NOTE: by the time we set up the stream there might be a new best block and so there is a risk
// that the stream has a hole in it. The alternative would be to look up the best block *after*
// we set up the stream and chain it to the stream. Consuming code would need to handle
// duplicates at the beginning of the stream though.
let stream = stream::iter(maybe_header).chain(stream());
let fut = async move {
if let Some(mut sink) = pending.accept() {
sink.pipe_from_stream(stream).await;
}
}
.boxed();
executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
+39 -182
View File
@@ -23,15 +23,14 @@ mod chain_full;
#[cfg(test)]
mod tests;
use futures::{future, StreamExt, TryStreamExt};
use log::warn;
use rpc::{
futures::{stream, FutureExt, SinkExt, Stream},
Result as RpcResult,
};
use std::sync::Arc;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use crate::SubscriptionTaskExecutor;
use jsonrpsee::{
core::{async_trait, RpcResult},
PendingSubscription,
};
use sc_client_api::BlockchainEvents;
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
use sp_runtime::{
@@ -39,13 +38,14 @@ use sp_runtime::{
traits::{Block as BlockT, Header, NumberFor},
};
use self::error::{Error, FutureResult, Result};
use self::error::Error;
use sc_client_api::BlockBackend;
pub use sc_rpc_api::chain::*;
use sp_blockchain::HeaderBackend;
/// Blockchain backend API
#[async_trait]
trait ChainBackend<Client, Block: BlockT>: Send + Sync + 'static
where
Block: BlockT + 'static,
@@ -55,9 +55,6 @@ where
/// Get client reference.
fn client(&self) -> &Arc<Client>;
/// Get subscriptions reference.
fn subscriptions(&self) -> &SubscriptionManager;
/// Tries to unwrap passed block hash, or uses best block hash otherwise.
fn unwrap_or_best(&self, hash: Option<Block::Hash>) -> Block::Hash {
match hash {
@@ -67,15 +64,15 @@ where
}
/// Get header of a relay chain block.
fn header(&self, hash: Option<Block::Hash>) -> FutureResult<Option<Block::Header>>;
async fn header(&self, hash: Option<Block::Hash>) -> Result<Option<Block::Header>, Error>;
/// Get header and body of a relay chain block.
fn block(&self, hash: Option<Block::Hash>) -> FutureResult<Option<SignedBlock<Block>>>;
async fn block(&self, hash: Option<Block::Hash>) -> Result<Option<SignedBlock<Block>>, Error>;
/// Get hash of the n-th block in the canon chain.
///
/// By default returns latest block hash.
fn block_hash(&self, number: Option<NumberOrHex>) -> Result<Option<Block::Hash>> {
fn block_hash(&self, number: Option<NumberOrHex>) -> Result<Option<Block::Hash>, Error> {
match number {
None => Ok(Some(self.client().info().best_hash)),
Some(num_or_hex) => {
@@ -97,107 +94,31 @@ where
}
/// Get hash of the last finalized block in the canon chain.
fn finalized_head(&self) -> Result<Block::Hash> {
fn finalized_head(&self) -> Result<Block::Hash, Error> {
Ok(self.client().info().finalized_hash)
}
/// All new head subscription
fn subscribe_all_heads(
&self,
_metadata: crate::Metadata,
subscriber: Subscriber<Block::Header>,
) {
subscribe_headers(
self.client(),
self.subscriptions(),
subscriber,
|| self.client().info().best_hash,
|| {
self.client()
.import_notification_stream()
.map(|notification| Ok::<_, rpc::Error>(notification.header))
},
)
}
/// Unsubscribe from all head subscription.
fn unsubscribe_all_heads(
&self,
_metadata: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions().cancel(id))
}
fn subscribe_all_heads(&self, sink: PendingSubscription);
/// New best head subscription
fn subscribe_new_heads(
&self,
_metadata: crate::Metadata,
subscriber: Subscriber<Block::Header>,
) {
subscribe_headers(
self.client(),
self.subscriptions(),
subscriber,
|| self.client().info().best_hash,
|| {
self.client()
.import_notification_stream()
.filter(|notification| future::ready(notification.is_new_best))
.map(|notification| Ok::<_, rpc::Error>(notification.header))
},
)
}
/// Unsubscribe from new best head subscription.
fn unsubscribe_new_heads(
&self,
_metadata: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions().cancel(id))
}
fn subscribe_new_heads(&self, sink: PendingSubscription);
/// Finalized head subscription
fn subscribe_finalized_heads(
&self,
_metadata: crate::Metadata,
subscriber: Subscriber<Block::Header>,
) {
subscribe_headers(
self.client(),
self.subscriptions(),
subscriber,
|| self.client().info().finalized_hash,
|| {
self.client()
.finality_notification_stream()
.map(|notification| Ok::<_, rpc::Error>(notification.header))
},
)
}
/// Unsubscribe from finalized head subscription.
fn unsubscribe_finalized_heads(
&self,
_metadata: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions().cancel(id))
}
fn subscribe_finalized_heads(&self, sink: PendingSubscription);
}
/// Create new state API that works on full node.
pub fn new_full<Block: BlockT, Client>(
client: Arc<Client>,
subscriptions: SubscriptionManager,
executor: SubscriptionTaskExecutor,
) -> Chain<Block, Client>
where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: BlockBackend<Block> + HeaderBackend<Block> + BlockchainEvents<Block> + 'static,
{
Chain { backend: Box::new(self::chain_full::FullChain::new(client, subscriptions)) }
Chain { backend: Box::new(self::chain_full::FullChain::new(client, executor)) }
}
/// Chain API with subscriptions support.
@@ -205,120 +126,56 @@ pub struct Chain<Block: BlockT, Client> {
backend: Box<dyn ChainBackend<Client, Block>>,
}
impl<Block, Client> ChainApi<NumberFor<Block>, Block::Hash, Block::Header, SignedBlock<Block>>
#[async_trait]
impl<Block, Client> ChainApiServer<NumberFor<Block>, Block::Hash, Block::Header, SignedBlock<Block>>
for Chain<Block, Client>
where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: HeaderBackend<Block> + BlockchainEvents<Block> + 'static,
{
type Metadata = crate::Metadata;
fn header(&self, hash: Option<Block::Hash>) -> FutureResult<Option<Block::Header>> {
self.backend.header(hash)
async fn header(&self, hash: Option<Block::Hash>) -> RpcResult<Option<Block::Header>> {
self.backend.header(hash).await.map_err(Into::into)
}
fn block(&self, hash: Option<Block::Hash>) -> FutureResult<Option<SignedBlock<Block>>> {
self.backend.block(hash)
async fn block(&self, hash: Option<Block::Hash>) -> RpcResult<Option<SignedBlock<Block>>> {
self.backend.block(hash).await.map_err(Into::into)
}
fn block_hash(
&self,
number: Option<ListOrValue<NumberOrHex>>,
) -> Result<ListOrValue<Option<Block::Hash>>> {
) -> RpcResult<ListOrValue<Option<Block::Hash>>> {
match number {
None => self.backend.block_hash(None).map(ListOrValue::Value),
Some(ListOrValue::Value(number)) =>
self.backend.block_hash(Some(number)).map(ListOrValue::Value),
None => self.backend.block_hash(None).map(ListOrValue::Value).map_err(Into::into),
Some(ListOrValue::Value(number)) => self
.backend
.block_hash(Some(number))
.map(ListOrValue::Value)
.map_err(Into::into),
Some(ListOrValue::List(list)) => Ok(ListOrValue::List(
list.into_iter()
.map(|number| self.backend.block_hash(Some(number)))
.collect::<Result<_>>()?,
.collect::<Result<_, _>>()?,
)),
}
}
fn finalized_head(&self) -> Result<Block::Hash> {
self.backend.finalized_head()
fn finalized_head(&self) -> RpcResult<Block::Hash> {
self.backend.finalized_head().map_err(Into::into)
}
fn subscribe_all_heads(&self, metadata: Self::Metadata, subscriber: Subscriber<Block::Header>) {
self.backend.subscribe_all_heads(metadata, subscriber)
fn subscribe_all_heads(&self, sink: PendingSubscription) {
self.backend.subscribe_all_heads(sink)
}
fn unsubscribe_all_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
self.backend.unsubscribe_all_heads(metadata, id)
fn subscribe_new_heads(&self, sink: PendingSubscription) {
self.backend.subscribe_new_heads(sink)
}
fn subscribe_new_heads(&self, metadata: Self::Metadata, subscriber: Subscriber<Block::Header>) {
self.backend.subscribe_new_heads(metadata, subscriber)
fn subscribe_finalized_heads(&self, sink: PendingSubscription) {
self.backend.subscribe_finalized_heads(sink)
}
fn unsubscribe_new_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
self.backend.unsubscribe_new_heads(metadata, id)
}
fn subscribe_finalized_heads(
&self,
metadata: Self::Metadata,
subscriber: Subscriber<Block::Header>,
) {
self.backend.subscribe_finalized_heads(metadata, subscriber)
}
fn unsubscribe_finalized_heads(
&self,
metadata: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
self.backend.unsubscribe_finalized_heads(metadata, id)
}
}
/// Subscribe to new headers.
fn subscribe_headers<Block, Client, F, G, S>(
client: &Arc<Client>,
subscriptions: &SubscriptionManager,
subscriber: Subscriber<Block::Header>,
best_block_hash: G,
stream: F,
) where
Block: BlockT + 'static,
Block::Header: Unpin,
Client: HeaderBackend<Block> + 'static,
F: FnOnce() -> S,
G: FnOnce() -> Block::Hash,
S: Stream<Item = std::result::Result<Block::Header, rpc::Error>> + Send + 'static,
{
subscriptions.add(subscriber, |sink| {
// send current head right at the start.
let header = client
.header(BlockId::Hash(best_block_hash()))
.map_err(client_err)
.and_then(|header| {
header.ok_or_else(|| Error::Other("Best header missing.".to_string()))
})
.map_err(Into::into);
// send further subscriptions
let stream = stream()
.inspect_err(|e| warn!("Block notification stream error: {:?}", e))
.map(Ok);
stream::iter(vec![Ok(header)])
.chain(stream)
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
// we ignore the resulting Stream (if the first stream is over we are unsubscribed)
.map(|_| ())
});
}
fn client_err(err: sp_blockchain::Error) -> Error {
+146 -149
View File
@@ -17,9 +17,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use crate::testing::TaskExecutor;
use crate::testing::{test_executor, timeout_secs};
use assert_matches::assert_matches;
use futures::executor;
use jsonrpsee::types::EmptyParams;
use sc_block_builder::BlockBuilderProvider;
use sp_consensus::BlockOrigin;
use sp_rpc::list::ListOrValue;
@@ -28,65 +28,92 @@ use substrate_test_runtime_client::{
runtime::{Block, Header, H256},
};
#[test]
fn should_return_header() {
#[tokio::test]
async fn should_return_header() {
let client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
let api = new_full(client.clone(), test_executor()).into_rpc();
assert_matches!(
executor::block_on(api.header(Some(client.genesis_hash()).into())),
Ok(Some(ref x)) if x == &Header {
let res: Header =
api.call("chain_getHeader", [H256::from(client.genesis_hash())]).await.unwrap();
assert_eq!(
res,
Header {
parent_hash: H256::from_low_u64_be(0),
number: 0,
state_root: x.state_root.clone(),
extrinsics_root:
"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(),
state_root: res.state_root.clone(),
extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
.parse()
.unwrap(),
digest: Default::default(),
}
);
let res: Header = api.call("chain_getHeader", EmptyParams::new()).await.unwrap();
assert_eq!(
res,
Header {
parent_hash: H256::from_low_u64_be(0),
number: 0,
state_root: res.state_root.clone(),
extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
.parse()
.unwrap(),
digest: Default::default(),
}
);
assert_matches!(
executor::block_on(api.header(None.into())),
Ok(Some(ref x)) if x == &Header {
parent_hash: H256::from_low_u64_be(0),
number: 0,
state_root: x.state_root.clone(),
extrinsics_root:
"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(),
digest: Default::default(),
}
);
assert_matches!(
executor::block_on(api.header(Some(H256::from_low_u64_be(5)).into())),
Ok(None)
api.call::<_, Option<Header>>("chain_getHeader", [H256::from_low_u64_be(5)])
.await
.unwrap(),
None
);
}
#[test]
fn should_return_a_block() {
#[tokio::test]
async fn should_return_a_block() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
let api = new_full(client.clone(), test_executor()).into_rpc();
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
let block_hash = block.hash();
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
client.import(BlockOrigin::Own, block).await.unwrap();
let res: SignedBlock<Block> =
api.call("chain_getBlock", [H256::from(client.genesis_hash())]).await.unwrap();
// Genesis block is not justified
assert_matches!(
executor::block_on(api.block(Some(client.genesis_hash()).into())),
Ok(Some(SignedBlock { justifications: None, .. }))
);
assert!(res.justifications.is_none());
assert_matches!(
executor::block_on(api.block(Some(block_hash).into())),
Ok(Some(ref x)) if x.block == Block {
let res: SignedBlock<Block> =
api.call("chain_getBlock", [H256::from(block_hash)]).await.unwrap();
assert_eq!(
res.block,
Block {
header: Header {
parent_hash: client.genesis_hash(),
number: 1,
state_root: x.block.header.state_root.clone(),
extrinsics_root:
"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(),
state_root: res.block.header.state_root.clone(),
extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
.parse()
.unwrap(),
digest: Default::default(),
},
extrinsics: vec![],
}
);
let res: SignedBlock<Block> = api.call("chain_getBlock", Vec::<H256>::new()).await.unwrap();
assert_eq!(
res.block,
Block {
header: Header {
parent_hash: client.genesis_hash(),
number: 1,
state_root: res.block.header.state_root.clone(),
extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
.parse()
.unwrap(),
digest: Default::default(),
},
extrinsics: vec![],
@@ -94,155 +121,125 @@ fn should_return_a_block() {
);
assert_matches!(
executor::block_on(api.block(None.into())),
Ok(Some(ref x)) if x.block == Block {
header: Header {
parent_hash: client.genesis_hash(),
number: 1,
state_root: x.block.header.state_root.clone(),
extrinsics_root:
"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(),
digest: Default::default(),
},
extrinsics: vec![],
}
api.call::<_, Option<Header>>("chain_getBlock", [H256::from_low_u64_be(5)])
.await
.unwrap(),
None
);
assert_matches!(executor::block_on(api.block(Some(H256::from_low_u64_be(5)).into())), Ok(None));
}
#[test]
fn should_return_block_hash() {
#[tokio::test]
async fn should_return_block_hash() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
let api = new_full(client.clone(), test_executor()).into_rpc();
let res: ListOrValue<Option<H256>> =
api.call("chain_getBlockHash", EmptyParams::new()).await.unwrap();
assert_matches!(
api.block_hash(None.into()),
Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash()
res,
ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash()
);
let res: ListOrValue<Option<H256>> =
api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap();
assert_matches!(
api.block_hash(Some(ListOrValue::Value(0u64.into())).into()),
Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash()
res,
ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash()
);
assert_matches!(
api.block_hash(Some(ListOrValue::Value(1u64.into())).into()),
Ok(ListOrValue::Value(None))
);
let res: Option<ListOrValue<Option<H256>>> =
api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap();
assert_matches!(res, None);
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap();
client.import(BlockOrigin::Own, block.clone()).await.unwrap();
let res: ListOrValue<Option<H256>> =
api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap();
assert_matches!(
api.block_hash(Some(ListOrValue::Value(0u64.into())).into()),
Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash()
);
assert_matches!(
api.block_hash(Some(ListOrValue::Value(1u64.into())).into()),
Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash()
);
assert_matches!(
api.block_hash(Some(ListOrValue::Value(sp_core::U256::from(1u64).into())).into()),
Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash()
res,
ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash()
);
let res: ListOrValue<Option<H256>> =
api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap();
assert_matches!(
api.block_hash(Some(vec![0u64.into(), 1u64.into(), 2u64.into()].into())),
Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None]
res,
ListOrValue::Value(Some(ref x)) if x == &block.hash()
);
let res: ListOrValue<Option<H256>> = api
.call("chain_getBlockHash", [ListOrValue::Value(sp_core::U256::from(1_u64))])
.await
.unwrap();
assert_matches!(
res,
ListOrValue::Value(Some(ref x)) if x == &block.hash()
);
let res: ListOrValue<Option<H256>> = api
.call("chain_getBlockHash", [ListOrValue::List(vec![0_u64, 1_u64, 2_u64])])
.await
.unwrap();
assert_matches!(
res,
ListOrValue::List(list) if list == &[client.genesis_hash().into(), block.hash().into(), None]
);
}
#[test]
fn should_return_finalized_hash() {
#[tokio::test]
async fn should_return_finalized_hash() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
let api = new_full(client.clone(), test_executor()).into_rpc();
assert_matches!(
api.finalized_head(),
Ok(ref x) if x == &client.genesis_hash()
);
let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap();
assert_eq!(res, client.genesis_hash());
// import new block
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
client.import(BlockOrigin::Own, block).await.unwrap();
// no finalization yet
assert_matches!(
api.finalized_head(),
Ok(ref x) if x == &client.genesis_hash()
);
let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap();
assert_eq!(res, client.genesis_hash());
// finalize
client.finalize_block(BlockId::number(1), None).unwrap();
assert_matches!(
api.finalized_head(),
Ok(ref x) if x == &client.block_hash(1).unwrap().unwrap()
);
let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap();
assert_eq!(res, client.block_hash(1).unwrap().unwrap());
}
#[test]
fn should_notify_about_latest_block() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
{
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
api.subscribe_all_heads(Default::default(), subscriber);
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
// Check for the correct number of notifications
executor::block_on((&mut transport).take(2).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
#[tokio::test]
async fn should_notify_about_latest_block() {
test_head_subscription("chain_subscribeAllHeads").await;
}
#[test]
fn should_notify_about_best_block() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
{
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
api.subscribe_new_heads(Default::default(), subscriber);
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
// Assert that the correct number of notifications have been sent.
executor::block_on((&mut transport).take(2).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
#[tokio::test]
async fn should_notify_about_best_block() {
test_head_subscription("chain_subscribeNewHeads").await;
}
#[test]
fn should_notify_about_finalized_block() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
#[tokio::test]
async fn should_notify_about_finalized_block() {
test_head_subscription("chain_subscribeFinalizedHeads").await;
}
{
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)));
api.subscribe_finalized_heads(Default::default(), subscriber);
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
async fn test_head_subscription(method: &str) {
let mut client = Arc::new(substrate_test_runtime_client::new());
let mut sub = {
let api = new_full(client.clone(), test_executor()).into_rpc();
let sub = api.subscribe(method, EmptyParams::new()).await.unwrap();
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
client.import(BlockOrigin::Own, block).await.unwrap();
client.finalize_block(BlockId::number(1), None).unwrap();
}
sub
};
// Assert that the correct number of notifications have been sent.
executor::block_on((&mut transport).take(2).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
assert_matches!(timeout_secs(10, sub.next::<Header>()).await, Ok(Some(_)));
assert_matches!(timeout_secs(10, sub.next::<Header>()).await, Ok(Some(_)));
sub.close();
assert_matches!(timeout_secs(10, sub.next::<Header>()).await, Ok(None));
}
+7 -9
View File
@@ -16,19 +16,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the [`DevApi`] trait providing debug utilities for Substrate based
//! Implementation of the [`DevApiServer`] trait providing debug utilities for Substrate based
//! blockchains.
#[cfg(test)]
mod tests;
pub use sc_rpc_api::dev::{BlockStats, DevApi};
use jsonrpsee::core::RpcResult;
use sc_client_api::{BlockBackend, HeaderBackend};
use sc_rpc_api::{
dev::error::{Error, Result},
DenyUnsafe,
};
use sc_rpc_api::{dev::error::Error, DenyUnsafe};
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
use sp_core::Encode;
use sp_runtime::{
@@ -40,6 +36,8 @@ use std::{
sync::Arc,
};
pub use sc_rpc_api::dev::{BlockStats, DevApiServer};
type HasherOf<Block> = <<Block as BlockT>::Header as Header>::Hashing;
/// The Dev API. All methods are unsafe.
@@ -56,7 +54,7 @@ impl<Block: BlockT, Client> Dev<Block, Client> {
}
}
impl<Block, Client> DevApi<Block::Hash> for Dev<Block, Client>
impl<Block, Client> DevApiServer<Block::Hash> for Dev<Block, Client>
where
Block: BlockT + 'static,
Client: BlockBackend<Block>
@@ -67,7 +65,7 @@ where
+ 'static,
Client::Api: Core<Block>,
{
fn block_stats(&self, hash: Block::Hash) -> Result<Option<BlockStats>> {
fn block_stats(&self, hash: Block::Hash) -> RpcResult<Option<BlockStats>> {
self.deny_unsafe.check_if_safe()?;
let block = {
+23 -12
View File
@@ -18,25 +18,32 @@
use super::*;
use assert_matches::assert_matches;
use futures::executor;
use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError};
use sc_block_builder::BlockBuilderProvider;
use sp_blockchain::HeaderBackend;
use sp_consensus::BlockOrigin;
use substrate_test_runtime_client::{prelude::*, runtime::Block};
#[test]
fn block_stats_work() {
#[tokio::test]
async fn block_stats_work() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::No);
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::No).into_rpc();
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
client.import(BlockOrigin::Own, block).await.unwrap();
// Can't gather stats for a block without a parent.
assert_eq!(api.block_stats(client.genesis_hash()).unwrap(), None);
assert_eq!(
api.call::<_, Option<BlockStats>>("dev_getBlockStats", [client.genesis_hash()])
.await
.unwrap(),
None
);
assert_eq!(
api.block_stats(client.info().best_hash).unwrap(),
api.call::<_, Option<BlockStats>>("dev_getBlockStats", [client.info().best_hash])
.await
.unwrap(),
Some(BlockStats {
witness_len: 597,
witness_compact_len: 500,
@@ -46,13 +53,17 @@ fn block_stats_work() {
);
}
#[test]
fn deny_unsafe_works() {
#[tokio::test]
async fn deny_unsafe_works() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::Yes);
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::Yes).into_rpc();
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
client.import(BlockOrigin::Own, block).await.unwrap();
assert_matches!(api.block_stats(client.info().best_hash), Err(Error::UnsafeRpcCalled(_)));
assert_matches!(
api.call::<_, Option<BlockStats>>("dev_getBlockStats", [client.info().best_hash])
.await,
Err(JsonRpseeError::Call(CallError::Custom(err))) if err.message().contains("RPC call is unsafe to be called externally")
);
}
+8 -29
View File
@@ -22,15 +22,14 @@
#![warn(missing_docs)]
use futures::{
task::{FutureObj, Spawn, SpawnError},
FutureExt,
pub use jsonrpsee::core::{
id_providers::{
RandomIntegerIdProvider as RandomIntegerSubscriptionId,
RandomStringIdProvider as RandomStringSubscriptionId,
},
traits::IdProvider as RpcSubscriptionIdProvider,
};
use sp_core::traits::SpawnNamed;
use std::sync::Arc;
pub use rpc::IoHandlerExtension as RpcExtension;
pub use sc_rpc_api::{DenyUnsafe, Metadata};
pub use sc_rpc_api::DenyUnsafe;
pub mod author;
pub mod chain;
@@ -43,24 +42,4 @@ pub mod system;
pub mod testing;
/// Task executor that is being used by RPC subscriptions.
#[derive(Clone)]
pub struct SubscriptionTaskExecutor(Arc<dyn SpawnNamed>);
impl SubscriptionTaskExecutor {
/// Create a new `Self` with the given spawner.
pub fn new(spawn: impl SpawnNamed + 'static) -> Self {
Self(Arc::new(spawn))
}
}
impl Spawn for SubscriptionTaskExecutor {
fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> {
self.0
.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed());
Ok(())
}
fn status(&self) -> Result<(), SpawnError> {
Ok(())
}
}
pub type SubscriptionTaskExecutor = std::sync::Arc<dyn sp_core::traits::SpawnNamed>;
+9 -8
View File
@@ -21,7 +21,8 @@
#[cfg(test)]
mod tests;
use self::error::{Error, Result};
use self::error::Error;
use jsonrpsee::core::{async_trait, Error as JsonRpseeError, RpcResult};
use parking_lot::RwLock;
/// Re-export the API for backward compatibility.
pub use sc_rpc_api::offchain::*;
@@ -47,27 +48,27 @@ 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<()> {
#[async_trait]
impl<T: OffchainStorage + 'static> OffchainApiServer for Offchain<T> {
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
let prefix = match kind {
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)),
};
self.storage.write().set(prefix, &*key, &*value);
Ok(())
}
/// Get offchain local storage under given key and prefix.
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>> {
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult<Option<Bytes>> {
self.deny_unsafe.check_if_safe()?;
let prefix = match kind {
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)),
};
Ok(self.storage.read().get(prefix, &*key).map(Into::into))
}
}
+7 -2
View File
@@ -39,6 +39,7 @@ fn local_storage_should_work() {
#[test]
fn offchain_calls_considered_unsafe() {
use jsonrpsee::types::error::CallError;
let storage = InMemOffchainStorage::default();
let offchain = Offchain::new(storage, DenyUnsafe::Yes);
let key = Bytes(b"offchain_storage".to_vec());
@@ -46,10 +47,14 @@ fn offchain_calls_considered_unsafe() {
assert_matches!(
offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()),
Err(Error::UnsafeRpcCalled(_))
Err(JsonRpseeError::Call(CallError::Custom(err))) => {
assert_eq!(err.message(), "RPC call is unsafe to be called externally")
}
);
assert_matches!(
offchain.get_local_storage(StorageKind::PERSISTENT, key),
Err(Error::UnsafeRpcCalled(_))
Err(JsonRpseeError::Call(CallError::Custom(err))) => {
assert_eq!(err.message(), "RPC call is unsafe to be called externally")
}
);
}
+182 -219
View File
@@ -23,11 +23,15 @@ mod state_full;
#[cfg(test)]
mod tests;
use futures::FutureExt;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use rpc::Result as RpcResult;
use std::sync::Arc;
use crate::SubscriptionTaskExecutor;
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
ws_server::PendingSubscription,
};
use sc_rpc_api::{state::ReadProof, DenyUnsafe};
use sp_core::{
storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey},
@@ -38,7 +42,7 @@ use sp_version::RuntimeVersion;
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
use self::error::{Error, FutureResult};
use self::error::Error;
use sc_client_api::{
Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider,
@@ -49,144 +53,122 @@ use sp_blockchain::{HeaderBackend, HeaderMetadata};
const STORAGE_KEYS_PAGED_MAX_COUNT: u32 = 1000;
/// State backend API.
#[async_trait]
pub trait StateBackend<Block: BlockT, Client>: Send + Sync + 'static
where
Block: BlockT + 'static,
Client: Send + Sync + 'static,
{
/// Call runtime method at given block.
fn call(
async fn call(
&self,
block: Option<Block::Hash>,
method: String,
call_data: Bytes,
) -> FutureResult<Bytes>;
) -> Result<Bytes, Error>;
/// Returns the keys with prefix, leave empty to get all the keys.
fn storage_keys(
async fn storage_keys(
&self,
block: Option<Block::Hash>,
prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>>;
) -> Result<Vec<StorageKey>, Error>;
/// Returns the keys with prefix along with their values, leave empty to get all the pairs.
fn storage_pairs(
async fn storage_pairs(
&self,
block: Option<Block::Hash>,
prefix: StorageKey,
) -> FutureResult<Vec<(StorageKey, StorageData)>>;
) -> Result<Vec<(StorageKey, StorageData)>, Error>;
/// Returns the keys with prefix with pagination support.
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
block: Option<Block::Hash>,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>>;
) -> Result<Vec<StorageKey>, Error>;
/// Returns a storage entry at a specific block's state.
fn storage(
async fn storage(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<StorageData>>;
) -> Result<Option<StorageData>, Error>;
/// Returns the hash of a storage entry at a block's state.
fn storage_hash(
async fn storage_hash(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>>;
) -> Result<Option<Block::Hash>, Error>;
/// Returns the size of a storage entry at a block's state.
///
/// If data is available at `key`, it is returned. Else, the sum of values who's key has `key`
/// prefix is returned, i.e. all the storage (double) maps that have this prefix.
fn storage_size(
async fn storage_size(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<u64>>;
) -> Result<Option<u64>, Error>;
/// Returns the runtime metadata as an opaque blob.
fn metadata(&self, block: Option<Block::Hash>) -> FutureResult<Bytes>;
async fn metadata(&self, block: Option<Block::Hash>) -> Result<Bytes, Error>;
/// Get the runtime version.
fn runtime_version(&self, block: Option<Block::Hash>) -> FutureResult<RuntimeVersion>;
async fn runtime_version(&self, block: Option<Block::Hash>) -> Result<RuntimeVersion, Error>;
/// Query historical storage entries (by key) starting from a block given as the second
/// parameter.
///
/// NOTE This first returned result contains the initial state of storage for all keys.
/// Subsequent values in the vector represent changes to the previous state (diffs).
fn query_storage(
async fn query_storage(
&self,
from: Block::Hash,
to: Option<Block::Hash>,
keys: Vec<StorageKey>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>>;
) -> Result<Vec<StorageChangeSet<Block::Hash>>, Error>;
/// Query storage entries (by key) starting at block hash given as the second parameter.
fn query_storage_at(
async fn query_storage_at(
&self,
keys: Vec<StorageKey>,
at: Option<Block::Hash>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>>;
) -> Result<Vec<StorageChangeSet<Block::Hash>>, Error>;
/// Returns proof of storage entries at a specific block's state.
fn read_proof(
async fn read_proof(
&self,
block: Option<Block::Hash>,
keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>>;
/// New runtime version subscription
fn subscribe_runtime_version(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<RuntimeVersion>,
);
/// Unsubscribe from runtime version subscription
fn unsubscribe_runtime_version(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
/// New storage subscription
fn subscribe_storage(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<StorageChangeSet<Block::Hash>>,
keys: Option<Vec<StorageKey>>,
);
/// Unsubscribe from storage subscription
fn unsubscribe_storage(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool>;
) -> Result<ReadProof<Block::Hash>, Error>;
/// Trace storage changes for block
fn trace_block(
async fn trace_block(
&self,
block: Block::Hash,
targets: Option<String>,
storage_keys: Option<String>,
methods: Option<String>,
) -> FutureResult<sp_rpc::tracing::TraceBlockResponse>;
) -> Result<sp_rpc::tracing::TraceBlockResponse, Error>;
/// New runtime version subscription
fn subscribe_runtime_version(&self, sink: PendingSubscription);
/// New storage subscription
fn subscribe_storage(&self, sink: PendingSubscription, keys: Option<Vec<StorageKey>>);
}
/// Create new state API that works on full node.
pub fn new_full<BE, Block: BlockT, Client>(
client: Arc<Client>,
subscriptions: SubscriptionManager,
executor: SubscriptionTaskExecutor,
deny_unsafe: DenyUnsafe,
rpc_max_payload: Option<usize>,
) -> (State<Block, Client>, ChildState<Block, Client>)
) -> (StateApi<Block, Client>, ChildState<Block, Client>)
where
Block: BlockT + 'static,
Block::Hash: Unpin,
@@ -207,168 +189,127 @@ where
{
let child_backend = Box::new(self::state_full::FullState::new(
client.clone(),
subscriptions.clone(),
executor.clone(),
rpc_max_payload,
));
let backend =
Box::new(self::state_full::FullState::new(client, subscriptions, rpc_max_payload));
(State { backend, deny_unsafe }, ChildState { backend: child_backend })
let backend = Box::new(self::state_full::FullState::new(client, executor, rpc_max_payload));
(StateApi { backend, deny_unsafe }, ChildState { backend: child_backend })
}
/// State API with subscriptions support.
pub struct State<Block, Client> {
pub struct StateApi<Block, Client> {
backend: Box<dyn StateBackend<Block, Client>>,
/// Whether to deny unsafe calls
deny_unsafe: DenyUnsafe,
}
impl<Block, Client> StateApi<Block::Hash> for State<Block, Client>
#[async_trait]
impl<Block, Client> StateApiServer<Block::Hash> for StateApi<Block, Client>
where
Block: BlockT + 'static,
Client: Send + Sync + 'static,
{
type Metadata = crate::Metadata;
fn call(&self, method: String, data: Bytes, block: Option<Block::Hash>) -> FutureResult<Bytes> {
self.backend.call(block, method, data)
async fn call(
&self,
method: String,
data: Bytes,
block: Option<Block::Hash>,
) -> RpcResult<Bytes> {
self.backend.call(block, method, data).await.map_err(Into::into)
}
fn storage_keys(
async fn storage_keys(
&self,
key_prefix: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Vec<StorageKey>> {
self.backend.storage_keys(block, key_prefix)
) -> RpcResult<Vec<StorageKey>> {
self.backend.storage_keys(block, key_prefix).await.map_err(Into::into)
}
fn storage_pairs(
async fn storage_pairs(
&self,
key_prefix: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Vec<(StorageKey, StorageData)>> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return async move { Err(err.into()) }.boxed()
}
self.backend.storage_pairs(block, key_prefix)
) -> RpcResult<Vec<(StorageKey, StorageData)>> {
self.deny_unsafe.check_if_safe()?;
self.backend.storage_pairs(block, key_prefix).await.map_err(Into::into)
}
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
block: Option<Block::Hash>,
) -> FutureResult<Vec<StorageKey>> {
) -> RpcResult<Vec<StorageKey>> {
if count > STORAGE_KEYS_PAGED_MAX_COUNT {
return async move {
Err(Error::InvalidCount { value: count, max: STORAGE_KEYS_PAGED_MAX_COUNT })
}
.boxed()
return Err(JsonRpseeError::from(Error::InvalidCount {
value: count,
max: STORAGE_KEYS_PAGED_MAX_COUNT,
}))
}
self.backend.storage_keys_paged(block, prefix, count, start_key)
self.backend
.storage_keys_paged(block, prefix, count, start_key)
.await
.map_err(Into::into)
}
fn storage(
async fn storage(
&self,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<StorageData>> {
self.backend.storage(block, key)
) -> RpcResult<Option<StorageData>> {
self.backend.storage(block, key).await.map_err(Into::into)
}
fn storage_hash(
async fn storage_hash(
&self,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<Block::Hash>> {
self.backend.storage_hash(block, key)
) -> RpcResult<Option<Block::Hash>> {
self.backend.storage_hash(block, key).await.map_err(Into::into)
}
fn storage_size(
async fn storage_size(
&self,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<u64>> {
self.backend.storage_size(block, key)
) -> RpcResult<Option<u64>> {
self.backend.storage_size(block, key).await.map_err(Into::into)
}
fn metadata(&self, block: Option<Block::Hash>) -> FutureResult<Bytes> {
self.backend.metadata(block)
async fn metadata(&self, block: Option<Block::Hash>) -> RpcResult<Bytes> {
self.backend.metadata(block).await.map_err(Into::into)
}
fn query_storage(
async fn runtime_version(&self, at: Option<Block::Hash>) -> RpcResult<RuntimeVersion> {
self.backend.runtime_version(at).await.map_err(Into::into)
}
async fn query_storage(
&self,
keys: Vec<StorageKey>,
from: Block::Hash,
to: Option<Block::Hash>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return async move { Err(err.into()) }.boxed()
}
self.backend.query_storage(from, to, keys)
) -> RpcResult<Vec<StorageChangeSet<Block::Hash>>> {
self.deny_unsafe.check_if_safe()?;
self.backend.query_storage(from, to, keys).await.map_err(Into::into)
}
fn query_storage_at(
async fn query_storage_at(
&self,
keys: Vec<StorageKey>,
at: Option<Block::Hash>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
self.backend.query_storage_at(keys, at)
) -> RpcResult<Vec<StorageChangeSet<Block::Hash>>> {
self.backend.query_storage_at(keys, at).await.map_err(Into::into)
}
fn read_proof(
async fn read_proof(
&self,
keys: Vec<StorageKey>,
block: Option<Block::Hash>,
) -> FutureResult<ReadProof<Block::Hash>> {
self.backend.read_proof(block, keys)
}
fn subscribe_storage(
&self,
meta: Self::Metadata,
subscriber: Subscriber<StorageChangeSet<Block::Hash>>,
keys: Option<Vec<StorageKey>>,
) {
if keys.is_none() {
if let Err(err) = self.deny_unsafe.check_if_safe() {
subscriber.reject(err.into())
.expect("subscription rejection can only fail if it's been already rejected, and we're rejecting it for the first time; qed");
return
}
}
self.backend.subscribe_storage(meta, subscriber, keys)
}
fn unsubscribe_storage(
&self,
meta: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
self.backend.unsubscribe_storage(meta, id)
}
fn runtime_version(&self, at: Option<Block::Hash>) -> FutureResult<RuntimeVersion> {
self.backend.runtime_version(at)
}
fn subscribe_runtime_version(
&self,
meta: Self::Metadata,
subscriber: Subscriber<RuntimeVersion>,
) {
self.backend.subscribe_runtime_version(meta, subscriber);
}
fn unsubscribe_runtime_version(
&self,
meta: Option<Self::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
self.backend.unsubscribe_runtime_version(meta, id)
) -> RpcResult<ReadProof<Block::Hash>> {
self.backend.read_proof(block, keys).await.map_err(Into::into)
}
/// Re-execute the given block with the tracing targets given in `targets`
@@ -376,88 +317,102 @@ where
///
/// Note: requires the node to run with `--rpc-methods=Unsafe`.
/// Note: requires runtimes compiled with wasm tracing support, `--features with-tracing`.
fn trace_block(
async fn trace_block(
&self,
block: Block::Hash,
targets: Option<String>,
storage_keys: Option<String>,
methods: Option<String>,
) -> FutureResult<sp_rpc::tracing::TraceBlockResponse> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return async move { Err(err.into()) }.boxed()
) -> RpcResult<sp_rpc::tracing::TraceBlockResponse> {
self.deny_unsafe.check_if_safe()?;
self.backend
.trace_block(block, targets, storage_keys, methods)
.await
.map_err(Into::into)
}
fn subscribe_runtime_version(&self, sink: PendingSubscription) {
self.backend.subscribe_runtime_version(sink)
}
fn subscribe_storage(&self, sink: PendingSubscription, keys: Option<Vec<StorageKey>>) {
if keys.is_none() {
if let Err(err) = self.deny_unsafe.check_if_safe() {
let _ = sink.reject(JsonRpseeError::from(err));
return
}
}
self.backend.trace_block(block, targets, storage_keys, methods)
self.backend.subscribe_storage(sink, keys)
}
}
/// Child state backend API.
#[async_trait]
pub trait ChildStateBackend<Block: BlockT, Client>: Send + Sync + 'static
where
Block: BlockT + 'static,
Client: Send + Sync + 'static,
{
/// Returns proof of storage for a child key entries at a specific block's state.
fn read_child_proof(
async fn read_child_proof(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>>;
) -> Result<ReadProof<Block::Hash>, Error>;
/// Returns the keys with prefix from a child storage,
/// leave prefix empty to get all the keys.
fn storage_keys(
async fn storage_keys(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>>;
) -> Result<Vec<StorageKey>, Error>;
/// Returns the keys with prefix from a child storage with pagination support.
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>>;
) -> Result<Vec<StorageKey>, Error>;
/// Returns a child storage entry at a specific block's state.
fn storage(
async fn storage(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<StorageData>>;
) -> Result<Option<StorageData>, Error>;
/// Returns child storage entries at a specific block's state.
fn storage_entries(
async fn storage_entries(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
) -> FutureResult<Vec<Option<StorageData>>>;
) -> Result<Vec<Option<StorageData>>, Error>;
/// Returns the hash of a child storage entry at a block's state.
fn storage_hash(
async fn storage_hash(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>>;
) -> Result<Option<Block::Hash>, Error>;
/// Returns the size of a child storage entry at a block's state.
fn storage_size(
async fn storage_size(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<u64>> {
self.storage(block, storage_key, key)
.map(|x| x.map(|r| r.map(|v| v.0.len() as u64)))
.boxed()
) -> Result<Option<u64>, Error> {
self.storage(block, storage_key, key).await.map(|x| x.map(|x| x.0.len() as u64))
}
}
@@ -466,76 +421,84 @@ pub struct ChildState<Block, Client> {
backend: Box<dyn ChildStateBackend<Block, Client>>,
}
impl<Block, Client> ChildStateApi<Block::Hash> for ChildState<Block, Client>
#[async_trait]
impl<Block, Client> ChildStateApiServer<Block::Hash> for ChildState<Block, Client>
where
Block: BlockT + 'static,
Client: Send + Sync + 'static,
{
type Metadata = crate::Metadata;
fn read_child_proof(
&self,
child_storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
block: Option<Block::Hash>,
) -> FutureResult<ReadProof<Block::Hash>> {
self.backend.read_child_proof(block, child_storage_key, keys)
}
fn storage(
&self,
storage_key: PrefixedStorageKey,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<StorageData>> {
self.backend.storage(block, storage_key, key)
}
fn storage_entries(
&self,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
block: Option<Block::Hash>,
) -> FutureResult<Vec<Option<StorageData>>> {
self.backend.storage_entries(block, storage_key, keys)
}
fn storage_keys(
async fn storage_keys(
&self,
storage_key: PrefixedStorageKey,
key_prefix: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Vec<StorageKey>> {
self.backend.storage_keys(block, storage_key, key_prefix)
) -> RpcResult<Vec<StorageKey>> {
self.backend
.storage_keys(block, storage_key, key_prefix)
.await
.map_err(Into::into)
}
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
storage_key: PrefixedStorageKey,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
block: Option<Block::Hash>,
) -> FutureResult<Vec<StorageKey>> {
self.backend.storage_keys_paged(block, storage_key, prefix, count, start_key)
) -> RpcResult<Vec<StorageKey>> {
self.backend
.storage_keys_paged(block, storage_key, prefix, count, start_key)
.await
.map_err(Into::into)
}
fn storage_hash(
async fn storage(
&self,
storage_key: PrefixedStorageKey,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<Block::Hash>> {
self.backend.storage_hash(block, storage_key, key)
) -> RpcResult<Option<StorageData>> {
self.backend.storage(block, storage_key, key).await.map_err(Into::into)
}
fn storage_size(
async fn storage_entries(
&self,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
block: Option<Block::Hash>,
) -> RpcResult<Vec<Option<StorageData>>> {
self.backend.storage_entries(block, storage_key, keys).await.map_err(Into::into)
}
async fn storage_hash(
&self,
storage_key: PrefixedStorageKey,
key: StorageKey,
block: Option<Block::Hash>,
) -> FutureResult<Option<u64>> {
self.backend.storage_size(block, storage_key, key)
) -> RpcResult<Option<Block::Hash>> {
self.backend.storage_hash(block, storage_key, key).await.map_err(Into::into)
}
async fn storage_size(
&self,
storage_key: PrefixedStorageKey,
key: StorageKey,
block: Option<Block::Hash>,
) -> RpcResult<Option<u64>> {
self.backend.storage_size(block, storage_key, key).await.map_err(Into::into)
}
async fn read_child_proof(
&self,
child_storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
block: Option<Block::Hash>,
) -> RpcResult<ReadProof<Block::Hash>> {
self.backend
.read_child_proof(block, child_storage_key, keys)
.await
.map_err(Into::into)
}
}
+185 -234
View File
@@ -18,17 +18,26 @@
//! State API backend for full nodes.
use futures::{
future,
future::{err, try_join_all},
stream, FutureExt, SinkExt, StreamExt,
};
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use log::warn;
use rpc::Result as RpcResult;
use std::{collections::HashMap, sync::Arc};
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use super::{
client_err,
error::{Error, Result},
ChildStateBackend, StateBackend,
};
use crate::SubscriptionTaskExecutor;
use futures::{future, stream, FutureExt, StreamExt};
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError},
PendingSubscription,
};
use sc_client_api::{
Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider,
StorageProvider,
};
use sc_rpc_api::state::ReadProof;
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
use sp_blockchain::{
CachedHeaderMetadata, Error as ClientError, HeaderBackend, HeaderMetadata,
Result as ClientResult,
@@ -42,19 +51,6 @@ use sp_core::{
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_version::RuntimeVersion;
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
use super::{
client_err,
error::{Error, FutureResult, Result},
ChildStateBackend, StateBackend,
};
use sc_client_api::{
Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider,
StorageNotification, StorageProvider,
};
use std::marker::PhantomData;
/// Ranges to query in state_queryStorage.
struct QueryStorageRange<Block: BlockT> {
/// Hashes of all the blocks in the range.
@@ -64,7 +60,7 @@ struct QueryStorageRange<Block: BlockT> {
/// State API backend for full nodes.
pub struct FullState<BE, Block: BlockT, Client> {
client: Arc<Client>,
subscriptions: SubscriptionManager,
executor: SubscriptionTaskExecutor,
_phantom: PhantomData<(BE, Block)>,
rpc_max_payload: Option<usize>,
}
@@ -81,10 +77,10 @@ where
/// Create new state API backend for full nodes.
pub fn new(
client: Arc<Client>,
subscriptions: SubscriptionManager,
executor: SubscriptionTaskExecutor,
rpc_max_payload: Option<usize>,
) -> Self {
Self { client, subscriptions, _phantom: PhantomData, rpc_max_payload }
Self { client, executor, _phantom: PhantomData, rpc_max_payload }
}
/// Returns given block hash or best block hash if None is passed.
@@ -174,6 +170,7 @@ where
}
}
#[async_trait]
impl<BE, Block, Client> StateBackend<Block, Client> for FullState<BE, Block, Client>
where
Block: BlockT + 'static,
@@ -193,14 +190,13 @@ where
+ 'static,
Client::Api: Metadata<Block>,
{
fn call(
async fn call(
&self,
block: Option<Block::Hash>,
method: String,
call_data: Bytes,
) -> FutureResult<Bytes> {
let r = self
.block_or_best(block)
) -> std::result::Result<Bytes, Error> {
self.block_or_best(block)
.and_then(|block| {
self.client
.executor()
@@ -213,43 +209,37 @@ where
)
.map(Into::into)
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_keys(
async fn storage_keys(
&self,
block: Option<Block::Hash>,
prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Vec<StorageKey>, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage_keys(&BlockId::Hash(block), &prefix))
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_pairs(
async fn storage_pairs(
&self,
block: Option<Block::Hash>,
prefix: StorageKey,
) -> FutureResult<Vec<(StorageKey, StorageData)>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Vec<(StorageKey, StorageData)>, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage_pairs(&BlockId::Hash(block), &prefix))
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
block: Option<Block::Hash>,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Vec<StorageKey>, Error> {
self.block_or_best(block)
.and_then(|block| {
self.client.storage_keys_iter(
&BlockId::Hash(block),
@@ -258,40 +248,36 @@ where
)
})
.map(|iter| iter.take(count as usize).collect())
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage(
async fn storage(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<StorageData>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Option<StorageData>, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage(&BlockId::Hash(block), &key))
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_size(
async fn storage_size(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<u64>> {
) -> std::result::Result<Option<u64>, Error> {
let block = match self.block_or_best(block) {
Ok(b) => b,
Err(e) => return async move { Err(client_err(e)) }.boxed(),
Err(e) => return Err(client_err(e)),
};
match self.client.storage(&BlockId::Hash(block), &key) {
Ok(Some(d)) => return async move { Ok(Some(d.0.len() as u64)) }.boxed(),
Err(e) => return async move { Err(client_err(e)) }.boxed(),
Ok(Some(d)) => return Ok(Some(d.0.len() as u64)),
Err(e) => return Err(client_err(e)),
Ok(None) => {},
}
let r = self
.client
self.client
.storage_pairs(&BlockId::Hash(block), &key)
.map(|kv| {
let item_sum = kv.iter().map(|(_, v)| v.0.len() as u64).sum::<u64>();
@@ -301,48 +287,46 @@ where
None
}
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_hash(
async fn storage_hash(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Option<Block::Hash>, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage_hash(&BlockId::Hash(block), &key))
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn metadata(&self, block: Option<Block::Hash>) -> FutureResult<Bytes> {
let r = self.block_or_best(block).map_err(client_err).and_then(|block| {
async fn metadata(&self, block: Option<Block::Hash>) -> std::result::Result<Bytes, Error> {
self.block_or_best(block).map_err(client_err).and_then(|block| {
self.client
.runtime_api()
.metadata(&BlockId::Hash(block))
.map(Into::into)
.map_err(|e| Error::Client(Box::new(e)))
});
async move { r }.boxed()
})
}
fn runtime_version(&self, block: Option<Block::Hash>) -> FutureResult<RuntimeVersion> {
let r = self.block_or_best(block).map_err(client_err).and_then(|block| {
async fn runtime_version(
&self,
block: Option<Block::Hash>,
) -> std::result::Result<RuntimeVersion, Error> {
self.block_or_best(block).map_err(client_err).and_then(|block| {
self.client
.runtime_version_at(&BlockId::Hash(block))
.map_err(|e| Error::Client(Box::new(e)))
});
async move { r }.boxed()
})
}
fn query_storage(
async fn query_storage(
&self,
from: Block::Hash,
to: Option<Block::Hash>,
keys: Vec<StorageKey>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
) -> std::result::Result<Vec<StorageChangeSet<Block::Hash>>, Error> {
let call_fn = move || {
let range = self.query_storage_range(from, to)?;
let mut changes = Vec::new();
@@ -350,168 +334,151 @@ where
self.query_storage_unfiltered(&range, &keys, &mut last_values, &mut changes)?;
Ok(changes)
};
let r = call_fn();
async move { r }.boxed()
call_fn()
}
fn query_storage_at(
async fn query_storage_at(
&self,
keys: Vec<StorageKey>,
at: Option<Block::Hash>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
) -> std::result::Result<Vec<StorageChangeSet<Block::Hash>>, Error> {
let at = at.unwrap_or_else(|| self.client.info().best_hash);
self.query_storage(at, Some(at), keys)
self.query_storage(at, Some(at), keys).await
}
fn read_proof(
async fn read_proof(
&self,
block: Option<Block::Hash>,
keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>> {
let r = self
.block_or_best(block)
) -> std::result::Result<ReadProof<Block::Hash>, Error> {
self.block_or_best(block)
.and_then(|block| {
self.client
.read_proof(&BlockId::Hash(block), &mut keys.iter().map(|key| key.0.as_ref()))
.map(|proof| proof.iter_nodes().map(|node| node.into()).collect())
.map(|proof| ReadProof { at: block, proof })
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn subscribe_runtime_version(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<RuntimeVersion>,
) {
self.subscriptions.add(subscriber, |sink| {
let version = self
.block_or_best(None)
.and_then(|block| {
self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into)
})
.map_err(client_err)
.map_err(Into::into);
fn subscribe_runtime_version(&self, pending: PendingSubscription) {
let client = self.client.clone();
let client = self.client.clone();
let mut previous_version = version.clone();
let initial = match self
.block_or_best(None)
.and_then(|block| {
self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into)
})
.map_err(|e| Error::Client(Box::new(e)))
{
Ok(initial) => initial,
Err(e) => {
pending.reject(JsonRpseeError::from(e));
return
},
};
// A stream of all best blocks.
let stream =
client.import_notification_stream().filter(|n| future::ready(n.is_new_best));
let mut previous_version = initial.clone();
let stream = stream.filter_map(move |n| {
// A stream of new versions
let version_stream = client
.import_notification_stream()
.filter(|n| future::ready(n.is_new_best))
.filter_map(move |n| {
let version = client
.runtime_version_at(&BlockId::hash(n.hash))
.map_err(|e| Error::Client(Box::new(e)))
.map_err(Into::into);
.map_err(|e| Error::Client(Box::new(e)));
if previous_version != version {
previous_version = version.clone();
future::ready(Some(Ok::<_, ()>(version)))
} else {
future::ready(None)
match version {
Ok(version) if version != previous_version => {
previous_version = version.clone();
future::ready(Some(version))
},
_ => future::ready(None),
}
});
stream::iter(vec![Ok(version)])
.chain(stream)
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
// we ignore the resulting Stream (if the first stream is over we are unsubscribed)
.map(|_| ())
});
let stream = futures::stream::once(future::ready(initial)).chain(version_stream);
let fut = async move {
if let Some(mut sink) = pending.accept() {
sink.pipe_from_stream(stream).await;
}
}
.boxed();
self.executor
.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
fn unsubscribe_runtime_version(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions.cancel(id))
}
fn subscribe_storage(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<StorageChangeSet<Block::Hash>>,
keys: Option<Vec<StorageKey>>,
) {
let keys = Into::<Option<Vec<_>>>::into(keys);
fn subscribe_storage(&self, pending: PendingSubscription, keys: Option<Vec<StorageKey>>) {
let stream = match self.client.storage_changes_notification_stream(keys.as_deref(), None) {
Ok(stream) => stream,
Err(err) => {
let _ = subscriber.reject(client_err(err).into());
Err(blockchain_err) => {
pending.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err))));
return
},
};
// initial values
let initial = stream::iter(
keys.map(|keys| {
let block = self.client.info().best_hash;
let changes = keys
.into_iter()
.map(|key| {
let v = self.client.storage(&BlockId::Hash(block), &key).ok().flatten();
(key, v)
})
.collect();
vec![Ok(Ok(StorageChangeSet { block, changes }))]
})
.unwrap_or_default(),
);
let initial = stream::iter(keys.map(|keys| {
let block = self.client.info().best_hash;
let changes = keys
.into_iter()
.map(|key| {
let v = self.client.storage(&BlockId::Hash(block), &key).ok().flatten();
(key, v)
})
.collect();
StorageChangeSet { block, changes }
}));
self.subscriptions.add(subscriber, |sink| {
let stream = stream.map(|StorageNotification { block, changes }| {
Ok(Ok::<_, rpc::Error>(StorageChangeSet {
block,
changes: changes
.iter()
.filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned())))
.collect(),
}))
});
initial
.chain(stream)
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
// we ignore the resulting Stream (if the first stream is over we are unsubscribed)
.map(|_| ())
// let storage_stream = stream.map(|(block, changes)| StorageChangeSet {
let storage_stream = stream.map(|storage_notif| StorageChangeSet {
block: storage_notif.block,
changes: storage_notif
.changes
.iter()
.filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned())))
.collect(),
});
let stream = initial
.chain(storage_stream)
.filter(|storage| future::ready(!storage.changes.is_empty()));
let fut = async move {
if let Some(mut sink) = pending.accept() {
sink.pipe_from_stream(stream).await;
}
}
.boxed();
self.executor
.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed());
}
fn unsubscribe_storage(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions.cancel(id))
}
fn trace_block(
async fn trace_block(
&self,
block: Block::Hash,
targets: Option<String>,
storage_keys: Option<String>,
methods: Option<String>,
) -> FutureResult<sp_rpc::tracing::TraceBlockResponse> {
let block_executor = sc_tracing::block::BlockExecutor::new(
) -> std::result::Result<sp_rpc::tracing::TraceBlockResponse, Error> {
sc_tracing::block::BlockExecutor::new(
self.client.clone(),
block,
targets,
storage_keys,
methods,
self.rpc_max_payload,
);
let r = block_executor
.trace_block()
.map_err(|e| invalid_block::<Block>(block, None, e.to_string()));
async move { r }.boxed()
)
.trace_block()
.map_err(|e| invalid_block::<Block>(block, None, e.to_string()))
}
}
#[async_trait]
impl<BE, Block, Client> ChildStateBackend<Block, Client> for FullState<BE, Block, Client>
where
Block: BlockT + 'static,
@@ -530,14 +497,13 @@ where
+ 'static,
Client::Api: Metadata<Block>,
{
fn read_child_proof(
async fn read_child_proof(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>> {
let r = self
.block_or_best(block)
) -> std::result::Result<ReadProof<Block::Hash>, Error> {
self.block_or_best(block)
.and_then(|block| {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
@@ -553,19 +519,16 @@ where
.map(|proof| proof.iter_nodes().map(|node| node.into()).collect())
.map(|proof| ReadProof { at: block, proof })
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_keys(
async fn storage_keys(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Vec<StorageKey>, Error> {
self.block_or_best(block)
.and_then(|block| {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
@@ -574,21 +537,18 @@ where
};
self.client.child_storage_keys(&BlockId::Hash(block), &child_info, &prefix)
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_keys_paged(
async fn storage_keys_paged(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
prefix: Option<StorageKey>,
count: u32,
start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Vec<StorageKey>, Error> {
self.block_or_best(block)
.and_then(|block| {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
@@ -603,19 +563,16 @@ where
)
})
.map(|iter| iter.take(count as usize).collect())
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage(
async fn storage(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<StorageData>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Option<StorageData>, Error> {
self.block_or_best(block)
.and_then(|block| {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
@@ -624,28 +581,25 @@ where
};
self.client.child_storage(&BlockId::Hash(block), &child_info, &key)
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
fn storage_entries(
async fn storage_entries(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
) -> FutureResult<Vec<Option<StorageData>>> {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
Arc::new(ChildInfo::new_default(storage_key)),
None => return err(client_err(sp_blockchain::Error::InvalidChildStorageKey)).boxed(),
};
let block = match self.block_or_best(block) {
Ok(b) => b,
Err(e) => return err(client_err(e)).boxed(),
) -> std::result::Result<Vec<Option<StorageData>>, Error> {
let child_info = if let Some((ChildType::ParentKeyId, storage_key)) =
ChildType::from_prefixed_key(&storage_key)
{
Arc::new(ChildInfo::new_default(storage_key))
} else {
return Err(client_err(sp_blockchain::Error::InvalidChildStorageKey))
};
let block = self.block_or_best(block).map_err(client_err)?;
let client = self.client.clone();
try_join_all(keys.into_iter().map(move |key| {
future::try_join_all(keys.into_iter().map(move |key| {
let res = client
.clone()
.child_storage(&BlockId::Hash(block), &child_info, &key)
@@ -653,17 +607,16 @@ where
async move { res }
}))
.boxed()
.await
}
fn storage_hash(
async fn storage_hash(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>> {
let r = self
.block_or_best(block)
) -> std::result::Result<Option<Block::Hash>, Error> {
self.block_or_best(block)
.and_then(|block| {
let child_info = match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
@@ -672,9 +625,7 @@ where
};
self.client.child_storage_hash(&BlockId::Hash(block), &child_info, &key)
})
.map_err(client_err);
async move { r }.boxed()
.map_err(client_err)
}
}
+204 -246
View File
@@ -18,9 +18,13 @@
use self::error::Error;
use super::*;
use crate::testing::TaskExecutor;
use crate::testing::{test_executor, timeout_secs};
use assert_matches::assert_matches;
use futures::{executor, StreamExt};
use futures::executor;
use jsonrpsee::{
core::Error as RpcError,
types::{error::CallError as RpcCallError, EmptyParams, ErrorObject},
};
use sc_block_builder::BlockBuilderProvider;
use sc_rpc_api::DenyUnsafe;
use sp_consensus::BlockOrigin;
@@ -36,8 +40,8 @@ fn prefixed_storage_key() -> PrefixedStorageKey {
child_info.prefixed_storage_key()
}
#[test]
fn should_return_storage() {
#[tokio::test]
async fn should_return_storage() {
const KEY: &[u8] = b":mock";
const VALUE: &[u8] = b"hello world";
const CHILD_VALUE: &[u8] = b"hello world !";
@@ -51,49 +55,46 @@ fn should_return_storage() {
.add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3])
.build();
let genesis_hash = client.genesis_hash();
let (client, child) = new_full(
Arc::new(client),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None);
let key = StorageKey(KEY.to_vec());
assert_eq!(
executor::block_on(client.storage(key.clone(), Some(genesis_hash).into()))
client
.storage(key.clone(), Some(genesis_hash).into())
.await
.map(|x| x.map(|x| x.0.len()))
.unwrap()
.unwrap() as usize,
VALUE.len(),
);
assert_matches!(
executor::block_on(client.storage_hash(key.clone(), Some(genesis_hash).into()))
client
.storage_hash(key.clone(), Some(genesis_hash).into())
.await
.map(|x| x.is_some()),
Ok(true)
);
assert_eq!(
executor::block_on(client.storage_size(key.clone(), None)).unwrap().unwrap() as usize,
client.storage_size(key.clone(), None).await.unwrap().unwrap() as usize,
VALUE.len(),
);
assert_eq!(
executor::block_on(client.storage_size(StorageKey(b":map".to_vec()), None))
.unwrap()
.unwrap() as usize,
client.storage_size(StorageKey(b":map".to_vec()), None).await.unwrap().unwrap() as usize,
2 + 3,
);
assert_eq!(
executor::block_on(
child
.storage(prefixed_storage_key(), key, Some(genesis_hash).into())
.map(|x| x.map(|x| x.unwrap().0.len()))
)
.unwrap() as usize,
child
.storage(prefixed_storage_key(), key, Some(genesis_hash).into())
.await
.map(|x| x.map(|x| x.0.len()))
.unwrap()
.unwrap() as usize,
CHILD_VALUE.len(),
);
}
#[test]
fn should_return_storage_entries() {
#[tokio::test]
async fn should_return_storage_entries() {
const KEY1: &[u8] = b":mock";
const KEY2: &[u8] = b":turtle";
const VALUE: &[u8] = b"hello world";
@@ -107,22 +108,15 @@ fn should_return_storage_entries() {
.add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec())
.build();
let genesis_hash = client.genesis_hash();
let (_client, child) = new_full(
Arc::new(client),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None);
let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())];
assert_eq!(
executor::block_on(child.storage_entries(
prefixed_storage_key(),
keys.to_vec(),
Some(genesis_hash).into()
))
.map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::<usize>())
.unwrap(),
child
.storage_entries(prefixed_storage_key(), keys.to_vec(), Some(genesis_hash).into())
.await
.map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::<usize>())
.unwrap(),
CHILD_VALUE1.len() + CHILD_VALUE2.len()
);
@@ -130,18 +124,16 @@ fn should_return_storage_entries() {
let mut failing_keys = vec![StorageKey(b":soup".to_vec())];
failing_keys.extend_from_slice(keys);
assert_matches!(
executor::block_on(child.storage_entries(
prefixed_storage_key(),
failing_keys,
Some(genesis_hash).into()
))
.map(|x| x.iter().all(|x| x.is_some())),
child
.storage_entries(prefixed_storage_key(), failing_keys, Some(genesis_hash).into())
.await
.map(|x| x.iter().all(|x| x.is_some())),
Ok(false)
);
}
#[test]
fn should_return_child_storage() {
#[tokio::test]
async fn should_return_child_storage() {
let child_info = ChildInfo::new_default(STORAGE_KEY);
let client = Arc::new(
substrate_test_runtime_client::TestClientBuilder::new()
@@ -149,49 +141,30 @@ fn should_return_child_storage() {
.build(),
);
let genesis_hash = client.genesis_hash();
let (_client, child) =
new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None);
let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None);
let child_key = prefixed_storage_key();
let key = StorageKey(b"key".to_vec());
assert_matches!(
executor::block_on(child.storage(
child.storage(
child_key.clone(),
key.clone(),
Some(genesis_hash).into(),
)),
).await,
Ok(Some(StorageData(ref d))) if d[0] == 42 && d.len() == 1
);
// should fail if key does not exist.
let failing_key = StorageKey(b":soup".to_vec());
assert_matches!(
executor::block_on(child.storage(
prefixed_storage_key(),
failing_key,
Some(genesis_hash).into()
))
.map(|x| x.is_some()),
Ok(false)
);
assert_matches!(
executor::block_on(child.storage_hash(
child_key.clone(),
key.clone(),
Some(genesis_hash).into(),
))
.map(|x| x.is_some()),
child
.storage_hash(child_key.clone(), key.clone(), Some(genesis_hash).into(),)
.await
.map(|x| x.is_some()),
Ok(true)
);
assert_matches!(
executor::block_on(child.storage_size(child_key.clone(), key.clone(), None)),
Ok(Some(1))
);
assert_matches!(child.storage_size(child_key.clone(), key.clone(), None).await, Ok(Some(1)));
}
#[test]
fn should_return_child_storage_entries() {
#[tokio::test]
async fn should_return_child_storage_entries() {
let child_info = ChildInfo::new_default(STORAGE_KEY);
let client = Arc::new(
substrate_test_runtime_client::TestClientBuilder::new()
@@ -200,17 +173,14 @@ fn should_return_child_storage_entries() {
.build(),
);
let genesis_hash = client.genesis_hash();
let (_client, child) =
new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None);
let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None);
let child_key = prefixed_storage_key();
let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())];
let res = executor::block_on(child.storage_entries(
child_key.clone(),
keys.clone(),
Some(genesis_hash).into(),
))
.unwrap();
let res = child
.storage_entries(child_key.clone(), keys.clone(), Some(genesis_hash).into())
.await
.unwrap();
assert_matches!(
res[0],
@@ -232,46 +202,37 @@ fn should_return_child_storage_entries() {
Ok(true)
);
assert_matches!(
executor::block_on(child.storage_size(child_key.clone(), keys[0].clone(), None)),
child.storage_size(child_key.clone(), keys[0].clone(), None).await,
Ok(Some(1))
);
}
#[test]
fn should_call_contract() {
#[tokio::test]
async fn should_call_contract() {
let client = Arc::new(substrate_test_runtime_client::new());
let genesis_hash = client.genesis_hash();
let (client, _child) =
new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None);
let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No, None);
use jsonrpsee::{core::Error, types::error::CallError};
assert_matches!(
executor::block_on(client.call(
"balanceOf".into(),
Bytes(vec![1, 2, 3]),
Some(genesis_hash).into()
)),
Err(Error::Client(_))
client
.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into())
.await,
Err(Error::Call(CallError::Failed(_)))
)
}
#[test]
fn should_notify_about_storage_changes() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
{
#[tokio::test]
async fn should_notify_about_storage_changes() {
let mut sub = {
let mut client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None);
api.subscribe_storage(Default::default(), subscriber, None.into());
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
let api_rpc = api.into_rpc();
let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap();
// Cause a change:
let mut builder = client.new_block(Default::default()).unwrap();
builder
.push_transfer(runtime::Transfer {
@@ -282,38 +243,32 @@ fn should_notify_about_storage_changes() {
})
.unwrap();
let block = builder.build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
client.import(BlockOrigin::Own, block).await.unwrap();
// Check notification sent to transport
executor::block_on((&mut transport).take(2).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
sub
};
// We should get a message back on our subscription about the storage change:
// NOTE: previous versions of the subscription code used to return an empty value for the
// "initial" storage change here
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
}
#[test]
fn should_send_initial_storage_changes_and_notifications() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
{
#[tokio::test]
async fn should_send_initial_storage_changes_and_notifications() {
let mut sub = {
let mut client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None);
let alice_balance_key =
blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into()));
api.subscribe_storage(
Default::default(),
subscriber,
Some(vec![StorageKey(alice_balance_key.to_vec())]).into(),
);
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
let api_rpc = api.into_rpc();
let sub = api_rpc
.subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key.to_vec())]])
.await
.unwrap();
let mut builder = client.new_block(Default::default()).unwrap();
builder
@@ -325,23 +280,22 @@ fn should_send_initial_storage_changes_and_notifications() {
})
.unwrap();
let block = builder.build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
client.import(BlockOrigin::Own, block).await.unwrap();
// Check for the correct number of notifications
executor::block_on((&mut transport).take(2).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
sub
};
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
// No more messages to follow
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
}
#[test]
fn should_query_storage() {
fn run_tests(mut client: Arc<TestClient>) {
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
#[tokio::test]
async fn should_query_storage() {
async fn run_tests(mut client: Arc<TestClient>) {
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None);
let mut add_block = |nonce| {
let mut builder = client.new_block(Default::default()).unwrap();
@@ -393,7 +347,7 @@ fn should_query_storage() {
let keys = (1..6).map(|k| StorageKey(vec![k])).collect::<Vec<_>>();
let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into());
assert_eq!(executor::block_on(result).unwrap(), expected);
assert_eq!(result.await.unwrap(), expected);
// Query all changes
let result = api.query_storage(keys.clone(), genesis_hash, None.into());
@@ -406,23 +360,28 @@ fn should_query_storage() {
(StorageKey(vec![5]), Some(StorageData(vec![1]))),
],
});
assert_eq!(executor::block_on(result).unwrap(), expected);
assert_eq!(result.await.unwrap(), expected);
// Query changes up to block2.
let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash));
assert_eq!(executor::block_on(result).unwrap(), expected);
assert_eq!(result.await.unwrap(), expected);
// Inverted range.
let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash));
assert_eq!(
executor::block_on(result).map_err(|e| e.to_string()),
Err(Error::InvalidBlockRange {
from: format!("1 ({:?})", block1_hash),
to: format!("0 ({:?})", genesis_hash),
details: "from number > to number".to_owned(),
})
result.await.map_err(|e| e.to_string()),
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
4001,
Error::InvalidBlockRange {
from: format!("1 ({:?})", block1_hash),
to: format!("0 ({:?})", genesis_hash),
details: "from number > to number".to_owned(),
}
.to_string(),
None::<()>,
))))
.map_err(|e| e.to_string())
);
@@ -433,15 +392,20 @@ fn should_query_storage() {
let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1));
assert_eq!(
executor::block_on(result).map_err(|e| e.to_string()),
Err(Error::InvalidBlockRange {
from: format!("{:?}", genesis_hash),
to: format!("{:?}", Some(random_hash1)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
})
result.await.map_err(|e| e.to_string()),
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
4001,
Error::InvalidBlockRange {
from: format!("{:?}", genesis_hash),
to: format!("{:?}", Some(random_hash1)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
}
.to_string(),
None::<()>,
))))
.map_err(|e| e.to_string())
);
@@ -449,15 +413,20 @@ fn should_query_storage() {
let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash));
assert_eq!(
executor::block_on(result).map_err(|e| e.to_string()),
Err(Error::InvalidBlockRange {
from: format!("{:?}", random_hash1),
to: format!("{:?}", Some(genesis_hash)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
})
result.await.map_err(|e| e.to_string()),
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
4001,
Error::InvalidBlockRange {
from: format!("{:?}", random_hash1),
to: format!("{:?}", Some(genesis_hash)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
}
.to_string(),
None::<()>,
))))
.map_err(|e| e.to_string()),
);
@@ -465,15 +434,20 @@ fn should_query_storage() {
let result = api.query_storage(keys.clone(), random_hash1, None);
assert_eq!(
executor::block_on(result).map_err(|e| e.to_string()),
Err(Error::InvalidBlockRange {
from: format!("{:?}", random_hash1),
to: format!("{:?}", Some(block2_hash)), // Best block hash.
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
})
result.await.map_err(|e| e.to_string()),
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
4001,
Error::InvalidBlockRange {
from: format!("{:?}", random_hash1),
to: format!("{:?}", Some(block2_hash)), // Best block hash.
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
}
.to_string(),
None::<()>,
))))
.map_err(|e| e.to_string()),
);
@@ -481,15 +455,20 @@ fn should_query_storage() {
let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2));
assert_eq!(
executor::block_on(result).map_err(|e| e.to_string()),
Err(Error::InvalidBlockRange {
from: format!("{:?}", random_hash1), // First hash not found.
to: format!("{:?}", Some(random_hash2)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
})
result.await.map_err(|e| e.to_string()),
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
4001,
Error::InvalidBlockRange {
from: format!("{:?}", random_hash1), // First hash not found.
to: format!("{:?}", Some(random_hash2)),
details: format!(
"UnknownBlock: Header was not found in the database: {:?}",
random_hash1
),
}
.to_string(),
None::<()>
))))
.map_err(|e| e.to_string()),
);
@@ -497,7 +476,7 @@ fn should_query_storage() {
let result = api.query_storage_at(keys.clone(), Some(block1_hash));
assert_eq!(
executor::block_on(result).unwrap(),
result.await.unwrap(),
vec![StorageChangeSet {
block: block1_hash,
changes: vec![
@@ -511,19 +490,14 @@ fn should_query_storage() {
);
}
run_tests(Arc::new(substrate_test_runtime_client::new()));
run_tests(Arc::new(TestClientBuilder::new().build()));
run_tests(Arc::new(substrate_test_runtime_client::new())).await;
run_tests(Arc::new(TestClientBuilder::new().build())).await;
}
#[test]
fn should_return_runtime_version() {
#[tokio::test]
async fn should_return_runtime_version() {
let client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None);
let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\
@@ -532,7 +506,7 @@ fn should_return_runtime_version() {
[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\
\"transactionVersion\":1,\"stateVersion\":1}";
let runtime_version = executor::block_on(api.runtime_version(None.into())).unwrap();
let runtime_version = api.runtime_version(None.into()).await.unwrap();
let serialized = serde_json::to_string(&runtime_version).unwrap();
assert_eq!(serialized, result);
@@ -540,28 +514,26 @@ fn should_return_runtime_version() {
assert_eq!(deserialized, runtime_version);
}
#[test]
fn should_notify_on_runtime_version_initially() {
let (subscriber, id, mut transport) = Subscriber::new_test("test");
{
#[tokio::test]
async fn should_notify_on_runtime_version_initially() {
let mut sub = {
let client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::No,
None,
);
let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No, None);
api.subscribe_runtime_version(Default::default(), subscriber);
let api_rpc = api.into_rpc();
let sub = api_rpc
.subscribe("state_subscribeRuntimeVersion", EmptyParams::new())
.await
.unwrap();
// assert id assigned
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
}
sub
};
// assert initial version sent.
executor::block_on((&mut transport).take(1).collect::<Vec<_>>());
assert!(executor::block_on(transport.next()).is_none());
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(Some(_)));
sub.close();
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(None));
}
#[test]
@@ -572,38 +544,24 @@ fn should_deserialize_storage_key() {
assert_eq!(k.0.len(), 32);
}
#[test]
fn wildcard_storage_subscriptions_are_rpc_unsafe() {
let (subscriber, id, _) = Subscriber::new_test("test");
#[tokio::test]
async fn wildcard_storage_subscriptions_are_rpc_unsafe() {
let client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::Yes,
None,
);
let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None);
api.subscribe_storage(Default::default(), subscriber, None.into());
let error = executor::block_on(id).unwrap().unwrap_err();
assert_eq!(error.to_string(), "Method not found: RPC call is unsafe to be called externally");
let api_rpc = api.into_rpc();
let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await;
assert_matches!(err, Err(RpcError::Call(RpcCallError::Custom(e))) if e.message() == "RPC call is unsafe to be called externally");
}
#[test]
fn concrete_storage_subscriptions_are_rpc_safe() {
let (subscriber, id, _) = Subscriber::new_test("test");
#[tokio::test]
async fn concrete_storage_subscriptions_are_rpc_safe() {
let client = Arc::new(substrate_test_runtime_client::new());
let (api, _child) = new_full(
client.clone(),
SubscriptionManager::new(Arc::new(TaskExecutor)),
DenyUnsafe::Yes,
None,
);
let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None);
let api_rpc = api.into_rpc();
let key = StorageKey(STORAGE_KEY.to_vec());
api.subscribe_storage(Default::default(), subscriber, Some(vec![key]));
let sub = api_rpc.subscribe("state_subscribeStorage", [[key]]).await;
assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_)))));
assert!(sub.is_ok());
}
+68 -73
View File
@@ -18,31 +18,24 @@
//! Substrate system API.
use self::error::Result;
use futures::{channel::oneshot, FutureExt};
use sc_rpc_api::{DenyUnsafe, Receiver};
#[cfg(test)]
mod tests;
use futures::channel::oneshot;
use jsonrpsee::{
core::{async_trait, error::Error as JsonRpseeError, JsonValue, RpcResult},
types::error::{CallError, ErrorCode, ErrorObject},
};
use sc_rpc_api::DenyUnsafe;
use sc_tracing::logging;
use sc_utils::mpsc::TracingUnboundedSender;
use sp_runtime::traits::{self, Header as HeaderT};
pub use self::{
gen_client::Client as SystemClient,
helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo},
};
use self::error::Result;
pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo};
pub use sc_rpc_api::system::*;
#[cfg(test)]
mod tests;
/// Early exit for RPCs that require `--rpc-methods=Unsafe` to be enabled
macro_rules! bail_if_unsafe {
($value: expr) => {
if let Err(err) = $value.check_if_safe() {
return async move { Err(err.into()) }.boxed()
}
};
}
/// System API implementation
pub struct System<B: traits::Block> {
info: SystemInfo,
@@ -62,7 +55,7 @@ pub enum Request<B: traits::Block> {
/// Must return information about the peers we are connected to.
Peers(oneshot::Sender<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>>),
/// Must return the state of the network.
NetworkState(oneshot::Sender<rpc::Value>),
NetworkState(oneshot::Sender<serde_json::Value>),
/// Must return any potential parse error.
NetworkAddReservedPeer(String, oneshot::Sender<Result<()>>),
/// Must return any potential parse error.
@@ -89,121 +82,123 @@ impl<B: traits::Block> System<B> {
}
}
impl<B: traits::Block> SystemApi<B::Hash, <B::Header as HeaderT>::Number> for System<B> {
fn system_name(&self) -> Result<String> {
#[async_trait]
impl<B: traits::Block> SystemApiServer<B::Hash, <B::Header as HeaderT>::Number> for System<B> {
fn system_name(&self) -> RpcResult<String> {
Ok(self.info.impl_name.clone())
}
fn system_version(&self) -> Result<String> {
fn system_version(&self) -> RpcResult<String> {
Ok(self.info.impl_version.clone())
}
fn system_chain(&self) -> Result<String> {
fn system_chain(&self) -> RpcResult<String> {
Ok(self.info.chain_name.clone())
}
fn system_type(&self) -> Result<sc_chain_spec::ChainType> {
fn system_type(&self) -> RpcResult<sc_chain_spec::ChainType> {
Ok(self.info.chain_type.clone())
}
fn system_properties(&self) -> Result<sc_chain_spec::Properties> {
fn system_properties(&self) -> RpcResult<sc_chain_spec::Properties> {
Ok(self.info.properties.clone())
}
fn system_health(&self) -> Receiver<Health> {
async fn system_health(&self) -> RpcResult<Health> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::Health(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_local_peer_id(&self) -> Receiver<String> {
async fn system_local_peer_id(&self) -> RpcResult<String> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::LocalPeerId(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_local_listen_addresses(&self) -> Receiver<Vec<String>> {
async fn system_local_listen_addresses(&self) -> RpcResult<Vec<String>> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::LocalListenAddresses(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_peers(
async fn system_peers(
&self,
) -> rpc::BoxFuture<rpc::Result<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>>> {
bail_if_unsafe!(self.deny_unsafe);
) -> RpcResult<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>> {
self.deny_unsafe.check_if_safe()?;
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::Peers(tx));
async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed()
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_network_state(&self) -> rpc::BoxFuture<rpc::Result<rpc::Value>> {
bail_if_unsafe!(self.deny_unsafe);
async fn system_network_state(&self) -> RpcResult<JsonValue> {
self.deny_unsafe.check_if_safe()?;
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::NetworkState(tx));
async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed()
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_add_reserved_peer(&self, peer: String) -> rpc::BoxFuture<rpc::Result<()>> {
bail_if_unsafe!(self.deny_unsafe);
async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx));
async move {
match rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(rpc::Error::from(e)),
Err(_) => Err(rpc::Error::internal_error()),
}
match rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(JsonRpseeError::from(e)),
Err(e) => Err(JsonRpseeError::to_call_error(e)),
}
.boxed()
}
fn system_remove_reserved_peer(&self, peer: String) -> rpc::BoxFuture<rpc::Result<()>> {
bail_if_unsafe!(self.deny_unsafe);
async fn system_remove_reserved_peer(&self, peer: String) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx));
async move {
match rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(rpc::Error::from(e)),
Err(_) => Err(rpc::Error::internal_error()),
}
match rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(JsonRpseeError::from(e)),
Err(e) => Err(JsonRpseeError::to_call_error(e)),
}
.boxed()
}
fn system_reserved_peers(&self) -> Receiver<Vec<String>> {
async fn system_reserved_peers(&self) -> RpcResult<Vec<String>> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::NetworkReservedPeers(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_node_roles(&self) -> Receiver<Vec<NodeRole>> {
async fn system_node_roles(&self) -> RpcResult<Vec<NodeRole>> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::NodeRoles(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_sync_state(&self) -> Receiver<SyncState<<B::Header as HeaderT>::Number>> {
async fn system_sync_state(&self) -> RpcResult<SyncState<<B::Header as HeaderT>::Number>> {
let (tx, rx) = oneshot::channel();
let _ = self.send_back.unbounded_send(Request::SyncState(tx));
Receiver(rx)
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
}
fn system_add_log_filter(&self, directives: String) -> rpc::Result<()> {
fn system_add_log_filter(&self, directives: String) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
logging::add_directives(&directives);
logging::reload_filter().map_err(|_e| rpc::Error::internal_error())
logging::reload_filter().map_err(|e| {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InternalError.code(),
e,
None::<()>,
)))
})
}
fn system_reset_log_filter(&self) -> rpc::Result<()> {
fn system_reset_log_filter(&self) -> RpcResult<()> {
self.deny_unsafe.check_if_safe()?;
logging::reset_log_filter().map_err(|_e| rpc::Error::internal_error())
logging::reset_log_filter().map_err(|e| {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InternalError.code(),
e,
None::<()>,
)))
})
}
}
+163 -117
View File
@@ -16,12 +16,18 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use super::{helpers::SyncState, *};
use assert_matches::assert_matches;
use futures::{executor, prelude::*};
use futures::prelude::*;
use jsonrpsee::{
core::Error as RpcError,
types::{error::CallError, EmptyParams},
RpcModule,
};
use sc_network::{self, config::Role, PeerId};
use sc_rpc_api::system::helpers::PeerInfo;
use sc_utils::mpsc::tracing_unbounded;
use sp_core::H256;
use std::{
env,
io::{BufRead, BufReader, Write},
@@ -43,7 +49,7 @@ impl Default for Status {
}
}
fn api<T: Into<Option<Status>>>(sync: T) -> System<Block> {
fn api<T: Into<Option<Status>>>(sync: T) -> RpcModule<System<Block>> {
let status = sync.into().unwrap_or_default();
let should_have_peers = !status.is_dev;
let (tx, rx) = tracing_unbounded("rpc_system_tests");
@@ -136,98 +142,122 @@ fn api<T: Into<Option<Status>>>(sync: T) -> System<Block> {
tx,
sc_rpc_api::DenyUnsafe::No,
)
.into_rpc()
}
fn wait_receiver<T>(rx: Receiver<T>) -> T {
futures::executor::block_on(rx).unwrap()
}
#[test]
fn system_name_works() {
assert_eq!(api(None).system_name().unwrap(), "testclient".to_owned());
}
#[test]
fn system_version_works() {
assert_eq!(api(None).system_version().unwrap(), "0.2.0".to_owned());
}
#[test]
fn system_chain_works() {
assert_eq!(api(None).system_chain().unwrap(), "testchain".to_owned());
}
#[test]
fn system_properties_works() {
assert_eq!(api(None).system_properties().unwrap(), serde_json::map::Map::new());
}
#[test]
fn system_type_works() {
assert_eq!(api(None).system_type().unwrap(), Default::default());
}
#[test]
fn system_health() {
assert_matches!(
wait_receiver(api(None).system_health()),
Health { peers: 0, is_syncing: false, should_have_peers: true }
#[tokio::test]
async fn system_name_works() {
assert_eq!(
api(None).call::<_, String>("system_name", EmptyParams::new()).await.unwrap(),
"testclient".to_string(),
);
}
assert_matches!(
wait_receiver(
api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true })
.system_health()
),
Health { peers: 5, is_syncing: true, should_have_peers: false }
#[tokio::test]
async fn system_version_works() {
assert_eq!(
api(None).call::<_, String>("system_version", EmptyParams::new()).await.unwrap(),
"0.2.0".to_string(),
);
}
#[tokio::test]
async fn system_chain_works() {
assert_eq!(
api(None).call::<_, String>("system_chain", EmptyParams::new()).await.unwrap(),
"testchain".to_string(),
);
}
#[tokio::test]
async fn system_properties_works() {
type Map = serde_json::map::Map<String, serde_json::Value>;
assert_eq!(
api(None).call::<_, Map>("system_properties", EmptyParams::new()).await.unwrap(),
Map::new()
);
}
#[tokio::test]
async fn system_type_works() {
assert_eq!(
api(None)
.call::<_, String>("system_chainType", EmptyParams::new())
.await
.unwrap(),
"Live".to_owned(),
);
}
#[tokio::test]
async fn system_health() {
assert_eq!(
api(None).call::<_, Health>("system_health", EmptyParams::new()).await.unwrap(),
Health { peers: 0, is_syncing: false, should_have_peers: true },
);
assert_eq!(
wait_receiver(
api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false })
.system_health()
),
Health { peers: 5, is_syncing: false, should_have_peers: true }
api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true })
.call::<_, Health>("system_health", EmptyParams::new())
.await
.unwrap(),
Health { peers: 5, is_syncing: true, should_have_peers: false },
);
assert_eq!(
wait_receiver(
api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true })
.system_health()
),
Health { peers: 0, is_syncing: false, should_have_peers: false }
api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false })
.call::<_, Health>("system_health", EmptyParams::new())
.await
.unwrap(),
Health { peers: 5, is_syncing: false, should_have_peers: true },
);
assert_eq!(
api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true })
.call::<_, Health>("system_health", EmptyParams::new())
.await
.unwrap(),
Health { peers: 0, is_syncing: false, should_have_peers: false },
);
}
#[test]
fn system_local_peer_id_works() {
#[tokio::test]
async fn system_local_peer_id_works() {
assert_eq!(
wait_receiver(api(None).system_local_peer_id()),
"QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned(),
api(None)
.call::<_, String>("system_localPeerId", EmptyParams::new())
.await
.unwrap(),
"QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned()
);
}
#[test]
fn system_local_listen_addresses_works() {
#[tokio::test]
async fn system_local_listen_addresses_works() {
assert_eq!(
wait_receiver(api(None).system_local_listen_addresses()),
api(None)
.call::<_, Vec<String>>("system_localListenAddresses", EmptyParams::new())
.await
.unwrap(),
vec![
"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"
.to_string(),
"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV",
"/ip4/127.0.0.1/tcp/30334/ws/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"
.to_string(),
]
);
}
#[test]
fn system_peers() {
#[tokio::test]
async fn system_peers() {
let peer_id = PeerId::random();
let req = api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true }).system_peers();
let res = executor::block_on(req).unwrap();
let peer_info: Vec<PeerInfo<H256, u64>> =
api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true })
.call("system_peers", EmptyParams::new())
.await
.unwrap();
assert_eq!(
res,
peer_info,
vec![PeerInfo {
peer_id: peer_id.to_base58(),
roles: "FULL".into(),
@@ -237,14 +267,16 @@ fn system_peers() {
);
}
#[test]
fn system_network_state() {
let req = api(None).system_network_state();
let res = executor::block_on(req).unwrap();
#[tokio::test]
async fn system_network_state() {
use sc_network::network_state::NetworkState;
let network_state: NetworkState = api(None)
.call("system_unstable_networkState", EmptyParams::new())
.await
.unwrap();
assert_eq!(
serde_json::from_value::<sc_network::network_state::NetworkState>(res).unwrap(),
sc_network::network_state::NetworkState {
network_state,
NetworkState {
peer_id: String::new(),
listened_addresses: Default::default(),
external_addresses: Default::default(),
@@ -255,51 +287,60 @@ fn system_network_state() {
);
}
#[test]
fn system_node_roles() {
assert_eq!(wait_receiver(api(None).system_node_roles()), vec![NodeRole::Authority]);
#[tokio::test]
async fn system_node_roles() {
let node_roles: Vec<NodeRole> =
api(None).call("system_nodeRoles", EmptyParams::new()).await.unwrap();
assert_eq!(node_roles, vec![NodeRole::Authority]);
}
#[test]
fn system_sync_state() {
#[tokio::test]
async fn system_sync_state() {
let sync_state: SyncState<i32> =
api(None).call("system_syncState", EmptyParams::new()).await.unwrap();
assert_eq!(
wait_receiver(api(None).system_sync_state()),
sync_state,
SyncState { starting_block: 1, current_block: 2, highest_block: Some(3) }
);
}
#[test]
fn system_network_add_reserved() {
#[tokio::test]
async fn system_network_add_reserved() {
let good_peer_id =
"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV";
let bad_peer_id = "/ip4/198.51.100.19/tcp/30333";
["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"];
let _good: () = api(None)
.call("system_addReservedPeer", good_peer_id)
.await
.expect("good peer id works");
let good_fut = api(None).system_add_reserved_peer(good_peer_id.into());
let bad_fut = api(None).system_add_reserved_peer(bad_peer_id.into());
assert_eq!(executor::block_on(good_fut), Ok(()));
assert!(executor::block_on(bad_fut).is_err());
}
#[test]
fn system_network_remove_reserved() {
let good_peer_id = "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV";
let bad_peer_id =
"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV";
let good_fut = api(None).system_remove_reserved_peer(good_peer_id.into());
let bad_fut = api(None).system_remove_reserved_peer(bad_peer_id.into());
assert_eq!(executor::block_on(good_fut), Ok(()));
assert!(executor::block_on(bad_fut).is_err());
}
#[test]
fn system_network_reserved_peers() {
assert_eq!(
wait_receiver(api(None).system_reserved_peers()),
vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()]
let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"];
assert_matches!(
api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Peer id is missing from the address")
);
}
#[tokio::test]
async fn system_network_remove_reserved() {
let _good_peer: () = api(None)
.call("system_removeReservedPeer", ["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"])
.await
.expect("call with good peer id works");
let bad_peer_id =
["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"];
assert_matches!(
api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0")
);
}
#[tokio::test]
async fn system_network_reserved_peers() {
let reserved_peers: Vec<String> =
api(None).call("system_reservedPeers", EmptyParams::new()).await.unwrap();
assert_eq!(reserved_peers, vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()],);
}
#[test]
fn test_add_reset_log_filter() {
const EXPECTED_BEFORE_ADD: &'static str = "EXPECTED_BEFORE_ADD";
@@ -315,15 +356,20 @@ fn test_add_reset_log_filter() {
for line in std::io::stdin().lock().lines() {
let line = line.expect("Failed to read bytes");
if line.contains("add_reload") {
api(None)
.system_add_log_filter("test_after_add".into())
.expect("`system_add_log_filter` failed");
let filter = "test_after_add";
let fut =
async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await };
futures::executor::block_on(fut).expect("`system_addLogFilter` failed");
} else if line.contains("add_trace") {
api(None)
.system_add_log_filter("test_before_add=trace".into())
.expect("`system_add_log_filter` failed");
let filter = "test_before_add=trace";
let fut =
async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await };
futures::executor::block_on(fut).expect("`system_addLogFilter (trace)` failed");
} else if line.contains("reset") {
api(None).system_reset_log_filter().expect("`system_reset_log_filter` failed");
let fut = async move {
api(None).call::<_, ()>("system_resetLogFilter", EmptyParams::new()).await
};
futures::executor::block_on(fut).expect("`system_resetLogFilter` failed");
} else if line.contains("exit") {
return
}
+9 -22
View File
@@ -18,29 +18,16 @@
//! Testing utils used by the RPC tests.
use futures::{
executor,
task::{FutureObj, Spawn, SpawnError},
};
use std::{future::Future, sync::Arc};
// Executor shared by all tests.
//
// This shared executor is used to prevent `Too many open files` errors
// on systems with a lot of cores.
lazy_static::lazy_static! {
static ref EXECUTOR: executor::ThreadPool = executor::ThreadPool::new()
.expect("Failed to create thread pool executor for tests");
use sp_core::testing::TaskExecutor;
/// Executor for testing.
pub fn test_executor() -> Arc<sp_core::testing::TaskExecutor> {
Arc::new(TaskExecutor::default())
}
/// Executor for use in testing
pub struct TaskExecutor;
impl Spawn for TaskExecutor {
fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> {
EXECUTOR.spawn_ok(future);
Ok(())
}
fn status(&self) -> Result<(), SpawnError> {
Ok(())
}
/// Wrap a future in a timeout a little more concisely
pub fn timeout_secs<I, F: Future<Output = I>>(s: u64, f: F) -> tokio::time::Timeout<F> {
tokio::time::timeout(std::time::Duration::from_secs(s), f)
}
+1 -2
View File
@@ -22,10 +22,9 @@ wasmtime = ["sc-executor/wasmtime"]
test-helpers = []
[dependencies]
jsonrpsee = { version = "0.12.0", features = ["server"] }
thiserror = "1.0.30"
futures = "0.3.21"
jsonrpc-pubsub = "18.0"
jsonrpc-core = "18.0"
rand = "0.7.3"
parking_lot = "0.12.0"
log = "0.4.16"
+64 -126
View File
@@ -25,7 +25,7 @@ use crate::{
start_rpc_servers, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter,
};
use futures::{channel::oneshot, future::ready, FutureExt, StreamExt};
use jsonrpc_pubsub::manager::SubscriptionManager;
use jsonrpsee::RpcModule;
use log::info;
use prometheus_endpoint::Registry;
use sc_chain_spec::get_extension;
@@ -45,6 +45,14 @@ use sc_network::{
warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider},
NetworkService,
};
use sc_rpc::{
author::AuthorApiServer,
chain::ChainApiServer,
offchain::OffchainApiServer,
state::{ChildStateApiServer, StateApiServer},
system::SystemApiServer,
DenyUnsafe, SubscriptionTaskExecutor,
};
use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO};
use sc_transaction_pool_api::MaintainedTransactionPool;
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
@@ -62,69 +70,6 @@ use sp_runtime::{
};
use std::{str::FromStr, sync::Arc, time::SystemTime};
/// A utility trait for building an RPC extension given a `DenyUnsafe` instance.
/// This is useful since at service definition time we don't know whether the
/// specific interface where the RPC extension will be exposed is safe or not.
/// This trait allows us to lazily build the RPC extension whenever we bind the
/// service to an interface.
pub trait RpcExtensionBuilder {
/// The type of the RPC extension that will be built.
type Output: sc_rpc::RpcExtension<sc_rpc::Metadata>;
/// Returns an instance of the RPC extension for a particular `DenyUnsafe`
/// value, e.g. the RPC extension might not expose some unsafe methods.
fn build(
&self,
deny: sc_rpc::DenyUnsafe,
subscription_executor: sc_rpc::SubscriptionTaskExecutor,
) -> Result<Self::Output, Error>;
}
impl<F, R> RpcExtensionBuilder for F
where
F: Fn(sc_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor) -> Result<R, Error>,
R: sc_rpc::RpcExtension<sc_rpc::Metadata>,
{
type Output = R;
fn build(
&self,
deny: sc_rpc::DenyUnsafe,
subscription_executor: sc_rpc::SubscriptionTaskExecutor,
) -> Result<Self::Output, Error> {
(*self)(deny, subscription_executor)
}
}
/// A utility struct for implementing an `RpcExtensionBuilder` given a cloneable
/// `RpcExtension`, the resulting builder will simply ignore the provided
/// `DenyUnsafe` instance and return a static `RpcExtension` instance.
pub struct NoopRpcExtensionBuilder<R>(pub R);
impl<R> RpcExtensionBuilder for NoopRpcExtensionBuilder<R>
where
R: Clone + sc_rpc::RpcExtension<sc_rpc::Metadata>,
{
type Output = R;
fn build(
&self,
_deny: sc_rpc::DenyUnsafe,
_subscription_executor: sc_rpc::SubscriptionTaskExecutor,
) -> Result<Self::Output, Error> {
Ok(self.0.clone())
}
}
impl<R> From<R> for NoopRpcExtensionBuilder<R>
where
R: sc_rpc::RpcExtension<sc_rpc::Metadata>,
{
fn from(e: R) -> NoopRpcExtensionBuilder<R> {
NoopRpcExtensionBuilder(e)
}
}
/// Full client type.
pub type TFullClient<TBl, TRtApi, TExec> =
Client<TFullBackend<TBl>, TFullCallExecutor<TBl, TExec>, TBl, TRtApi>;
@@ -389,9 +334,9 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> {
pub keystore: SyncCryptoStorePtr,
/// A shared transaction pool.
pub transaction_pool: Arc<TExPool>,
/// A RPC extension builder. Use `NoopRpcExtensionBuilder` if you just want to pass in the
/// extensions directly.
pub rpc_extensions_builder: Box<dyn RpcExtensionBuilder<Output = TRpc> + Send>,
/// Builds additional [`RpcModule`]s that should be added to the server
pub rpc_builder:
Box<dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result<RpcModule<TRpc>, Error>>,
/// A shared network instance.
pub network: Arc<NetworkService<TBl, <TBl as BlockT>::Hash>>,
/// A Sender for RPC requests.
@@ -463,7 +408,6 @@ where
TExPool: MaintainedTransactionPool<Block = TBl, Hash = <TBl as BlockT>::Hash>
+ parity_util_mem::MallocSizeOf
+ 'static,
TRpc: sc_rpc::RpcExtension<sc_rpc::Metadata>,
{
let SpawnTasksParams {
mut config,
@@ -472,7 +416,7 @@ where
backend,
keystore,
transaction_pool,
rpc_extensions_builder,
rpc_builder,
network,
system_rpc_tx,
telemetry,
@@ -536,35 +480,25 @@ where
metrics_service.run(client.clone(), transaction_pool.clone(), network.clone()),
);
// RPC
let gen_handler = |deny_unsafe: sc_rpc::DenyUnsafe,
rpc_middleware: sc_rpc_server::RpcMiddleware| {
gen_handler(
let rpc_id_provider = config.rpc_id_provider.take();
// jsonrpsee RPC
let gen_rpc_module = |deny_unsafe: DenyUnsafe| {
gen_rpc_module(
deny_unsafe,
rpc_middleware,
&config,
task_manager.spawn_handle(),
client.clone(),
transaction_pool.clone(),
keystore.clone(),
&*rpc_extensions_builder,
backend.offchain_storage(),
system_rpc_tx.clone(),
&config,
backend.offchain_storage(),
&*rpc_builder,
)
};
let rpc_metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?;
let server_metrics = sc_rpc_server::ServerMetrics::new(config.prometheus_registry())?;
let rpc = start_rpc_servers(&config, gen_handler, rpc_metrics.clone(), server_metrics)?;
// This is used internally, so don't restrict access to unsafe RPC
let known_rpc_method_names =
sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?;
let rpc_handlers = RpcHandlers(Arc::new(
gen_handler(
sc_rpc::DenyUnsafe::No,
sc_rpc_server::RpcMiddleware::new(rpc_metrics, known_rpc_method_names, "inbrowser"),
)?
.into(),
));
let rpc = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?;
let rpc_handlers = RpcHandlers(Arc::new(gen_rpc_module(sc_rpc::DenyUnsafe::No)?.into()));
// Spawn informant task
spawn_handle.spawn(
@@ -578,7 +512,7 @@ where
),
);
task_manager.keep_alive((config.base_path, rpc, rpc_handlers.clone()));
task_manager.keep_alive((config.base_path, rpc));
Ok(rpc_handlers)
}
@@ -642,18 +576,17 @@ fn init_telemetry<TBl: BlockT, TCl: BlockBackend<TBl>>(
Ok(telemetry.handle())
}
fn gen_handler<TBl, TBackend, TExPool, TRpc, TCl>(
deny_unsafe: sc_rpc::DenyUnsafe,
rpc_middleware: sc_rpc_server::RpcMiddleware,
config: &Configuration,
fn gen_rpc_module<TBl, TBackend, TCl, TRpc, TExPool>(
deny_unsafe: DenyUnsafe,
spawn_handle: SpawnTaskHandle,
client: Arc<TCl>,
transaction_pool: Arc<TExPool>,
keystore: SyncCryptoStorePtr,
rpc_extensions_builder: &(dyn RpcExtensionBuilder<Output = TRpc> + Send),
offchain_storage: Option<<TBackend as sc_client_api::backend::Backend<TBl>>::OffchainStorage>,
system_rpc_tx: TracingUnboundedSender<sc_rpc::system::Request<TBl>>,
) -> Result<sc_rpc_server::RpcHandler<sc_rpc::Metadata>, Error>
config: &Configuration,
offchain_storage: Option<<TBackend as sc_client_api::backend::Backend<TBl>>::OffchainStorage>,
rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result<RpcModule<TRpc>, Error>),
) -> Result<RpcModule<()>, Error>
where
TBl: BlockT,
TCl: ProvideRuntimeApi<TBl>
@@ -668,15 +601,12 @@ where
+ Send
+ Sync
+ 'static,
TExPool: MaintainedTransactionPool<Block = TBl, Hash = <TBl as BlockT>::Hash> + 'static,
TBackend: sc_client_api::backend::Backend<TBl> + 'static,
TRpc: sc_rpc::RpcExtension<sc_rpc::Metadata>,
<TCl as ProvideRuntimeApi<TBl>>::Api: sp_session::SessionKeys<TBl> + sp_api::Metadata<TBl>,
TExPool: MaintainedTransactionPool<Block = TBl, Hash = <TBl as BlockT>::Hash> + 'static,
TBl::Hash: Unpin,
TBl::Header: Unpin,
{
use sc_rpc::{author, chain, offchain, state, system};
let system_info = sc_rpc::system::SystemInfo {
chain_name: config.chain_spec.name().into(),
impl_name: config.impl_name.clone(),
@@ -685,42 +615,50 @@ where
chain_type: config.chain_spec.chain_type(),
};
let task_executor = sc_rpc::SubscriptionTaskExecutor::new(spawn_handle);
let subscriptions = SubscriptionManager::new(Arc::new(task_executor.clone()));
let mut rpc_api = RpcModule::new(());
let task_executor = Arc::new(spawn_handle);
let (chain, state, child_state) = {
// Full nodes
let chain = sc_rpc::chain::new_full(client.clone(), subscriptions.clone());
let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc();
let (state, child_state) = sc_rpc::state::new_full(
client.clone(),
subscriptions.clone(),
task_executor.clone(),
deny_unsafe,
config.rpc_max_payload,
);
let state = state.into_rpc();
let child_state = child_state.into_rpc();
(chain, state, child_state)
};
let author =
sc_rpc::author::Author::new(client, transaction_pool, subscriptions, keystore, deny_unsafe);
let system = system::System::new(system_info, system_rpc_tx, deny_unsafe);
let author = sc_rpc::author::Author::new(
client.clone(),
transaction_pool,
keystore,
deny_unsafe,
task_executor.clone(),
)
.into_rpc();
let maybe_offchain_rpc = offchain_storage.map(|storage| {
let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe);
offchain::OffchainApi::to_delegate(offchain)
});
let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc();
Ok(sc_rpc_server::rpc_handler(
(
state::StateApi::to_delegate(state),
state::ChildStateApi::to_delegate(child_state),
chain::ChainApi::to_delegate(chain),
maybe_offchain_rpc,
author::AuthorApi::to_delegate(author),
system::SystemApi::to_delegate(system),
rpc_extensions_builder.build(deny_unsafe, task_executor)?,
),
rpc_middleware,
))
if let Some(storage) = offchain_storage {
let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc();
rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?;
}
rpc_api.merge(chain).map_err(|e| Error::Application(e.into()))?;
rpc_api.merge(author).map_err(|e| Error::Application(e.into()))?;
rpc_api.merge(system).map_err(|e| Error::Application(e.into()))?;
rpc_api.merge(state).map_err(|e| Error::Application(e.into()))?;
rpc_api.merge(child_state).map_err(|e| Error::Application(e.into()))?;
// Additional [`RpcModule`]s defined in the node to fit the specific blockchain
let extra_rpcs = rpc_builder(deny_unsafe, task_executor.clone())?;
rpc_api.merge(extra_rpcs).map_err(|e| Error::Application(e.into()))?;
Ok(rpc_api)
}
/// Parameters to pass into `build_network`.
+12
View File
@@ -100,6 +100,18 @@ pub struct Configuration {
pub rpc_methods: RpcMethods,
/// Maximum payload of rpc request/responses.
pub rpc_max_payload: Option<usize>,
/// Maximum payload of a rpc request
pub rpc_max_request_size: Option<usize>,
/// Maximum payload of a rpc request
pub rpc_max_response_size: Option<usize>,
/// Custom JSON-RPC subscription ID provider.
///
/// Default: [`crate::RandomStringSubscriptionId`].
pub rpc_id_provider: Option<Box<dyn crate::RpcSubscriptionIdProvider>>,
/// Maximum allowed subscriptions per rpc connection
///
/// Default: 1024.
pub rpc_max_subs_per_conn: Option<usize>,
/// Maximum size of the output buffer capacity for websocket connections.
pub ws_max_out_buffer_capacity: Option<usize>,
/// Prometheus endpoint configuration. `None` if disabled.
+123 -151
View File
@@ -34,13 +34,15 @@ mod client;
mod metrics;
mod task_manager;
use std::{collections::HashMap, io, net::SocketAddr, pin::Pin};
use std::{collections::HashMap, net::SocketAddr};
use codec::{Decode, Encode};
use futures::{Future, FutureExt, StreamExt};
use futures::{channel::mpsc, FutureExt, StreamExt};
use jsonrpsee::{core::Error as JsonRpseeError, RpcModule};
use log::{debug, error, warn};
use sc_client_api::{BlockBackend, ProofProvider};
use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider};
use sc_network::PeerId;
use sc_rpc_server::WsConfig;
use sc_utils::mpsc::TracingUnboundedReceiver;
use sp_blockchain::HeaderMetadata;
use sp_runtime::{
@@ -52,8 +54,7 @@ pub use self::{
builder::{
build_network, build_offchain_workers, new_client, new_db_backend, new_full_client,
new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter,
NoopRpcExtensionBuilder, RpcExtensionBuilder, SpawnTasksParams, TFullBackend,
TFullCallExecutor, TFullClient,
SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient,
},
client::{ClientConfig, LocalCallExecutor},
error::Error,
@@ -65,12 +66,14 @@ pub use sc_chain_spec::{
ChainSpec, ChainType, Extension as ChainSpecExtension, GenericChainSpec, NoExtension,
Properties, RuntimeGenesis,
};
use sc_client_api::{blockchain::HeaderBackend, BlockchainEvents};
pub use sc_consensus::ImportQueue;
pub use sc_executor::NativeExecutionDispatch;
#[doc(hidden)]
pub use sc_network::config::{TransactionImport, TransactionImportFuture};
pub use sc_rpc::Metadata as RpcMetadata;
pub use sc_rpc::{
RandomIntegerSubscriptionId, RandomStringSubscriptionId, RpcSubscriptionIdProvider,
};
pub use sc_tracing::TracingReceiver;
pub use sc_transaction_pool::Options as TransactionPoolOptions;
pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool};
@@ -82,32 +85,27 @@ const DEFAULT_PROTOCOL_ID: &str = "sup";
/// RPC handlers that can perform RPC queries.
#[derive(Clone)]
pub struct RpcHandlers(
Arc<jsonrpc_core::MetaIoHandler<sc_rpc::Metadata, sc_rpc_server::RpcMiddleware>>,
);
pub struct RpcHandlers(Arc<RpcModule<()>>);
impl RpcHandlers {
/// Starts an RPC query.
///
/// The query is passed as a string and must be a JSON text similar to what an HTTP client
/// would for example send.
/// The query is passed as a string and must be valid JSON-RPC request object.
///
/// Returns a `Future` that contains the optional response.
/// Returns a response and a stream if the call successful, fails if the
/// query could not be decoded as a JSON-RPC request object.
///
/// If the request subscribes you to events, the `Sender` in the `RpcSession` object is used to
/// send back spontaneous events.
pub fn rpc_query(
/// If the request subscribes you to events, the `stream` can be used to
/// retrieve the events.
pub async fn rpc_query(
&self,
mem: &RpcSession,
request: &str,
) -> Pin<Box<dyn Future<Output = Option<String>> + Send>> {
self.0.handle_request(request, mem.metadata.clone()).boxed()
json_query: &str,
) -> Result<(String, mpsc::UnboundedReceiver<String>), JsonRpseeError> {
self.0.raw_json_request(json_query).await
}
/// Provides access to the underlying `MetaIoHandler`
pub fn io_handler(
&self,
) -> Arc<jsonrpc_core::MetaIoHandler<sc_rpc::Metadata, sc_rpc_server::RpcMiddleware>> {
/// Provides access to the underlying `RpcModule`
pub fn handle(&self) -> Arc<RpcModule<()>> {
self.0.clone()
}
}
@@ -284,74 +282,41 @@ async fn build_network_future<
// Wrapper for HTTP and WS servers that makes sure they are properly shut down.
mod waiting {
pub struct HttpServer(pub Option<sc_rpc_server::HttpServer>);
impl Drop for HttpServer {
fn drop(&mut self) {
if let Some(server) = self.0.take() {
server.close_handle().close();
server.wait();
}
}
}
pub struct IpcServer(pub Option<sc_rpc_server::IpcServer>);
impl Drop for IpcServer {
fn drop(&mut self) {
if let Some(server) = self.0.take() {
server.close_handle().close();
let _ = server.wait();
// This doesn't not wait for the server to be stopped but fires the signal.
let _ = server.stop();
}
}
}
pub struct WsServer(pub Option<sc_rpc_server::WsServer>);
impl Drop for WsServer {
fn drop(&mut self) {
if let Some(server) = self.0.take() {
server.close_handle().close();
let _ = server.wait();
// This doesn't not wait for the server to be stopped but fires the signal.
let _ = server.stop();
}
}
}
}
/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them
/// alive.
fn start_rpc_servers<
H: FnMut(
sc_rpc::DenyUnsafe,
sc_rpc_server::RpcMiddleware,
) -> Result<sc_rpc_server::RpcHandler<sc_rpc::Metadata>, Error>,
>(
/// Starts RPC servers.
fn start_rpc_servers<R>(
config: &Configuration,
mut gen_handler: H,
rpc_metrics: Option<sc_rpc_server::RpcMetrics>,
server_metrics: sc_rpc_server::ServerMetrics,
) -> Result<Box<dyn std::any::Any + Send>, Error> {
fn maybe_start_server<T, F>(
address: Option<SocketAddr>,
mut start: F,
) -> Result<Option<T>, Error>
where
F: FnMut(&SocketAddr) -> Result<T, Error>,
{
address
.map(|mut address| {
start(&address).or_else(|e| match e {
Error::Io(e) => match e.kind() {
io::ErrorKind::AddrInUse | io::ErrorKind::PermissionDenied => {
warn!("Unable to bind RPC server to {}. Trying random port.", address);
address.set_port(0);
start(&address)
},
_ => Err(e.into()),
},
e => Err(e),
})
})
.transpose()
}
gen_rpc_module: R,
rpc_id_provider: Option<Box<dyn RpcSubscriptionIdProvider>>,
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error>
where
R: Fn(sc_rpc::DenyUnsafe) -> Result<RpcModule<()>, Error>,
{
let (max_request_size, ws_max_response_size, http_max_response_size) =
legacy_cli_parsing(config);
fn deny_unsafe(addr: &SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe {
fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe {
let is_exposed_addr = !addr.ip().is_loopback();
match (is_exposed_addr, methods) {
| (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => sc_rpc::DenyUnsafe::No,
@@ -359,85 +324,54 @@ fn start_rpc_servers<
}
}
let rpc_method_names = sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?;
Ok(Box::new((
config
.rpc_ipc
.as_ref()
.map(|path| {
sc_rpc_server::start_ipc(
&*path,
gen_handler(
sc_rpc::DenyUnsafe::No,
sc_rpc_server::RpcMiddleware::new(
rpc_metrics.clone(),
rpc_method_names.clone(),
"ipc",
),
)?,
server_metrics.clone(),
)
.map_err(Error::from)
})
.transpose()?,
maybe_start_server(config.rpc_http, |address| {
sc_rpc_server::start_http(
address,
config.rpc_cors.as_ref(),
gen_handler(
deny_unsafe(address, &config.rpc_methods),
sc_rpc_server::RpcMiddleware::new(
rpc_metrics.clone(),
rpc_method_names.clone(),
"http",
),
)?,
config.rpc_max_payload,
config.tokio_handle.clone(),
)
.map_err(Error::from)
})?
.map(|s| waiting::HttpServer(Some(s))),
maybe_start_server(config.rpc_ws, |address| {
sc_rpc_server::start_ws(
address,
config.rpc_ws_max_connections,
config.rpc_cors.as_ref(),
gen_handler(
deny_unsafe(address, &config.rpc_methods),
sc_rpc_server::RpcMiddleware::new(
rpc_metrics.clone(),
rpc_method_names.clone(),
"ws",
),
)?,
config.rpc_max_payload,
config.ws_max_out_buffer_capacity,
server_metrics.clone(),
config.tokio_handle.clone(),
)
.map_err(Error::from)
})?
.map(|s| waiting::WsServer(Some(s))),
)))
}
let random_port = |mut addr: SocketAddr| {
addr.set_port(0);
addr
};
/// An RPC session. Used to perform in-memory RPC queries (ie. RPC queries that don't go through
/// the HTTP or WebSockets server).
#[derive(Clone)]
pub struct RpcSession {
metadata: sc_rpc::Metadata,
}
let ws_addr = config
.rpc_ws
.unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed"));
let ws_addr2 = random_port(ws_addr);
let http_addr = config
.rpc_http
.unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed"));
let http_addr2 = random_port(http_addr);
impl RpcSession {
/// Creates an RPC session.
///
/// The `sender` is stored inside the `RpcSession` and is used to communicate spontaneous JSON
/// messages.
///
/// The `RpcSession` must be kept alive in order to receive messages on the sender.
pub fn new(sender: futures::channel::mpsc::UnboundedSender<String>) -> RpcSession {
RpcSession { metadata: sender.into() }
let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?;
let http_fut = sc_rpc_server::start_http(
[http_addr, http_addr2],
config.rpc_cors.as_ref(),
max_request_size,
http_max_response_size,
metrics.clone(),
gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?,
config.tokio_handle.clone(),
);
let ws_config = WsConfig {
max_connections: config.rpc_ws_max_connections,
max_payload_in_mb: max_request_size,
max_payload_out_mb: ws_max_response_size,
max_subs_per_conn: config.rpc_max_subs_per_conn,
};
let ws_fut = sc_rpc_server::start_ws(
[ws_addr, ws_addr2],
config.rpc_cors.as_ref(),
ws_config,
metrics,
gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?,
config.tokio_handle.clone(),
rpc_id_provider,
);
match tokio::task::block_in_place(|| {
config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut))
}) {
Ok((http, ws)) => Ok(Box::new((http, ws))),
Err(e) => Err(Error::Application(e)),
}
}
@@ -545,6 +479,44 @@ where
}
}
fn legacy_cli_parsing(config: &Configuration) -> (Option<usize>, Option<usize>, Option<usize>) {
let ws_max_response_size = config.ws_max_out_buffer_capacity.map(|max| {
eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed use `rpc-max-response-size or rpc-max-request-size` instead");
eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`");
std::cmp::max(max, config.rpc_max_response_size.unwrap_or(0))
});
let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) {
(Some(legacy_max), max) => {
eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead");
eprintln!(
"Setting `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_request_size)`"
);
Some(std::cmp::max(legacy_max, max.unwrap_or(0)))
},
(None, Some(max)) => Some(max),
(None, None) => None,
};
let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_request_size) {
(Some(legacy_max), max) => {
eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead");
eprintln!(
"Setting HTTP `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_response_size)`"
);
Some(std::cmp::max(legacy_max, max.unwrap_or(0)))
},
(None, Some(max)) => Some(max),
(None, None) => None,
};
if config.rpc_ipc.is_some() {
eprintln!("DEPRECATED: `--ipc-path` has no effect anymore IPC support has been removed");
}
(max_request_size, ws_max_response_size, http_max_response_size)
}
#[cfg(test)]
mod tests {
use super::*;
+4
View File
@@ -246,6 +246,10 @@ fn node_config<
rpc_cors: None,
rpc_methods: Default::default(),
rpc_max_payload: None,
rpc_max_request_size: None,
rpc_max_response_size: None,
rpc_id_provider: None,
rpc_max_subs_per_conn: None,
ws_max_out_buffer_capacity: None,
prometheus_config: None,
telemetry_endpoints: None,
+1 -3
View File
@@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
thiserror = "1.0.30"
+27 -33
View File
@@ -37,10 +37,15 @@
//! ```
//!
//! If the [`LightSyncStateExtension`] is not added as an extension to the chain spec,
//! the [`SyncStateRpcHandler`] will fail at instantiation.
//! the [`SyncStateRpc`] will fail at instantiation.
#![deny(unused_crate_dependencies)]
use jsonrpsee::{
core::{Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::{error::CallError, ErrorObject},
};
use sc_client_api::StorageData;
use sp_blockchain::HeaderBackend;
use sp_runtime::{
@@ -49,8 +54,6 @@ use sp_runtime::{
};
use std::sync::Arc;
use jsonrpc_derive::rpc;
type SharedAuthoritySet<TBl> =
sc_finality_grandpa::SharedAuthoritySet<<TBl as BlockT>::Hash, NumberFor<TBl>>;
type SharedEpochChanges<TBl> =
@@ -76,13 +79,13 @@ pub enum Error<Block: BlockT> {
LightSyncStateExtensionNotFound,
}
impl<Block: BlockT> From<Error<Block>> for jsonrpc_core::Error {
impl<Block: BlockT> From<Error<Block>> for JsonRpseeError {
fn from(error: Error<Block>) -> Self {
let message = match error {
Error::JsonRpc(s) => s,
_ => error.to_string(),
};
jsonrpc_core::Error { message, code: jsonrpc_core::ErrorCode::ServerError(1), data: None }
CallError::Custom(ErrorObject::owned(1, message, None::<()>)).into()
}
}
@@ -101,7 +104,7 @@ fn serialize_encoded<S: serde::Serializer, T: codec::Encode>(
/// chain-spec as an extension.
pub type LightSyncStateExtension = Option<serde_json::Value>;
/// Hardcoded infomation that allows light clients to sync quickly.
/// Hardcoded information that allows light clients to sync quickly.
#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
@@ -121,30 +124,30 @@ pub struct LightSyncState<Block: BlockT> {
}
/// An api for sync state RPC calls.
#[rpc]
#[rpc(client, server)]
pub trait SyncStateRpcApi {
/// Returns the json-serialized chainspec running the node, with a sync state.
#[rpc(name = "sync_state_genSyncSpec", returns = "jsonrpc_core::Value")]
fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result<jsonrpc_core::Value>;
/// Returns the JSON serialized chainspec running the node, with a sync state.
#[method(name = "sync_state_genSyncSpec")]
fn system_gen_sync_spec(&self, raw: bool) -> RpcResult<String>;
}
/// The handler for sync state RPC calls.
pub struct SyncStateRpcHandler<Block: BlockT, Backend> {
/// An api for sync state RPC calls.
pub struct SyncStateRpc<Block: BlockT, Client> {
chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
client: Arc<Backend>,
client: Arc<Client>,
shared_authority_set: SharedAuthoritySet<Block>,
shared_epoch_changes: SharedEpochChanges<Block>,
}
impl<Block, Backend> SyncStateRpcHandler<Block, Backend>
impl<Block, Client> SyncStateRpc<Block, Client>
where
Block: BlockT,
Backend: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
Client: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
{
/// Create a new handler.
/// Create a new sync state RPC helper.
pub fn new(
chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
client: Arc<Backend>,
client: Arc<Client>,
shared_authority_set: SharedAuthoritySet<Block>,
shared_epoch_changes: SharedEpochChanges<Block>,
) -> Result<Self, Error<Block>> {
@@ -177,32 +180,23 @@ where
}
}
impl<Block, Backend> SyncStateRpcApi for SyncStateRpcHandler<Block, Backend>
impl<Block, Backend> SyncStateRpcApiServer for SyncStateRpc<Block, Backend>
where
Block: BlockT,
Backend: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
{
fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result<jsonrpc_core::Value> {
fn system_gen_sync_spec(&self, raw: bool) -> RpcResult<String> {
let current_sync_state = self.build_sync_state()?;
let mut chain_spec = self.chain_spec.cloned_box();
let sync_state = self.build_sync_state().map_err(map_error::<Block, Error<Block>>)?;
let extension = sc_chain_spec::get_extension_mut::<LightSyncStateExtension>(
chain_spec.extensions_mut(),
)
.ok_or_else(|| {
Error::<Block>::JsonRpc("Could not find `LightSyncState` chain-spec extension!".into())
})?;
.ok_or(Error::<Block>::LightSyncStateExtensionNotFound)?;
*extension =
Some(serde_json::to_value(&sync_state).map_err(|err| map_error::<Block, _>(err))?);
let val = serde_json::to_value(&current_sync_state)?;
*extension = Some(val);
let json_string = chain_spec.as_json(raw).map_err(map_error::<Block, _>)?;
serde_json::from_str(&json_string).map_err(|err| map_error::<Block, _>(err))
chain_spec.as_json(raw).map_err(|e| Error::<Block>::JsonRpc(e).into())
}
}
fn map_error<Block: BlockT, S: ToString>(error: S) -> jsonrpc_core::Error {
Error::<Block>::JsonRpc(error.to_string()).into()
}
+1 -1
View File
@@ -53,7 +53,7 @@ const AVG_SPAN: usize = 100 * 8;
// are used for the RPC Id this may need to be adjusted. Note: The base payload
// does not include the RPC result.
//
// The estimate is based on the JSONRPC response message which has the following format:
// The estimate is based on the JSON-RPC response message which has the following format:
// `{"jsonrpc":"2.0","result":[],"id":18446744073709551615}`.
//
// We care about the total size of the payload because jsonrpc-server will simply ignore
@@ -46,7 +46,7 @@ pub enum Error {
TemporarilyBanned,
#[error("[{0:?}] Already imported")]
AlreadyImported(Box<dyn std::any::Any + Send>),
AlreadyImported(Box<dyn std::any::Any + Send + Sync>),
#[error("Too low priority ({} > {})", old, new)]
TooLowPriority {
@@ -72,7 +72,7 @@ pub enum Error {
}
/// Transaction pool error conversion.
pub trait IntoPoolError: std::error::Error + Send + Sized {
pub trait IntoPoolError: std::error::Error + Send + Sized + Sync {
/// Try to extract original `Error`
///
/// This implementation is optional and used only to
@@ -22,16 +22,17 @@
pub mod error;
use futures::{Future, Stream};
use serde::{Deserialize, Serialize};
pub use sp_runtime::transaction_validity::{
TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Member, NumberFor},
};
use std::{collections::HashMap, hash::Hash, pin::Pin, sync::Arc};
pub use sp_runtime::transaction_validity::{
TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag,
};
/// Transaction pool status.
#[derive(Debug)]
pub struct PoolStatus {
@@ -177,7 +178,7 @@ pub trait TransactionPool: Send + Sync {
/// Block type.
type Block: BlockT;
/// Transaction hash type.
type Hash: Hash + Eq + Member + Serialize;
type Hash: Hash + Eq + Member + Serialize + DeserializeOwned;
/// In-pool transaction type.
type InPoolTransaction: InPoolTransaction<
Transaction = TransactionFor<Self>,
@@ -32,4 +32,3 @@ remote-externalities = { path = "../../../utils/frame/remote-externalities", ver
# others
log = "0.4.16"
tokio = { version = "1", features = ["macros"] }
+1 -3
View File
@@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
serde = { version = "1", features = ["derive"] }
# Substrate Dependencies
+68 -58
View File
@@ -17,11 +17,16 @@
//! Node-specific RPC methods for interaction with contracts.
use std::sync::Arc;
#![warn(unused_crate_dependencies)]
use std::{marker::PhantomData, sync::Arc};
use codec::Codec;
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::error::{CallError, ErrorCode, ErrorObject},
};
use pallet_contracts_primitives::{
Code, CodeUploadResult, ContractExecResult, ContractInstantiateResult,
};
@@ -37,8 +42,8 @@ use sp_runtime::{
pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;
const RUNTIME_ERROR: i64 = 1;
const CONTRACT_DOESNT_EXIST: i64 = 2;
const RUNTIME_ERROR: i32 = 1;
const CONTRACT_DOESNT_EXIST: i32 = 2;
pub type Weight = u64;
@@ -58,15 +63,17 @@ const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND;
/// A private newtype for converting `ContractAccessError` into an RPC error.
struct ContractAccessError(pallet_contracts_primitives::ContractAccessError);
impl From<ContractAccessError> for Error {
fn from(e: ContractAccessError) -> Error {
impl From<ContractAccessError> for JsonRpseeError {
fn from(e: ContractAccessError) -> Self {
use pallet_contracts_primitives::ContractAccessError::*;
match e.0 {
DoesntExist => Error {
code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),
message: "The specified contract doesn't exist.".into(),
data: None,
},
DoesntExist => CallError::Custom(ErrorObject::owned(
CONTRACT_DOESNT_EXIST,
"The specified contract doesn't exist.",
None::<()>,
))
.into(),
}
}
}
@@ -109,7 +116,7 @@ pub struct CodeUploadRequest<AccountId> {
}
/// Contracts RPC methods.
#[rpc]
#[rpc(client, server)]
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash>
where
Balance: Copy + TryFrom<NumberOrHex> + Into<NumberOrHex>,
@@ -121,12 +128,12 @@ where
///
/// This method is useful for calling getter-like methods on contracts or to dry-run a
/// a contract call in order to determine the `gas_limit`.
#[rpc(name = "contracts_call")]
#[method(name = "contracts_call")]
fn call(
&self,
call_request: CallRequest<AccountId>,
at: Option<BlockHash>,
) -> Result<ContractExecResult<Balance>>;
) -> RpcResult<ContractExecResult<Balance>>;
/// Instantiate a new contract.
///
@@ -134,12 +141,12 @@ where
/// is not actually created.
///
/// This method is useful for UIs to dry-run contract instantiations.
#[rpc(name = "contracts_instantiate")]
#[method(name = "contracts_instantiate")]
fn instantiate(
&self,
instantiate_request: InstantiateRequest<AccountId, Hash>,
at: Option<BlockHash>,
) -> Result<ContractInstantiateResult<AccountId, Balance>>;
) -> RpcResult<ContractInstantiateResult<AccountId, Balance>>;
/// Upload new code without instantiating a contract from it.
///
@@ -147,48 +154,50 @@ where
/// won't change any state.
///
/// This method is useful for UIs to dry-run code upload.
#[rpc(name = "contracts_upload_code")]
#[method(name = "contracts_upload_code")]
fn upload_code(
&self,
upload_request: CodeUploadRequest<AccountId>,
at: Option<BlockHash>,
) -> Result<CodeUploadResult<Hash, Balance>>;
) -> RpcResult<CodeUploadResult<Hash, Balance>>;
/// Returns the value under a specified storage `key` in a contract given by `address` param,
/// or `None` if it is not set.
#[rpc(name = "contracts_getStorage")]
#[method(name = "contracts_getStorage")]
fn get_storage(
&self,
address: AccountId,
key: H256,
at: Option<BlockHash>,
) -> Result<Option<Bytes>>;
) -> RpcResult<Option<Bytes>>;
}
/// An implementation of contract specific RPC methods.
pub struct Contracts<C, B> {
client: Arc<C>,
_marker: std::marker::PhantomData<B>,
/// Contracts RPC methods.
pub struct ContractsRpc<Client, Block> {
client: Arc<Client>,
_marker: PhantomData<Block>,
}
impl<C, B> Contracts<C, B> {
impl<Client, Block> ContractsRpc<Client, Block> {
/// Create new `Contracts` with the given reference to the client.
pub fn new(client: Arc<C>) -> Self {
Contracts { client, _marker: Default::default() }
pub fn new(client: Arc<Client>) -> Self {
Self { client, _marker: Default::default() }
}
}
impl<C, Block, AccountId, Balance, Hash>
ContractsApi<
#[async_trait]
impl<Client, Block, AccountId, Balance, Hash>
ContractsApiServer<
<Block as BlockT>::Hash,
<<Block as BlockT>::Header as HeaderT>::Number,
AccountId,
Balance,
Hash,
> for Contracts<C, Block>
> for ContractsRpc<Client, Block>
where
Block: BlockT,
C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
C::Api: ContractsRuntimeApi<
Client: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
Client::Api: ContractsRuntimeApi<
Block,
AccountId,
Balance,
@@ -203,7 +212,7 @@ where
&self,
call_request: CallRequest<AccountId>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<ContractExecResult<Balance>> {
) -> RpcResult<ContractExecResult<Balance>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
@@ -226,7 +235,7 @@ where
&self,
instantiate_request: InstantiateRequest<AccountId, Hash>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<ContractInstantiateResult<AccountId, Balance>> {
) -> RpcResult<ContractInstantiateResult<AccountId, Balance>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
@@ -265,7 +274,7 @@ where
&self,
upload_request: CodeUploadRequest<AccountId>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<CodeUploadResult<Hash, Balance>> {
) -> RpcResult<CodeUploadResult<Hash, Balance>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
@@ -285,12 +294,9 @@ where
address: AccountId,
key: H256,
at: Option<<Block as BlockT>::Hash>,
) -> Result<Option<Bytes>> {
) -> RpcResult<Option<Bytes>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
let result = api
.get_storage(&at, address, key.into())
.map_err(runtime_error_into_rpc_err)?
@@ -302,32 +308,35 @@ where
}
/// Converts a runtime trap into an RPC error.
fn runtime_error_into_rpc_err(err: impl std::fmt::Display) -> Error {
Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime error".into(),
data: Some(err.to_string().into()),
}
fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> JsonRpseeError {
CallError::Custom(ErrorObject::owned(
RUNTIME_ERROR,
"Runtime error",
Some(format!("{:?}", err)),
))
.into()
}
fn decode_hex<H: std::fmt::Debug + Copy, T: TryFrom<H>>(from: H, name: &str) -> Result<T> {
from.try_into().map_err(|_| Error {
code: ErrorCode::InvalidParams,
message: format!("{:?} does not fit into the {} type", from, name),
data: None,
fn decode_hex<H: std::fmt::Debug + Copy, T: TryFrom<H>>(from: H, name: &str) -> RpcResult<T> {
from.try_into().map_err(|_| {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!("{:?} does not fit into the {} type", from, name),
None::<()>,
)))
})
}
fn limit_gas(gas_limit: Weight) -> Result<()> {
fn limit_gas(gas_limit: Weight) -> RpcResult<()> {
if gas_limit > GAS_LIMIT {
Err(Error {
code: ErrorCode::InvalidParams,
message: format!(
Err(JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!(
"Requested gas limit is greater than maximum allowed: {} > {}",
gas_limit, GAS_LIMIT
),
data: None,
})
None::<()>,
))))
} else {
Ok(())
}
@@ -336,6 +345,7 @@ fn limit_gas(gas_limit: Weight) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use pallet_contracts_primitives::{ContractExecResult, ContractInstantiateResult};
use sp_core::U256;
fn trim(json: &str) -> String {
@@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
serde = { version = "1.0.136", features = ["derive"] }
sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" }
sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" }
@@ -16,14 +16,18 @@
// limitations under the License.
#![warn(missing_docs)]
#![warn(unused_crate_dependencies)]
//! Node-specific RPC methods for interaction with Merkle Mountain Range pallet.
use std::sync::Arc;
use std::{marker::PhantomData, sync::Arc};
use codec::{Codec, Encode};
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpsee::{
core::{async_trait, RpcResult},
proc_macros::rpc,
types::error::{CallError, ErrorObject},
};
use serde::{Deserialize, Serialize};
use sp_api::ProvideRuntimeApi;
@@ -34,6 +38,11 @@ use sp_runtime::{generic::BlockId, traits::Block as BlockT};
pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi;
const RUNTIME_ERROR: i32 = 8000;
const MMR_ERROR: i32 = 8010;
const LEAF_NOT_FOUND_ERROR: i32 = MMR_ERROR + 1;
const GENERATE_PROOF_ERROR: i32 = MMR_ERROR + 2;
/// Retrieved MMR leaf and its proof.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
@@ -86,7 +95,7 @@ impl<BlockHash> LeafBatchProof<BlockHash> {
}
/// MMR RPC methods.
#[rpc]
#[rpc(client, server)]
pub trait MmrApi<BlockHash> {
/// Generate MMR proof for given leaf index.
///
@@ -96,12 +105,12 @@ pub trait MmrApi<BlockHash> {
///
/// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of
/// the leaf). Both parameters are SCALE-encoded.
#[rpc(name = "mmr_generateProof")]
#[method(name = "mmr_generateProof")]
fn generate_proof(
&self,
leaf_index: LeafIndex,
at: Option<BlockHash>,
) -> Result<LeafProof<BlockHash>>;
) -> RpcResult<LeafProof<BlockHash>>;
/// Generate MMR proof for the given leaf indices.
///
@@ -113,43 +122,43 @@ pub trait MmrApi<BlockHash> {
/// the leaves). Both parameters are SCALE-encoded.
/// The order of entries in the `leaves` field of the returned struct
/// is the same as the order of the entries in `leaf_indices` supplied
#[rpc(name = "mmr_generateBatchProof")]
#[method(name = "mmr_generateBatchProof")]
fn generate_batch_proof(
&self,
leaf_indices: Vec<LeafIndex>,
at: Option<BlockHash>,
) -> Result<LeafBatchProof<BlockHash>>;
) -> RpcResult<LeafBatchProof<BlockHash>>;
}
/// An implementation of MMR specific RPC methods.
pub struct Mmr<C, B> {
client: Arc<C>,
_marker: std::marker::PhantomData<B>,
/// MMR RPC methods.
pub struct MmrRpc<Client, Block> {
client: Arc<Client>,
_marker: PhantomData<Block>,
}
impl<C, B> Mmr<C, B> {
impl<C, B> MmrRpc<C, B> {
/// Create new `Mmr` with the given reference to the client.
pub fn new(client: Arc<C>) -> Self {
Self { client, _marker: Default::default() }
}
}
impl<C, Block, MmrHash> MmrApi<<Block as BlockT>::Hash> for Mmr<C, (Block, MmrHash)>
#[async_trait]
impl<Client, Block, MmrHash> MmrApiServer<<Block as BlockT>::Hash>
for MmrRpc<Client, (Block, MmrHash)>
where
Block: BlockT,
C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
C::Api: MmrRuntimeApi<Block, MmrHash>,
Client: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
Client::Api: MmrRuntimeApi<Block, MmrHash>,
MmrHash: Codec + Send + Sync + 'static,
{
fn generate_proof(
&self,
leaf_index: LeafIndex,
at: Option<<Block as BlockT>::Hash>,
) -> Result<LeafProof<<Block as BlockT>::Hash>> {
) -> RpcResult<LeafProof<Block::Hash>> {
let api = self.client.runtime_api();
let block_hash = at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash);
let block_hash = at.unwrap_or_else(|| self.client.info().best_hash);
let (leaf, proof) = api
.generate_proof_with_context(
@@ -167,7 +176,7 @@ where
&self,
leaf_indices: Vec<LeafIndex>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<LeafBatchProof<<Block as BlockT>::Hash>> {
) -> RpcResult<LeafBatchProof<<Block as BlockT>::Hash>> {
let api = self.client.runtime_api();
let block_hash = at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
@@ -186,37 +195,31 @@ where
}
}
const RUNTIME_ERROR: i64 = 8000;
const MMR_ERROR: i64 = 8010;
/// Converts a mmr-specific error into an RPC error.
fn mmr_error_into_rpc_error(err: MmrError) -> Error {
/// Converts a mmr-specific error into a [`CallError`].
fn mmr_error_into_rpc_error(err: MmrError) -> CallError {
let data = format!("{:?}", err);
match err {
MmrError::LeafNotFound => Error {
code: ErrorCode::ServerError(MMR_ERROR + 1),
message: "Leaf was not found".into(),
data: Some(format!("{:?}", err).into()),
},
MmrError::GenerateProof => Error {
code: ErrorCode::ServerError(MMR_ERROR + 2),
message: "Error while generating the proof".into(),
data: Some(format!("{:?}", err).into()),
},
_ => Error {
code: ErrorCode::ServerError(MMR_ERROR),
message: "Unexpected MMR error".into(),
data: Some(format!("{:?}", err).into()),
},
MmrError::LeafNotFound => CallError::Custom(ErrorObject::owned(
LEAF_NOT_FOUND_ERROR,
"Leaf was not found",
Some(data),
)),
MmrError::GenerateProof => CallError::Custom(ErrorObject::owned(
GENERATE_PROOF_ERROR,
"Error while generating the proof",
Some(data),
)),
_ => CallError::Custom(ErrorObject::owned(MMR_ERROR, "Unexpected MMR error", Some(data))),
}
}
/// Converts a runtime trap into an RPC error.
fn runtime_error_into_rpc_error(err: impl std::fmt::Display) -> Error {
Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime trapped".into(),
data: Some(err.to_string().into()),
}
/// Converts a runtime trap into a [`CallError`].
fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> CallError {
CallError::Custom(ErrorObject::owned(
RUNTIME_ERROR,
"Runtime trapped",
Some(format!("{:?}", err)),
))
}
#[cfg(test)]
@@ -31,7 +31,7 @@ substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/fram
[dev-dependencies]
parking_lot = "0.12.0"
tokio = { version = "1.10", features = ["macros"] }
tokio = { version = "1.17.0", features = ["macros"] }
pallet-balances = { path = "../balances" }
sp-tracing = { path = "../../primitives/tracing" }
@@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" }
sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" }
sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" }
@@ -17,11 +17,14 @@
//! RPC interface for the transaction payment pallet.
pub use self::gen_client::Client as TransactionPaymentClient;
use std::{convert::TryInto, sync::Arc};
use codec::{Codec, Decode};
use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
use jsonrpc_derive::rpc;
pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi;
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::error::{CallError, ErrorCode, ErrorObject},
};
use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
@@ -31,28 +34,31 @@ use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, MaybeDisplay},
};
use std::sync::Arc;
#[rpc]
pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi;
#[rpc(client, server)]
pub trait TransactionPaymentApi<BlockHash, ResponseType> {
#[rpc(name = "payment_queryInfo")]
fn query_info(&self, encoded_xt: Bytes, at: Option<BlockHash>) -> Result<ResponseType>;
#[rpc(name = "payment_queryFeeDetails")]
#[method(name = "payment_queryInfo")]
fn query_info(&self, encoded_xt: Bytes, at: Option<BlockHash>) -> RpcResult<ResponseType>;
#[method(name = "payment_queryFeeDetails")]
fn query_fee_details(
&self,
encoded_xt: Bytes,
at: Option<BlockHash>,
) -> Result<FeeDetails<NumberOrHex>>;
) -> RpcResult<FeeDetails<NumberOrHex>>;
}
/// A struct that implements the [`TransactionPaymentApi`].
pub struct TransactionPayment<C, P> {
/// Provides RPC methods to query a dispatchable's class, weight and fee.
pub struct TransactionPaymentRpc<C, P> {
/// Shared reference to the client.
client: Arc<C>,
_marker: std::marker::PhantomData<P>,
}
impl<C, P> TransactionPayment<C, P> {
/// Create new `TransactionPayment` with the given reference to the client.
impl<C, P> TransactionPaymentRpc<C, P> {
/// Creates a new instance of the TransactionPaymentRpc helper.
pub fn new(client: Arc<C>) -> Self {
Self { client, _marker: Default::default() }
}
@@ -66,8 +72,8 @@ pub enum Error {
RuntimeError,
}
impl From<Error> for i64 {
fn from(e: Error) -> i64 {
impl From<Error> for i32 {
fn from(e: Error) -> i32 {
match e {
Error::RuntimeError => 1,
Error::DecodeError => 2,
@@ -75,66 +81,75 @@ impl From<Error> for i64 {
}
}
impl<C, Block, Balance> TransactionPaymentApi<<Block as BlockT>::Hash, RuntimeDispatchInfo<Balance>>
for TransactionPayment<C, Block>
#[async_trait]
impl<C, Block, Balance>
TransactionPaymentApiServer<<Block as BlockT>::Hash, RuntimeDispatchInfo<Balance>>
for TransactionPaymentRpc<C, Block>
where
Block: BlockT,
C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
C: ProvideRuntimeApi<Block> + HeaderBackend<Block> + Send + Sync + 'static,
C::Api: TransactionPaymentRuntimeApi<Block, Balance>,
Balance: Codec + MaybeDisplay + Copy + TryInto<NumberOrHex>,
Balance: Codec + MaybeDisplay + Copy + TryInto<NumberOrHex> + Send + Sync + 'static,
{
fn query_info(
&self,
encoded_xt: Bytes,
at: Option<<Block as BlockT>::Hash>,
) -> Result<RuntimeDispatchInfo<Balance>> {
at: Option<Block::Hash>,
) -> RpcResult<RuntimeDispatchInfo<Balance>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
let encoded_len = encoded_xt.len() as u32;
let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::DecodeError.into()),
message: "Unable to query dispatch info.".into(),
data: Some(format!("{:?}", e).into()),
let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::DecodeError.into(),
"Unable to query dispatch info.",
Some(format!("{:?}", e)),
))
})?;
api.query_info(&at, uxt, encoded_len).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query dispatch info.".into(),
data: Some(e.to_string().into()),
api.query_info(&at, uxt, encoded_len).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to query dispatch info.",
Some(e.to_string()),
))
.into()
})
}
fn query_fee_details(
&self,
encoded_xt: Bytes,
at: Option<<Block as BlockT>::Hash>,
) -> Result<FeeDetails<NumberOrHex>> {
at: Option<Block::Hash>,
) -> RpcResult<FeeDetails<NumberOrHex>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
let encoded_len = encoded_xt.len() as u32;
let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::DecodeError.into()),
message: "Unable to query fee details.".into(),
data: Some(format!("{:?}", e).into()),
let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::DecodeError.into(),
"Unable to query fee details.",
Some(format!("{:?}", e)),
))
})?;
let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query fee details.".into(),
data: Some(e.to_string().into()),
let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to query fee details.",
Some(e.to_string()),
))
})?;
let try_into_rpc_balance = |value: Balance| {
value.try_into().map_err(|_| RpcError {
code: ErrorCode::InvalidParams,
message: format!("{} doesn't fit in NumberOrHex representation", value),
data: None,
value.try_into().map_err(|_| {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!("{} doesn't fit in NumberOrHex representation", value),
None::<()>,
)))
})
};
+36 -68
View File
@@ -28,7 +28,7 @@ pub use sc_client_api::{
};
pub use sc_client_db::{self, Backend};
pub use sc_executor::{self, NativeElseWasmExecutor, WasmExecutionMethod};
pub use sc_service::{client, RpcHandlers, RpcSession};
pub use sc_service::{client, RpcHandlers};
pub use sp_consensus;
pub use sp_keyring::{
ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring,
@@ -37,10 +37,7 @@ pub use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
pub use sp_runtime::{Storage, StorageChild};
pub use sp_state_machine::ExecutionStrategy;
use futures::{
future::{Future, FutureExt},
stream::StreamExt,
};
use futures::{future::Future, stream::StreamExt};
use sc_client_api::BlockchainEvents;
use sc_service::client::{ClientConfig, LocalCallExecutor};
use serde::Deserialize;
@@ -297,16 +294,14 @@ impl<Block: BlockT, D, Backend, G: GenesisInit>
/// The output of an RPC transaction.
pub struct RpcTransactionOutput {
/// The output string of the transaction if any.
pub result: Option<String>,
/// The session object.
pub session: RpcSession,
pub result: String,
/// An async receiver if data will be returned via a callback.
pub receiver: futures::channel::mpsc::UnboundedReceiver<String>,
}
impl std::fmt::Debug for RpcTransactionOutput {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "RpcTransactionOutput {{ result: {:?}, session, receiver }}", self.result)
write!(f, "RpcTransactionOutput {{ result: {:?}, receiver }}", self.result)
}
}
@@ -328,56 +323,51 @@ impl std::fmt::Display for RpcTransactionError {
}
/// An extension trait for `RpcHandlers`.
#[async_trait::async_trait]
pub trait RpcHandlersExt {
/// Send a transaction through the RpcHandlers.
fn send_transaction(
async fn send_transaction(
&self,
extrinsic: OpaqueExtrinsic,
) -> Pin<Box<dyn Future<Output = Result<RpcTransactionOutput, RpcTransactionError>> + Send>>;
) -> Result<RpcTransactionOutput, RpcTransactionError>;
}
#[async_trait::async_trait]
impl RpcHandlersExt for RpcHandlers {
fn send_transaction(
async fn send_transaction(
&self,
extrinsic: OpaqueExtrinsic,
) -> Pin<Box<dyn Future<Output = Result<RpcTransactionOutput, RpcTransactionError>> + Send>> {
let (tx, rx) = futures::channel::mpsc::unbounded();
let mem = RpcSession::new(tx);
Box::pin(
self.rpc_query(
&mem,
&format!(
r#"{{
) -> Result<RpcTransactionOutput, RpcTransactionError> {
let (result, rx) = self
.rpc_query(&format!(
r#"{{
"jsonrpc": "2.0",
"method": "author_submitExtrinsic",
"params": ["0x{}"],
"id": 0
}}"#,
hex::encode(extrinsic.encode())
),
)
.map(move |result| parse_rpc_result(result, mem, rx)),
)
hex::encode(extrinsic.encode())
))
.await
.expect("valid JSON-RPC request object; qed");
parse_rpc_result(result, rx)
}
}
pub(crate) fn parse_rpc_result(
result: Option<String>,
session: RpcSession,
result: String,
receiver: futures::channel::mpsc::UnboundedReceiver<String>,
) -> Result<RpcTransactionOutput, RpcTransactionError> {
if let Some(ref result) = result {
let json: serde_json::Value =
serde_json::from_str(result).expect("the result can only be a JSONRPC string; qed");
let error = json.as_object().expect("JSON result is always an object; qed").get("error");
let json: serde_json::Value =
serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed");
let error = json.as_object().expect("JSON result is always an object; qed").get("error");
if let Some(error) = error {
return Err(serde_json::from_value(error.clone())
.expect("the JSONRPC result's error is always valid; qed"))
}
if let Some(error) = error {
return Err(serde_json::from_value(error.clone())
.expect("the JSONRPC result's error is always valid; qed"))
}
Ok(RpcTransactionOutput { result, session, receiver })
Ok(RpcTransactionOutput { result, receiver })
}
/// An extension trait for `BlockchainEvents`.
@@ -418,40 +408,23 @@ where
#[cfg(test)]
mod tests {
use sc_service::RpcSession;
fn create_session_and_receiver(
) -> (RpcSession, futures::channel::mpsc::UnboundedReceiver<String>) {
let (tx, rx) = futures::channel::mpsc::unbounded();
let mem = RpcSession::new(tx.into());
(mem, rx)
}
#[test]
fn parses_error_properly() {
let (mem, rx) = create_session_and_receiver();
assert!(super::parse_rpc_result(None, mem, rx).is_ok());
let (mem, rx) = create_session_and_receiver();
let (_, rx) = futures::channel::mpsc::unbounded();
assert!(super::parse_rpc_result(
Some(
r#"{
r#"{
"jsonrpc": "2.0",
"result": 19,
"id": 1
}"#
.to_string()
),
mem,
.to_string(),
rx
)
.is_ok(),);
.is_ok());
let (mem, rx) = create_session_and_receiver();
let (_, rx) = futures::channel::mpsc::unbounded();
let error = super::parse_rpc_result(
Some(
r#"{
r#"{
"jsonrpc": "2.0",
"error": {
"code": -32601,
@@ -459,9 +432,7 @@ mod tests {
},
"id": 1
}"#
.to_string(),
),
mem,
.to_string(),
rx,
)
.unwrap_err();
@@ -469,10 +440,9 @@ mod tests {
assert_eq!(error.message, "Method not found");
assert!(error.data.is_none());
let (mem, rx) = create_session_and_receiver();
let (_, rx) = futures::channel::mpsc::unbounded();
let error = super::parse_rpc_result(
Some(
r#"{
r#"{
"jsonrpc": "2.0",
"error": {
"code": -32601,
@@ -481,9 +451,7 @@ mod tests {
},
"id": 1
}"#
.to_string(),
),
mem,
.to_string(),
rx,
)
.unwrap_err();
+13
View File
@@ -176,6 +176,19 @@ impl serde::Serialize for Extrinsic {
}
}
// rustc can't deduce this trait bound https://github.com/rust-lang/rust/issues/48214
#[cfg(feature = "std")]
impl<'a> serde::Deserialize<'a> for Extrinsic {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let r = sp_core::bytes::deserialize(de)?;
Decode::decode(&mut &r[..])
.map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e)))
}
}
impl BlindCheckable for Extrinsic {
type Checked = Self;
@@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
env_logger = "0.9"
jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] }
jsonrpsee = { version = "0.12.0", features = ["ws-client", "macros"] }
log = "0.4.16"
serde = "1.0.136"
serde_json = "1.0"
@@ -25,9 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" }
sp-trie = { path = "../../../../primitives/trie" }
trie-db = { version = "0.23.1" }
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpsee = { version = "0.12.0", features = ["server", "macros"] }
# Substrate Dependencies
sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" }
@@ -17,8 +17,11 @@
//! Rpc for state migration.
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpsee::{
core::{Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
types::error::{CallError, ErrorCode, ErrorObject},
};
use sc_rpc_api::DenyUnsafe;
use serde::{Deserialize, Serialize};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
@@ -107,15 +110,15 @@ pub struct MigrationStatusResult {
}
/// Migration RPC methods.
#[rpc]
#[rpc(server)]
pub trait StateMigrationApi<BlockHash> {
/// Check current migration state.
///
/// This call is performed locally without submitting any transactions. Thus executing this
/// won't change any state. Nonetheless it is a VERY costy call that should be
/// only exposed to trusted peers.
#[rpc(name = "state_trieMigrationStatus")]
fn call(&self, at: Option<BlockHash>) -> Result<MigrationStatusResult>;
#[method(name = "state_trieMigrationStatus")]
fn call(&self, at: Option<BlockHash>) -> RpcResult<MigrationStatusResult>;
}
/// An implementation of state migration specific RPC methods.
@@ -133,16 +136,14 @@ impl<C, B, BA> MigrationRpc<C, B, BA> {
}
}
impl<C, B, BA> StateMigrationApi<<B as BlockT>::Hash> for MigrationRpc<C, B, BA>
impl<C, B, BA> StateMigrationApiServer<<B as BlockT>::Hash> for MigrationRpc<C, B, BA>
where
B: BlockT,
C: Send + Sync + 'static + sc_client_api::HeaderBackend<B>,
BA: 'static + sc_client_api::backend::Backend<B>,
{
fn call(&self, at: Option<<B as BlockT>::Hash>) -> Result<MigrationStatusResult> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return Err(err.into())
}
fn call(&self, at: Option<<B as BlockT>::Hash>) -> RpcResult<MigrationStatusResult> {
self.deny_unsafe.check_if_safe()?;
let block_id = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
let state = self.backend.state_at(block_id).map_err(error_into_rpc_err)?;
@@ -155,10 +156,10 @@ where
}
}
fn error_into_rpc_err(err: impl std::fmt::Display) -> Error {
Error {
code: ErrorCode::InternalError,
message: "Error while checking migration state".into(),
data: Some(err.to_string().into()),
}
fn error_into_rpc_err(err: impl std::fmt::Display) -> JsonRpseeError {
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
ErrorCode::InternalError.code(),
"Error while checking migration state",
Some(err.to_string()),
)))
}
+2 -1
View File
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.21"
jsonrpc-client-transports = { version = "18.0.0", features = ["http"] }
jsonrpsee = { version = "0.12.0", features = ["jsonrpsee-types"] }
serde = "1"
frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" }
sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" }
@@ -25,5 +25,6 @@ sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" }
[dev-dependencies]
scale-info = "2.0.1"
jsonrpsee = { version = "0.12.0", features = ["ws-client", "jsonrpsee-types"] }
tokio = "1.17.0"
frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" }
+29 -27
View File
@@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Combines [sc_rpc_api::state::StateClient] with [frame_support::storage::generator] traits
//! Combines [sc_rpc_api::state::StateApiClient] with [frame_support::storage::generator] traits
//! to provide strongly typed chain state queries over rpc.
#![warn(missing_docs)]
@@ -23,29 +23,26 @@
use codec::{DecodeAll, FullCodec, FullEncode};
use core::marker::PhantomData;
use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue};
use jsonrpc_client_transports::RpcError;
use sc_rpc_api::state::StateClient;
use jsonrpsee::core::Error as RpcError;
use sc_rpc_api::state::StateApiClient;
use serde::{de::DeserializeOwned, Serialize};
use sp_storage::{StorageData, StorageKey};
/// A typed query on chain state usable from an RPC client.
///
/// ```no_run
/// # use jsonrpc_client_transports::RpcError;
/// # use jsonrpc_client_transports::transports::http;
/// # use jsonrpsee::core::Error as RpcError;
/// # use jsonrpsee::ws_client::WsClientBuilder;
/// # use codec::Encode;
/// # use frame_support::{decl_storage, decl_module};
/// # use substrate_frame_rpc_support::StorageQuery;
/// # use frame_system::Config;
/// # use sc_rpc_api::state::StateClient;
/// # use sc_rpc_api::state::StateApiClient;
/// #
/// # // Hash would normally be <TestRuntime as frame_system::Config>::Hash, but we don't have
/// # // frame_system::Config implemented for TestRuntime. Here we just pretend.
/// # type Hash = ();
/// #
/// # fn main() -> Result<(), RpcError> {
/// # tokio::runtime::Runtime::new().unwrap().block_on(test())
/// # }
/// #
/// # struct TestRuntime;
/// #
@@ -66,24 +63,25 @@ use sp_storage::{StorageData, StorageKey};
/// }
/// }
///
/// # async fn test() -> Result<(), RpcError> {
/// let conn = http::connect("http://[::1]:9933").await?;
/// let cl = StateClient::<Hash>::new(conn);
/// #[tokio::main]
/// async fn main() -> Result<(), RpcError> {
/// let cl = WsClientBuilder::default().build("ws://[::1]:9944").await?;
///
/// let q = StorageQuery::value::<LastActionId>();
/// let _: Option<u64> = q.get(&cl, None).await?;
/// let q = StorageQuery::value::<LastActionId>();
/// let hash = None::<Hash>;
/// let _: Option<u64> = q.get(&cl, hash).await?;
///
/// let q = StorageQuery::map::<Voxels, _>((0, 0, 0));
/// let _: Option<Block> = q.get(&cl, None).await?;
/// let q = StorageQuery::map::<Voxels, _>((0, 0, 0));
/// let _: Option<Block> = q.get(&cl, hash).await?;
///
/// let q = StorageQuery::map::<Actions, _>(12);
/// let _: Option<Loc> = q.get(&cl, None).await?;
/// let q = StorageQuery::map::<Actions, _>(12);
/// let _: Option<Loc> = q.get(&cl, hash).await?;
///
/// let q = StorageQuery::double_map::<Prefab, _, _>(3, (0, 0, 0));
/// let _: Option<Block> = q.get(&cl, None).await?;
/// #
/// # Ok(())
/// # }
/// let q = StorageQuery::double_map::<Prefab, _, _>(3, (0, 0, 0));
/// let _: Option<Block> = q.get(&cl, hash).await?;
///
/// Ok(())
/// }
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct StorageQuery<V> {
@@ -120,14 +118,18 @@ impl<V: FullCodec> StorageQuery<V> {
///
/// block_index indicates the block for which state will be queried. A value of None indicates
/// the latest block.
pub async fn get<Hash: Send + Sync + 'static + DeserializeOwned + Serialize>(
pub async fn get<Hash, StateClient>(
self,
state_client: &StateClient<Hash>,
state_client: &StateClient,
block_index: Option<Hash>,
) -> Result<Option<V>, RpcError> {
) -> Result<Option<V>, RpcError>
where
Hash: Send + Sync + 'static + DeserializeOwned + Serialize,
StateClient: StateApiClient<Hash> + Sync,
{
let opt: Option<StorageData> = state_client.storage(self.key, block_index).await?;
opt.map(|encoded| V::decode_all(&mut &encoded.0[..]))
.transpose()
.map_err(|decode_err| RpcError::Other(Box::new(decode_err)))
.map_err(|decode_err| RpcError::Custom(decode_err.to_string()))
}
}
+4 -3
View File
@@ -13,11 +13,10 @@ readme = "README.md"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
serde_json = "1"
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpsee = { version = "0.12.0", features = ["server"] }
futures = "0.3.21"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
log = "0.4.16"
frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" }
sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" }
@@ -31,5 +30,7 @@ sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" }
[dev-dependencies]
sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" }
tokio = "1.17.0"
assert_matches = "1.3.0"
sp-tracing = { version = "5.0.0", path = "../../../../primitives/tracing" }
substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" }
+109 -114
View File
@@ -17,12 +17,15 @@
//! System FRAME specific RPC methods.
use std::sync::Arc;
use std::{fmt::Display, sync::Arc};
use codec::{self, Codec, Decode, Encode};
use jsonrpsee::{
core::{async_trait, RpcResult},
proc_macros::rpc,
types::error::{CallError, ErrorObject},
};
use codec::{Codec, Decode, Encode};
use futures::FutureExt;
use jsonrpc_core::{Error as RpcError, ErrorCode};
use jsonrpc_derive::rpc;
use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::{InPoolTransaction, TransactionPool};
use sp_api::ApiExt;
@@ -31,26 +34,22 @@ use sp_blockchain::HeaderBackend;
use sp_core::{hexdisplay::HexDisplay, Bytes};
use sp_runtime::{generic::BlockId, legacy, traits};
pub use self::gen_client::Client as SystemClient;
pub use frame_system_rpc_runtime_api::AccountNonceApi;
/// Future that resolves to account nonce.
type FutureResult<T> = jsonrpc_core::BoxFuture<Result<T, RpcError>>;
/// System RPC methods.
#[rpc]
#[rpc(client, server)]
pub trait SystemApi<BlockHash, AccountId, Index> {
/// Returns the next valid index (aka nonce) for given account.
///
/// This method takes into consideration all pending transactions
/// currently in the pool and if no transactions are found in the pool
/// it fallbacks to query the index from the runtime (aka. state nonce).
#[rpc(name = "system_accountNextIndex", alias("account_nextIndex"))]
fn nonce(&self, account: AccountId) -> FutureResult<Index>;
#[method(name = "system_accountNextIndex", aliases = ["account_nextIndex"])]
async fn nonce(&self, account: AccountId) -> RpcResult<Index>;
/// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult.
#[rpc(name = "system_dryRun", alias("system_dryRunAt"))]
fn dry_run(&self, extrinsic: Bytes, at: Option<BlockHash>) -> FutureResult<Bytes>;
#[method(name = "system_dryRun", aliases = ["system_dryRunAt"])]
async fn dry_run(&self, extrinsic: Bytes, at: Option<BlockHash>) -> RpcResult<Bytes>;
}
/// Error type of this RPC api.
@@ -61,8 +60,8 @@ pub enum Error {
RuntimeError,
}
impl From<Error> for i64 {
fn from(e: Error) -> i64 {
impl From<Error> for i32 {
fn from(e: Error) -> i32 {
match e {
Error::RuntimeError => 1,
Error::DecodeError => 2,
@@ -71,22 +70,23 @@ impl From<Error> for i64 {
}
/// An implementation of System-specific RPC methods on full client.
pub struct FullSystem<P: TransactionPool, C, B> {
pub struct SystemRpc<P: TransactionPool, C, B> {
client: Arc<C>,
pool: Arc<P>,
deny_unsafe: DenyUnsafe,
_marker: std::marker::PhantomData<B>,
}
impl<P: TransactionPool, C, B> FullSystem<P, C, B> {
impl<P: TransactionPool, C, B> SystemRpc<P, C, B> {
/// Create new `FullSystem` given client and transaction pool.
pub fn new(client: Arc<C>, pool: Arc<P>, deny_unsafe: DenyUnsafe) -> Self {
FullSystem { client, pool, deny_unsafe, _marker: Default::default() }
Self { client, pool, deny_unsafe, _marker: Default::default() }
}
}
impl<P, C, Block, AccountId, Index> SystemApi<<Block as traits::Block>::Hash, AccountId, Index>
for FullSystem<P, C, Block>
#[async_trait]
impl<P, C, Block, AccountId, Index>
SystemApiServer<<Block as traits::Block>::Hash, AccountId, Index> for SystemRpc<P, C, Block>
where
C: sp_api::ProvideRuntimeApi<Block>,
C: HeaderBackend<Block>,
@@ -95,88 +95,83 @@ where
C::Api: BlockBuilder<Block>,
P: TransactionPool + 'static,
Block: traits::Block,
AccountId: Clone + std::fmt::Display + Codec,
Index: Clone + std::fmt::Display + Codec + Send + traits::AtLeast32Bit + 'static,
AccountId: Clone + Display + Codec + Send + 'static,
Index: Clone + Display + Codec + Send + traits::AtLeast32Bit + 'static,
{
fn nonce(&self, account: AccountId) -> FutureResult<Index> {
let get_nonce = || {
let api = self.client.runtime_api();
let best = self.client.info().best_hash;
let at = BlockId::hash(best);
async fn nonce(&self, account: AccountId) -> RpcResult<Index> {
let api = self.client.runtime_api();
let best = self.client.info().best_hash;
let at = BlockId::hash(best);
let nonce = api.account_nonce(&at, account.clone()).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query nonce.".into(),
data: Some(e.to_string().into()),
})?;
Ok(adjust_nonce(&*self.pool, account, nonce))
};
let res = get_nonce();
async move { res }.boxed()
let nonce = api.account_nonce(&at, account.clone()).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to query nonce.",
Some(e.to_string()),
))
})?;
Ok(adjust_nonce(&*self.pool, account, nonce))
}
fn dry_run(
async fn dry_run(
&self,
extrinsic: Bytes,
at: Option<<Block as traits::Block>::Hash>,
) -> FutureResult<Bytes> {
if let Err(err) = self.deny_unsafe.check_if_safe() {
return async move { Err(err.into()) }.boxed()
}
) -> RpcResult<Bytes> {
self.deny_unsafe.check_if_safe()?;
let api = self.client.runtime_api();
let at = BlockId::<Block>::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let dry_run = || {
let api = self.client.runtime_api();
let at = BlockId::<Block>::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let uxt: <Block as traits::Block>::Extrinsic =
Decode::decode(&mut &*extrinsic).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::DecodeError.into(),
"Unable to dry run extrinsic",
Some(e.to_string()),
))
})?;
let uxt: <Block as traits::Block>::Extrinsic = Decode::decode(&mut &*extrinsic)
.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::DecodeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(e.to_string().into()),
})?;
let api_version = api
.api_version::<dyn BlockBuilder<Block>>(&at)
.map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to dry run extrinsic.",
Some(e.to_string()),
))
})?
.ok_or_else(|| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to dry run extrinsic.",
Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", at)),
))
})?;
let api_version = api
.api_version::<dyn BlockBuilder<Block>>(&at)
.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(e.to_string().into()),
let result = if api_version < 6 {
#[allow(deprecated)]
api.apply_extrinsic_before_version_6(&at, uxt)
.map(legacy::byte_sized_error::convert_to_latest)
.map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to dry run extrinsic.",
Some(e.to_string()),
))
})?
.ok_or_else(|| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(
format!("Could not find `BlockBuilder` api for block `{:?}`.", at).into(),
),
})?;
let result = if api_version < 6 {
#[allow(deprecated)]
api.apply_extrinsic_before_version_6(&at, uxt)
.map(legacy::byte_sized_error::convert_to_latest)
.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(e.to_string().into()),
})?
} else {
api.apply_extrinsic(&at, uxt).map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to dry run extrinsic.".into(),
data: Some(e.to_string().into()),
})?
};
Ok(Encode::encode(&result).into())
} else {
api.apply_extrinsic(&at, uxt).map_err(|e| {
CallError::Custom(ErrorObject::owned(
Error::RuntimeError.into(),
"Unable to dry run extrinsic.",
Some(e.to_string()),
))
})?
};
let res = dry_run();
async move { res }.boxed()
Ok(Encode::encode(&result).into())
}
}
@@ -220,7 +215,9 @@ where
mod tests {
use super::*;
use assert_matches::assert_matches;
use futures::executor::block_on;
use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError};
use sc_transaction_pool::BasicPool;
use sp_runtime::{
transaction_validity::{InvalidTransaction, TransactionValidityError},
@@ -228,8 +225,8 @@ mod tests {
};
use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring};
#[test]
fn should_return_next_nonce_for_some_account() {
#[tokio::test]
async fn should_return_next_nonce_for_some_account() {
sp_tracing::try_init_simple();
// given
@@ -254,17 +251,17 @@ mod tests {
let ext1 = new_transaction(1);
block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap();
let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes);
let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes);
// when
let nonce = accounts.nonce(AccountKeyring::Alice.into());
let nonce = accounts.nonce(AccountKeyring::Alice.into()).await;
// then
assert_eq!(block_on(nonce).unwrap(), 2);
assert_eq!(nonce.unwrap(), 2);
}
#[test]
fn dry_run_should_deny_unsafe() {
#[tokio::test]
async fn dry_run_should_deny_unsafe() {
sp_tracing::try_init_simple();
// given
@@ -273,17 +270,17 @@ mod tests {
let pool =
BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone());
let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes);
let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes);
// when
let res = accounts.dry_run(vec![].into(), None);
// then
assert_eq!(block_on(res), Err(sc_rpc_api::UnsafeRpcError.into()));
let res = accounts.dry_run(vec![].into(), None).await;
assert_matches!(res, Err(JsonRpseeError::Call(CallError::Custom(e))) => {
assert!(e.message().contains("RPC call is unsafe to be called externally"));
});
}
#[test]
fn dry_run_should_work() {
#[tokio::test]
async fn dry_run_should_work() {
sp_tracing::try_init_simple();
// given
@@ -292,7 +289,7 @@ mod tests {
let pool =
BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone());
let accounts = FullSystem::new(client, pool, DenyUnsafe::No);
let accounts = SystemRpc::new(client, pool, DenyUnsafe::No);
let tx = Transfer {
from: AccountKeyring::Alice.into(),
@@ -303,16 +300,15 @@ mod tests {
.into_signed_tx();
// when
let res = accounts.dry_run(tx.encode().into(), None);
let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful");
// then
let bytes = block_on(res).unwrap().0;
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap();
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap();
assert_eq!(apply_res, Ok(Ok(())));
}
#[test]
fn dry_run_should_indicate_error() {
#[tokio::test]
async fn dry_run_should_indicate_error() {
sp_tracing::try_init_simple();
// given
@@ -321,7 +317,7 @@ mod tests {
let pool =
BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone());
let accounts = FullSystem::new(client, pool, DenyUnsafe::No);
let accounts = SystemRpc::new(client, pool, DenyUnsafe::No);
let tx = Transfer {
from: AccountKeyring::Alice.into(),
@@ -332,11 +328,10 @@ mod tests {
.into_signed_tx();
// when
let res = accounts.dry_run(tx.encode().into(), None);
let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful");
// then
let bytes = block_on(res).unwrap().0;
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap();
let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap();
assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)));
}
}
@@ -14,12 +14,12 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
clap = { version = "3.1.6", features = ["derive"] }
jsonrpsee = { version = "0.10.1", default-features = false, features = ["ws-client"] }
log = "0.4.16"
parity-scale-codec = "3.0.0"
serde = "1.0.136"
zstd = { version = "0.10.0", default-features = false }
remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" }
jsonrpsee = { version = "0.12.0", default-features = false, features = ["ws-client"] }
sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" }
sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" }
sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" }