Skip to content

Commit 978f907

Browse files
authored
Rewrite the light client JSON-RPC server (#1685)
* Don't spawn sub-tasks anymore * Prepare for refactor * Prepare match block for requests handling * Some functions copy-pasting * Restore transactions watching * `chain_getBlockHash` * `chain_getBlock` * Clean up modules * Move chain_head requests to main task * Misc work * Misc work * WIP * WIP * WIP * WIP * WIP * It compiles * WIP * WIP * WIP * WIP * It compiles * WIP * Implement best block correctly * WIP * Finish the runtime call related functions * Finish chain_getHeader * Don't freeze on `chain_getFinalizedHead` * Restore state_getKeysPaged full functionalities * Fix bad merge * WIP * Restore legacy API warning * Notify whether best block has changed when finalizing * Mention rewrite in CHANGELOG * Docfix * Misc changes and shrink_to_fit * Put the legacy subscription notified blocks in cache * Add tons of comments * DRY for chainHead_call finished path
1 parent 0aff19f commit 978f907

File tree

17 files changed

+5583
-5460
lines changed

17 files changed

+5583
-5460
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/chain/async_tree.rs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -852,40 +852,21 @@ where
852852
///
853853
/// # Panic
854854
///
855-
/// Panics if `node_to_finalize` or `new_best_block` aren't valid nodes.
856-
/// Panics if `new_best_block` is not a descendant of `node_to_finalize`.
855+
/// Panics if `node_to_finalize` isn't a valid node.
856+
/// Panics if the current input best block is not a descendant of `node_to_finalize`.
857857
///
858-
pub fn input_finalize(&mut self, node_to_finalize: NodeIndex, new_best_block: NodeIndex) {
858+
pub fn input_finalize(&mut self, node_to_finalize: NodeIndex) {
859859
// Make sure that `new_best_block` is a descendant of `node_to_finalize`,
860860
// otherwise the state of the tree will be corrupted.
861861
// This is checked with an `assert!` rather than a `debug_assert!`, as this constraint
862862
// is part of the public API of this method.
863863
assert!(self
864-
.non_finalized_blocks
865-
.is_ancestor(node_to_finalize, new_best_block));
864+
.input_best_block_index
865+
.map_or(false, |current_input_best| self
866+
.non_finalized_blocks
867+
.is_ancestor(node_to_finalize, current_input_best)));
866868

867869
self.input_finalized_index = Some(node_to_finalize);
868-
self.input_best_block_index = Some(new_best_block);
869-
870-
// If necessary, update the weight of the block.
871-
match &mut self
872-
.non_finalized_blocks
873-
.get_mut(new_best_block)
874-
.unwrap()
875-
.input_best_block_weight
876-
{
877-
w if *w == self.input_best_block_next_weight - 1 => {}
878-
w => {
879-
*w = self.input_best_block_next_weight;
880-
self.input_best_block_next_weight += 1;
881-
}
882-
}
883-
884-
// Minor sanity checks.
885-
debug_assert!(self
886-
.non_finalized_blocks
887-
.iter_unordered()
888-
.all(|(_, b)| b.input_best_block_weight < self.input_best_block_next_weight));
889870
}
890871

891872
/// Tries to update the output blocks to follow the input.
@@ -924,6 +905,7 @@ where
924905

925906
let mut pruned_blocks = Vec::new();
926907
let mut pruned_finalized = None;
908+
let mut best_output_block_updated = false;
927909

928910
for pruned in self.non_finalized_blocks.prune_ancestors(new_finalized) {
929911
debug_assert_ne!(Some(pruned.index), self.input_finalized_index);
@@ -935,6 +917,7 @@ where
935917
.map_or(false, |b| b == pruned.index)
936918
{
937919
self.output_best_block_index = None;
920+
best_output_block_updated = true;
938921
}
939922

940923
// Update `self.finalized_block_weight`.
@@ -1005,6 +988,7 @@ where
1005988
// Input best can be updated to the block being iterated.
1006989
current_runtime_service_best_block_weight = block.input_best_block_weight;
1007990
self.output_best_block_index = Some(node_index);
991+
best_output_block_updated = true;
1008992

1009993
// Continue looping, as there might be another block with an even
1010994
// higher weight.
@@ -1024,7 +1008,7 @@ where
10241008
user_data: pruned_finalized.user_data.user_data,
10251009
former_finalized_async_op_user_data,
10261010
pruned_blocks,
1027-
best_block_index: self.output_best_block_index,
1011+
best_output_block_updated,
10281012
});
10291013
}
10301014
}
@@ -1202,9 +1186,8 @@ pub enum OutputUpdate<TBl, TAsync> {
12021186
/// User data associated to the `async` operation of the previous finalized block.
12031187
former_finalized_async_op_user_data: TAsync,
12041188

1205-
/// Index of the best block after the finalization. `None` if the best block is the block
1206-
/// that has just been finalized.
1207-
best_block_index: Option<NodeIndex>,
1189+
/// `true` if the finalization has updated the best output block.
1190+
best_output_block_updated: bool,
12081191

12091192
/// Blocks that were a descendant of the former finalized block but not of the new
12101193
/// finalized block. These blocks are no longer part of the data structure.

light-base/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async-channel = { version = "2.2.0", default-features = false }
1717
async-lock = { version = "3.0.0", default-features = false }
1818
base64 = { version = "0.22.0", default-features = false, features = ["alloc"] }
1919
blake2-rfc = { version = "0.2.18", default-features = false }
20+
bs58 = { version = "0.5.0", default-features = false, features = ["alloc"] }
2021
derive_more = "0.99.17"
2122
either = { version = "1.9.0", default-features = false }
2223
event-listener = { version = "5.0.0", default-features = false }

light-base/src/json_rpc_service.rs

Lines changed: 41 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ use crate::{
4545
};
4646

4747
use alloc::{
48+
borrow::Cow,
49+
boxed::Box,
4850
format,
4951
string::{String, ToString as _},
5052
sync::Arc,
5153
};
52-
use core::num::NonZeroU32;
53-
use smoldot::{chain_spec, json_rpc::service};
54+
use core::{num::NonZeroU32, pin::Pin};
55+
use futures_lite::StreamExt as _;
5456

5557
/// Configuration for [`service()`].
5658
pub struct Config<TPlat: PlatformRef> {
@@ -81,6 +83,7 @@ pub struct Config<TPlat: PlatformRef> {
8183
///
8284
/// This parameter is necessary in order to prevent users from using up too much memory within
8385
/// the client.
86+
// TODO: unused at the moment
8487
pub max_parallel_requests: NonZeroU32,
8588
}
8689

@@ -93,22 +96,20 @@ pub struct Config<TPlat: PlatformRef> {
9396
pub fn service<TPlat: PlatformRef>(config: Config<TPlat>) -> (Frontend<TPlat>, ServicePrototype) {
9497
let log_target = format!("json-rpc-{}", config.log_name);
9598

96-
let (requests_processing_task, requests_responses_io) =
97-
service::client_main_task(service::Config {
98-
max_active_subscriptions: config.max_subscriptions,
99-
max_pending_requests: config.max_pending_requests,
100-
});
99+
let (requests_tx, requests_rx) = async_channel::bounded(32); // TODO: capacity?
100+
let (responses_tx, responses_rx) = async_channel::bounded(16); // TODO: capacity?
101101

102102
let frontend = Frontend {
103103
platform: config.platform,
104104
log_target: log_target.clone(),
105-
requests_responses_io: Arc::new(requests_responses_io),
105+
responses_rx: Arc::new(async_lock::Mutex::new(Box::pin(responses_rx))),
106+
requests_tx,
106107
};
107108

108109
let prototype = ServicePrototype {
109110
log_target,
110-
requests_processing_task,
111-
max_parallel_requests: config.max_parallel_requests,
111+
requests_rx,
112+
responses_tx,
112113
};
113114

114115
(frontend, prototype)
@@ -125,10 +126,12 @@ pub struct Frontend<TPlat> {
125126
/// See [`Config::platform`].
126127
platform: TPlat,
127128

128-
/// Sending requests and receiving responses.
129-
///
130-
/// Connected to the [`background`].
131-
requests_responses_io: Arc<service::SerializedRequestsIo>,
129+
/// How to send requests to the background task.
130+
requests_tx: async_channel::Sender<String>,
131+
132+
/// How to receive responses coming from the background task.
133+
// TODO: we use an Arc so that it's clonable, but that's questionnable
134+
responses_rx: Arc<async_lock::Mutex<Pin<Box<async_channel::Receiver<String>>>>>,
132135

133136
/// Target to use when emitting logs.
134137
log_target: String,
@@ -145,10 +148,7 @@ impl<TPlat: PlatformRef> Frontend<TPlat> {
145148
crate::util::truncated_str(json_rpc_request.chars().filter(|c| !c.is_control()), 250)
146149
.to_string();
147150

148-
match self
149-
.requests_responses_io
150-
.try_send_request(json_rpc_request)
151-
{
151+
match self.requests_tx.try_send(json_rpc_request) {
152152
Ok(()) => {
153153
log!(
154154
&self.platform,
@@ -159,16 +159,9 @@ impl<TPlat: PlatformRef> Frontend<TPlat> {
159159
);
160160
Ok(())
161161
}
162-
Err(service::TrySendRequestError {
163-
cause: service::TrySendRequestErrorCause::TooManyPendingRequests,
164-
request,
165-
}) => Err(HandleRpcError::TooManyPendingRequests {
166-
json_rpc_request: request,
162+
Err(err) => Err(HandleRpcError::TooManyPendingRequests {
163+
json_rpc_request: err.into_inner(),
167164
}),
168-
Err(service::TrySendRequestError {
169-
cause: service::TrySendRequestErrorCause::ClientMainTaskDestroyed,
170-
..
171-
}) => unreachable!(),
172165
}
173166
}
174167

@@ -177,9 +170,9 @@ impl<TPlat: PlatformRef> Frontend<TPlat> {
177170
/// If this function is called multiple times in parallel, the order in which the calls are
178171
/// responded to is unspecified.
179172
pub async fn next_json_rpc_response(&self) -> String {
180-
let message = match self.requests_responses_io.wait_next_response().await {
181-
Ok(m) => m,
182-
Err(service::WaitNextResponseError::ClientMainTaskDestroyed) => unreachable!(),
173+
let message = match self.responses_rx.lock().await.next().await {
174+
Some(m) => m,
175+
None => unreachable!(),
183176
};
184177

185178
log!(
@@ -197,20 +190,16 @@ impl<TPlat: PlatformRef> Frontend<TPlat> {
197190

198191
/// Prototype for a JSON-RPC service. Must be initialized using [`ServicePrototype::start`].
199192
pub struct ServicePrototype {
200-
/// Task processing the requests.
201-
///
202-
/// Later sent to the [`background`].
203-
requests_processing_task: service::ClientMainTask,
204-
205193
/// Target to use when emitting logs.
206194
log_target: String,
207195

208-
/// Value obtained through [`Config::max_parallel_requests`].
209-
max_parallel_requests: NonZeroU32,
196+
requests_rx: async_channel::Receiver<String>,
197+
198+
responses_tx: async_channel::Sender<String>,
210199
}
211200

212201
/// Configuration for a JSON-RPC service.
213-
pub struct StartConfig<'a, TPlat: PlatformRef> {
202+
pub struct StartConfig<TPlat: PlatformRef> {
214203
/// Access to the platform's capabilities.
215204
// TODO: redundant with Config above?
216205
pub platform: TPlat,
@@ -228,8 +217,14 @@ pub struct StartConfig<'a, TPlat: PlatformRef> {
228217
/// Service that provides a ready-to-be-called runtime for the current best block.
229218
pub runtime_service: Arc<runtime_service::RuntimeService<TPlat>>,
230219

231-
/// Specification of the chain.
232-
pub chain_spec: &'a chain_spec::ChainSpec,
220+
/// Name of the chain, as found in the chain specification.
221+
pub chain_name: String,
222+
/// Type of chain, as found in the chain specification.
223+
pub chain_ty: String,
224+
/// JSON-encoded properties of the chain, as found in the chain specification.
225+
pub chain_properties_json: String,
226+
/// Whether the chain is a live network. Found in the chain specification.
227+
pub chain_is_live: bool,
233228

234229
/// Value to return when the `system_name` RPC is called. Should be set to the name of the
235230
/// final executable.
@@ -240,33 +235,20 @@ pub struct StartConfig<'a, TPlat: PlatformRef> {
240235
pub system_version: String,
241236

242237
/// Hash of the genesis block of the chain.
243-
///
244-
/// > **Note**: This can be derived from a [`chain_spec::ChainSpec`]. While the
245-
/// > [`ServicePrototype::start`] function could in theory use the
246-
/// > [`StartConfig::chain_spec`] parameter to derive this value, doing so is quite
247-
/// > expensive. We prefer to require this value from the upper layer instead, as
248-
/// > it is most likely needed anyway.
249238
pub genesis_block_hash: [u8; 32],
250239

251240
/// Hash of the storage trie root of the genesis block of the chain.
252-
///
253-
/// > **Note**: This can be derived from a [`chain_spec::ChainSpec`]. While the
254-
/// > [`ServicePrototype::start`] function could in theory use the
255-
/// > [`StartConfig::chain_spec`] parameter to derive this value, doing so is quite
256-
/// > expensive. We prefer to require this value from the upper layer instead, as
257-
/// > it is most likely needed anyway.
258241
pub genesis_block_state_root: [u8; 32],
259242
}
260243

261244
impl ServicePrototype {
262245
/// Consumes this prototype and starts the service through [`PlatformRef::spawn_task`].
263-
pub fn start<TPlat: PlatformRef>(self, config: StartConfig<'_, TPlat>) {
264-
background::start(
265-
self.log_target.clone(),
266-
config,
267-
self.requests_processing_task,
268-
self.max_parallel_requests,
269-
)
246+
pub fn start<TPlat: PlatformRef>(self, config: StartConfig<TPlat>) {
247+
let platform = config.platform.clone();
248+
platform.spawn_task(
249+
Cow::Owned(self.log_target.clone()),
250+
background::run(self.log_target, config, self.requests_rx, self.responses_tx),
251+
);
270252
}
271253
}
272254

0 commit comments

Comments
 (0)