Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a cache for state_getKeysPaged requests #361

Merged
merged 2 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions light-base/src/json_rpc_service/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<methods::HexString>), Vec<Vec<u8>>, fnv::FnvBuildHasher>,
}

pub(super) fn start<TPlat: Platform>(
Expand Down Expand Up @@ -249,6 +256,10 @@ pub(super) fn start<TPlat: Platform>(
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),
Expand Down
46 changes: 43 additions & 3 deletions light-base/src/json_rpc_service/background/state_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,33 @@ impl<TPlat: Platform> Background<TPlat> {
),
};

// 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::<Vec<_>>();

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 {
Expand Down Expand Up @@ -1080,7 +1107,7 @@ impl<TPlat: Platform> Background<TPlat> {
.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),
Expand All @@ -1092,11 +1119,24 @@ impl<TPlat: Platform> Background<TPlat> {
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::<Vec<_>>();

// 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(
Expand Down
1 change: 1 addition & 0 deletions wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down