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

Rewrite state_get_runtime_version to use shared code #2736

Merged
merged 4 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
164 changes: 84 additions & 80 deletions bin/light-base/src/json_rpc_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,89 @@ impl<TPlat: Platform> Background<TPlat> {
Ok(result)
}

/// Obtain a lock to the runtime of the given block against the runtime service.
// TODO: return better error?
async fn runtime_lock<'a>(
self: &'a Arc<Self>,
block_hash: &[u8; 32],
) -> Result<runtime_service::RuntimeLock<'a, TPlat>, RuntimeCallError> {
let cache_lock = self.cache.lock().await;

// Try to find the block in the cache of recent blocks. Most of the time, the call target
// should be in there.
let lock = if cache_lock.recent_pinned_blocks.contains(block_hash) {
// The runtime service has the block pinned, meaning that we can ask the runtime
// service to perform the call.
self.runtime_service
.pinned_block_runtime_lock(cache_lock.subscription_id.clone().unwrap(), block_hash)
.await
.ok()
} else {
None
};

Ok(if let Some(lock) = lock {
lock
} else {
// Second situation: the block is not in the cache of recent blocks. This isn't great.
drop::<futures::lock::MutexGuard<_>>(cache_lock);

// The only solution is to download the runtime of the block in question from the network.

// TODO: considering caching the runtime code the same way as the state trie root hash

// In order to grab the runtime code and perform the call network request, we need
// to know the state trie root hash and the height of the block.
let (state_trie_root_hash, block_number) =
self.state_trie_root_hash(block_hash).await.unwrap(); // TODO: don't unwrap

// Download the runtime of this block. This takes a long time as the runtime is rather
// big (around 1MiB in general).
let (storage_code, storage_heap_pages) = {
let mut code_query_result = self
.sync_service
.clone()
.storage_query(
block_number,
block_hash,
&state_trie_root_hash,
iter::once(&b":code"[..]).chain(iter::once(&b":heappages"[..])),
3,
Duration::from_secs(20),
NonZeroU32::new(1).unwrap(),
)
.await
.map_err(runtime_service::RuntimeCallError::StorageQuery)
.map_err(RuntimeCallError::Call)?;
let heap_pages = code_query_result.pop().unwrap();
let code = code_query_result.pop().unwrap();
(code, heap_pages)
};

// Give the code and heap pages to the runtime service. The runtime service will
// try to find any similar runtime it might have, and if not will compile it.
let pinned_runtime_id = self
.runtime_service
.compile_and_pin_runtime(storage_code, storage_heap_pages)
.await;

let precall = self
.runtime_service
.pinned_runtime_lock(
pinned_runtime_id.clone(),
*block_hash,
block_number,
state_trie_root_hash,
)
.await;

// TODO: consider keeping pinned runtimes in a cache instead
self.runtime_service.unpin_runtime(pinned_runtime_id).await;

precall
})
}

