mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 03:01:07 +00:00
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:
Generated
+509
-1194
File diff suppressed because it is too large
Load Diff
@@ -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" }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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/" }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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::<()>,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<()>;
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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(¬ification.method),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_success(output: &Option<jsonrpc_core::Output>) -> bool {
|
||||
matches!(output, Some(jsonrpc_core::Output::Success(..)))
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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::<()>,
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
@@ -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::*;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(¤t_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()
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::<()>,
|
||||
)))
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user