diff --git a/light-base/src/json_rpc_service/background.rs b/light-base/src/json_rpc_service/background.rs index 7971ed6bfe..0e4a8cd2f9 100644 --- a/light-base/src/json_rpc_service/background.rs +++ b/light-base/src/json_rpc_service/background.rs @@ -215,6 +215,13 @@ struct Cache { >, fnv::FnvBuildHasher, >, + + /// When `state_getKeysPaged` is called and the response is truncated, the response is + /// inserted in this cache. The API user is likely to call `state_getKeysPaged` again with + /// the same parameters, in which case we hit the cache and avoid the networking requests. + /// The keys are `(block_hash, prefix)` and values are list of keys. + state_get_keys_paged: + lru::LruCache<([u8; 32], Option), Vec>, fnv::FnvBuildHasher>, } pub(super) fn start( @@ -249,6 +256,10 @@ pub(super) fn start( NonZeroUsize::new(32).unwrap(), Default::default(), ), + state_get_keys_paged: lru::LruCache::with_hasher( + NonZeroUsize::new(2).unwrap(), + Default::default(), + ), }), genesis_block_hash: config.genesis_block_hash, printed_legacy_json_rpc_warning: atomic::AtomicBool::new(false), diff --git a/light-base/src/json_rpc_service/background/state_chain.rs b/light-base/src/json_rpc_service/background/state_chain.rs index 9bd40eaf4c..7c58442045 100644 --- a/light-base/src/json_rpc_service/background/state_chain.rs +++ b/light-base/src/json_rpc_service/background/state_chain.rs @@ -1052,6 +1052,33 @@ impl Background { ), }; + // Because the user is likely to call this function multiple times in a row with the exact + // same parameters, we store the untruncated responses in a cache. Check if we hit the + // cache. + if let Some(keys) = self + .cache + .lock() + .await + .state_get_keys_paged + .get(&(hash, prefix.clone())) + { + let out = keys + .iter() + .filter(|k| start_key.as_ref().map_or(true, |start| *k >= &start.0)) // TODO: not sure if start should be in the set or not? + .cloned() + .map(methods::HexString) + .take(usize::try_from(count).unwrap_or(usize::max_value())) + .collect::>(); + + self.requests_subscriptions + .respond( + request_id.1, + methods::Response::state_getKeysPaged(out).to_json_response(request_id.0), + ) + .await; + return; + } + // Obtain the state trie root and height of the requested block. // This is necessary to perform network storage queries. let (state_root, block_number) = match self.state_trie_root_hash(&hash).await { @@ -1080,7 +1107,7 @@ impl Background { .storage_prefix_keys_query( block_number, &hash, - &prefix.unwrap().0, // TODO: don't unwrap! what is this Option? + &prefix.as_ref().unwrap().0, // TODO: don't unwrap! what is this Option? &state_root, 3, Duration::from_secs(12), @@ -1092,11 +1119,24 @@ impl Background { Ok(keys) => { // TODO: instead of requesting all keys with that prefix from the network, pass `start_key` to the network service let out = keys - .into_iter() - .filter(|k| start_key.as_ref().map_or(true, |start| k >= &start.0)) // TODO: not sure if start should be in the set or not? + .iter() + .filter(|k| start_key.as_ref().map_or(true, |start| *k >= &start.0)) // TODO: not sure if start should be in the set or not? + .cloned() // TODO: instead of cloning, make `Response::state_getKeysPaged` accept references .map(methods::HexString) .take(usize::try_from(count).unwrap_or(usize::max_value())) .collect::>(); + + // If the returned response is somehow truncated, it is very likely that the + // JSON-RPC client will call the function again with the exact same parameters. + // Thus, store the results in a cache. + if out.len() != keys.len() { + self.cache + .lock() + .await + .state_get_keys_paged + .push((hash, prefix), keys); + } + methods::Response::state_getKeysPaged(out).to_json_response(request_id.0) } Err(error) => json_rpc::parse::build_error_response( diff --git a/wasm-node/CHANGELOG.md b/wasm-node/CHANGELOG.md index 89fc144f43..a27b145fcf 100644 --- a/wasm-node/CHANGELOG.md +++ b/wasm-node/CHANGELOG.md @@ -9,6 +9,7 @@ - Rename `/webrtc` to `/webrtc-direct` in multiaddresses, in accordance with the rest of the libp2p ecosystem. ([#326](https://github.com/smol-dot/smoldot/pull/326)) - Improved the ganularity of the tasks that handle JSON-RPC requests and libp2p connections. Smoldot now yields more often to the browser, reducing the chances and the severity of freezes during the rendering of the web page. ([#349](https://github.com/smol-dot/smoldot/pull/349)) - Smoldot is now compiled with the `bulk-memory-operations` and `sign-extensions-ops` WebAssembly features enabled. This is expected to considerably speed up its execution. The minimum version required to run smoldot is now Chrome 75, Firefox 79, NodeJS v12.5, and Deno v0.4. ([#356](https://github.com/smol-dot/smoldot/pull/356)) +- When the `state_getKeysPaged` JSON-RPC function is called, and the list of keys returned in the response is truncated (due to the `count` and `startKey` parameters), the rest of the keys are now put in a cache with the expectation that `state_getKeysPaged` is called again in order to obtain the rest of the keys. The `state_getKeysPaged` JSON-RPC function is unfortunately very often used by PolkadotJS despite being completely unsuitable for light clients. ([#361](https://github.com/smol-dot/smoldot/pull/361)) ### Fixed