/// Performs a runtime call to a random block.
// TODO: maybe add a parameter to check for a runtime API?
async fn runtime_call(
Expand All @@ -1314,86 +1397,7 @@ impl<TPlat: Platform> Background<TPlat> {
) -> Result<Vec<u8>, RuntimeCallError> {
// This function contains two steps: obtaining the runtime of the block in question,
// then performing the actual call. The first step is the longest and most difficult.
let precall = {
let cache_lock = self.cache.lock().await;

// Try to find the block in the cache of recent blocks. Most of the time, the call target
// should be in there.
let lock = if cache_lock.recent_pinned_blocks.contains(block_hash) {
// The runtime service has the block pinned, meaning that we can ask the runtime
// service to perform the call.
self.runtime_service
.pinned_block_runtime_lock(
cache_lock.subscription_id.clone().unwrap(),
block_hash,
)
.await
.ok()
} else {
None
};

if let Some(lock) = lock {
lock
} else {
// Second situation: the block is not in the cache of recent blocks. This isn't great.
drop::<futures::lock::MutexGuard<_>>(cache_lock);

// The only solution is to download the runtime of the block in question from the network.

// TODO: considering caching the runtime code the same way as the state trie root hash

// In order to grab the runtime code and perform the call network request, we need
// to know the state trie root hash and the height of the block.
let (state_trie_root_hash, block_number) =
self.state_trie_root_hash(block_hash).await.unwrap(); // TODO: don't unwrap

// Download the runtime of this block. This takes a long time as the runtime is rather
// big (around 1MiB in general).
let (storage_code, storage_heap_pages) = {
let mut code_query_result = self
.sync_service
.clone()
.storage_query(
block_number,
block_hash,
&state_trie_root_hash,
iter::once(&b":code"[..]).chain(iter::once(&b":heappages"[..])),
3,
Duration::from_secs(20),
NonZeroU32::new(1).unwrap(),
)
.await
.map_err(runtime_service::RuntimeCallError::StorageQuery)
.map_err(RuntimeCallError::Call)?;
let heap_pages = code_query_result.pop().unwrap();
let code = code_query_result.pop().unwrap();
(code, heap_pages)
};

// Give the code and heap pages to the runtime service. The runtime service will
// try to find any similar runtime it might have, and if not will compile it.
let pinned_runtime_id = self
.runtime_service
.compile_and_pin_runtime(storage_code, storage_heap_pages)
.await;

let precall = self
.runtime_service
.pinned_runtime_lock(
pinned_runtime_id.clone(),
*block_hash,
block_number,
state_trie_root_hash,
)
.await;

// TODO: consider keeping pinned runtimes in a cache instead
self.runtime_service.unpin_runtime(pinned_runtime_id).await;

precall
}
};
let precall = self.runtime_lock(block_hash).await?;

let (runtime_call_lock, virtual_machine) = precall
.start(
Expand Down
105 changes: 12 additions & 93 deletions bin/light-base/src/json_rpc_service/state_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,98 +1162,12 @@ impl<TPlat: Platform> Background<TPlat> {
),
};

// This function contains two steps: obtaining the runtime of the block in question,
// then obtaining the specification. The first step is the longest and most difficult.
let specification = {
let cache_lock = self.cache.lock().await;

// Try to find the block in the cache of recent blocks. Most of the time, the call
// target should be in there.
let spec = if cache_lock.recent_pinned_blocks.contains(&block_hash) {
// The runtime service has the block pinned, meaning that we can ask the runtime
// service for the specification.
self.runtime_service
.pinned_block_runtime_lock(
cache_lock.subscription_id.clone().unwrap(),
&block_hash,
)
.await
.ok()
.map(|rt| rt.specification())
} else {
None
};

// If the block isn't a recent block or if the subscription is obsolete, fall back
// to downloading it.
if let Some(spec) = spec {
spec
} else {
// Second situation: the block is not in the cache of recent blocks. This
// isn't great.
drop::<futures::lock::MutexGuard<_>>(cache_lock);

// The only solution is to download the runtime of the block in question from the network.

// TODO: considering caching the runtime code the same way as the state trie root hash
// TODO: DRY with runtime_call()

// In order to grab the runtime code and perform the call network request, we need
// to know the state trie root hash and the height of the block.
let (state_trie_root_hash, block_number) =
self.state_trie_root_hash(&block_hash).await.unwrap(); // TODO: don't unwrap

// Download the runtime of this block. This takes a long time as the runtime is rather
// big (around 1MiB in general).
let (storage_code, storage_heap_pages) = {
let mut code_query_result = self
.sync_service
.clone()
.storage_query(
block_number,
&block_hash,
&state_trie_root_hash,
iter::once(&b":code"[..]).chain(iter::once(&b":heappages"[..])),
3,
Duration::from_secs(24),
NonZeroU32::new(1).unwrap(),
)
.await
.map_err(runtime_service::RuntimeCallError::StorageQuery)
.map_err(RuntimeCallError::Call)
.unwrap(); // TODO: don't unwrap /!\
let heap_pages = code_query_result.pop().unwrap();
let code = code_query_result.pop().unwrap();
(code, heap_pages)
};

// Give the code and heap pages to the runtime service. The runtime service will
// try to find any similar runtime it might have, and if not will compile it.
let pinned_runtime_id = self
.runtime_service
.compile_and_pin_runtime(storage_code, storage_heap_pages)
.await;

let runtime = self
.runtime_service
.pinned_runtime_lock(
pinned_runtime_id.clone(),
block_hash,
block_number,
state_trie_root_hash,
)
.await;

// TODO: consider keeping pinned runtimes in a cache instead
self.runtime_service.unpin_runtime(pinned_runtime_id).await;

runtime.specification()
}
};

// Now that we have the runtime specification, turn it into a JSON-RPC response.
let response = match specification {
Ok(spec) => {
let response = match self
.runtime_lock(&block_hash)
.await
.map(|l| l.specification())
{
Ok(Ok(spec)) => {
let runtime_spec = spec.decode();
methods::Response::state_getRuntimeVersion(methods::RuntimeVersion {
spec_name: runtime_spec.spec_name.into(),
Expand All @@ -1269,7 +1183,12 @@ impl<TPlat: Platform> Background<TPlat> {
.collect(),
})
.to_json_response(request_id)
}
},
Ok(Err(error)) => json_rpc::parse::build_error_response(
request_id,
json_rpc::parse::ErrorResponse::ServerError(-32000, &error.to_string()),
None,
),
Err(error) => json_rpc::parse::build_error_response(
request_id,
json_rpc::parse::ErrorResponse::ServerError(-32000, &error.to_string()),
Expand Down
4 changes: 4 additions & 0 deletions bin/wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

- A limit to the number of substreams a remote can maintain open over a connection is now enforced. ([#2724](https://github.com/paritytech/smoldot/pull/2724))

### Fixed

- No longer panic when calling `state_getRuntimeVersion` is unable to download the runtime code of an old block from the network. ([#2736](https://github.com/paritytech/smoldot/pull/2736))

## 0.6.32 - 2022-09-07

### Fixed
Expand Down