Skip to content

Commit

Permalink
Add a cache for state_getKeysPaged requests (#361)
Browse files Browse the repository at this point in the history
* Add a cache for state_getKeysPaged requests

* PR link
  • Loading branch information
tomaka authored Mar 28, 2023
1 parent 02038a9 commit 7bf22e6
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 3 deletions.
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

0 comments on commit 7bf22e6

Please sign in to comment.