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

Remove Chain.databaseContent function #2791

Merged
merged 6 commits into from
Sep 28, 2022
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
46 changes: 1 addition & 45 deletions bin/light-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub struct AddChainConfig<'a, TChain, TRelays> {
pub specification: &'a str,

/// Opaque data containing the database content that was retrieved by calling
/// [`Client::database_content`] in the past.
/// the `chainHead_unstable_finalizedDatabase` JSON-RPC function in the past.
///
/// Pass an empty string if no database content exists or is known.
///
Expand Down Expand Up @@ -883,50 +883,6 @@ impl<TPlat: platform::Platform, TChain> Client<TPlat, TChain> {

json_rpc_sender.queue_rpc_request(json_rpc_request)
}

/// Returns opaque data that can later by passing back through
/// [`AddChainConfig::database_content`].
///
/// Note that the `Future` being returned doesn't borrow `self`. Even if the chain is later
/// removed, this `Future` will still return a value.
///
/// If the database content can't be obtained because not enough information is known about
/// the chain, a dummy value is intentionally returned.
///
/// `max_size` can be passed force the output of the function to be smaller than the given
/// value.
///
/// # Panic
///
/// Panics if the [`ChainId`] is invalid.
///
pub fn database_content(
&self,
chain_id: ChainId,
max_size: usize,
) -> impl Future<Output = String> {
let key = &self.public_api_chains.get(chain_id.0).unwrap().key;

// Clone the services initialization future.
let mut services = match &self.chains_by_key.get(key).unwrap().services {
future::MaybeDone::Done(d) => future::MaybeDone::Done(d.clone()),
future::MaybeDone::Future(d) => future::MaybeDone::Future(d.clone()),
future::MaybeDone::Gone => unreachable!(),
};

async move {
// Wait for the chain to finish initializing before we can obtain the database.
(&mut services).await;
let services = Pin::new(&mut services).take_output().unwrap();
encode_database(
&services.network_service,
&services.sync_service,
services.block_number_bytes,
max_size,
)
.await
}
}
}

/// Starts all the services of the client.
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 @@ -2,6 +2,10 @@

## Unreleased

### Removed

- Removed `Chain.databaseContent` function. Use the `chainHead_unstable_finalizedDatabase` JSON-RPC function to obtain the database content instead. ([#2791](https://github.com/paritytech/smoldot/pull/2791))

### Changed

- `Chain.sendJsonRpc` now throws a `MalformedJsonRpcError` exception if the JSON-RPC request is too large or malformed, or a `QueueFullError` if the queue of JSON-RPC requests of the chain is full. ([#2778](https://github.com/paritytech/smoldot/pull/2778))
Expand Down
26 changes: 2 additions & 24 deletions bin/wasm-node/javascript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,6 @@ export interface Chain {
*/
nextJsonRpcResponse(): Promise<string>;

/**
* Serializes the important information about the state of the chain so that it can be provided
* back in the {AddChainOptions.databaseContent} when the chain is recreated.
*
* The content of the string is opaque and shouldn't be decoded.
*
* A parameter can be passed to indicate the maximum length of the returned value (in number
* of bytes this string would occupy in the UTF-8 encoding). The higher this limit is the more
* information can be included. This parameter is optional, and not passing any value means
* "unbounded".
*
* @throws {AlreadyDestroyedError} If the chain has been removed or the client has been terminated.
* @throws {CrashError} If the background client has crashed.
*/
databaseContent(maxUtf8BytesSize?: number): Promise<string>;

/**
* Disconnects from the blockchain.
*
Expand Down Expand Up @@ -295,7 +279,8 @@ export interface AddChainOptions {
chainSpec: string;

/**
* Content of the database of this chain. Can be obtained with {Client.databaseContent}.
* Content of the database of this chain. Can be obtained by using the
* `chainHead_unstable_finalizedDatabase` JSON-RPC function.
*
* Smoldot reserves the right to change its database format, making previous databases
* incompatible. For this reason, no error is generated if the content of the database is invalid
Expand Down Expand Up @@ -448,13 +433,6 @@ export function start(options: ClientOptions, platformBindings: PlatformBindings
instance.nextJsonRpcResponse(chainId, resolve, reject)
});
},
databaseContent: (maxUtf8BytesSize) => {
if (alreadyDestroyedError)
return Promise.reject(alreadyDestroyedError);
if (wasDestroyed.destroyed)
return Promise.reject(new AlreadyDestroyedError);
return instance.databaseContent(chainId, maxUtf8BytesSize);
},
remove: () => {
if (alreadyDestroyedError)
throw alreadyDestroyedError;
Expand Down
16 changes: 0 additions & 16 deletions bin/wasm-node/javascript/src/instance/bindings-smoldot-light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export interface Config {

logCallback: (level: number, target: string, message: string) => void,
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void,
databaseContentCallback: (data: string, chainId: number) => void,
currentTaskCallback?: (taskName: string | null) => void,
}

Expand Down Expand Up @@ -222,21 +221,6 @@ export default function (config: Config): { imports: WebAssembly.ModuleImports,
config.jsonRpcResponsesNonEmptyCallback(chainId);
},

// Used by the Rust side in response to asking for the database content of a chain.
database_content_ready: (ptr: number, len: number, chainId: number) => {
if (killedTracked.killed) return;

const instance = config.instance!;

ptr >>>= 0;
len >>>= 0;

let content = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), ptr, len);
if (config.databaseContentCallback) {
config.databaseContentCallback(content, chainId);
}
},

// Used by the Rust side to emit a log entry.
// See also the `max_log_level` parameter in the configuration.
log: (level: number, targetPtr: number, targetLen: number, messagePtr: number, messageLen: number) => {
Expand Down
1 change: 0 additions & 1 deletion bin/wasm-node/javascript/src/instance/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export interface SmoldotWasmExports extends WebAssembly.Exports {
json_rpc_send: (textPtr: number, textLen: number, chainId: number) => number,
json_rpc_responses_peek: (chainId: number) => number,
json_rpc_responses_pop: (chainId: number) => void,
database_content: (chainId: number, maxSize: number) => void,
timer_finished: (timerId: number) => void,
connection_open_single_stream: (connectionId: number, handshakeTy: number) => void,
connection_open_multi_stream: (connectionId: number, handshakeTyPtr: number, handshakeTyLen: number) => void,
Expand Down
59 changes: 3 additions & 56 deletions bin/wasm-node/javascript/src/instance/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export interface Instance {
nextJsonRpcResponse: (chainId: number, resolve: (response: string) => void, reject: (error: Error) => void) => void
addChain: (chainSpec: string, databaseContent: string, potentialRelayChains: number[], disableJsonRpc: boolean) => Promise<{ success: true, chainId: number } | { success: false, error: string }>
removeChain: (chainId: number) => void
databaseContent: (chainId: number, maxUtf8BytesSize?: number) => Promise<string>
startShutdown: () => void
}

Expand All @@ -87,8 +86,7 @@ export function start(configMessage: Config, platformBindings: instance.Platform

// Contains the information of each chain that is currently alive.
let chains: Map<number, {
jsonRpcResponsesPromises: PromiseFunctions[],
databasePromises: PromiseFunctions[],
jsonRpcResponsesPromises: JsonRpcResponsesPromise[],
}> = new Map();

// Start initialization of the Wasm VM.
Expand All @@ -110,10 +108,6 @@ export function start(configMessage: Config, platformBindings: instance.Platform
promise.reject(crashError.error)
}
chain.jsonRpcResponsesPromises = [];
for (const promise of chain.databasePromises) {
promise.reject(crashError.error)
}
chain.databasePromises = [];
}
},
logCallback: (level, target, message) => {
Expand Down Expand Up @@ -160,10 +154,6 @@ export function start(configMessage: Config, platformBindings: instance.Platform
setTimeout(update, 0)
}
},
databaseContentCallback: (data, chainId) => {
const promises = chains.get(chainId)?.databasePromises!;
(promises.shift() as PromiseFunctions).resolve(data);
},
currentTaskCallback: (taskName) => {
currentTask.name = taskName
},
Expand Down Expand Up @@ -302,8 +292,7 @@ export function start(configMessage: Config, platformBindings: instance.Platform
if (instance.exports.chain_is_ok(chainId) != 0) {
console.assert(!chains.has(chainId));
chains.set(chainId, {
jsonRpcResponsesPromises: new Array(),
databasePromises: new Array()
jsonRpcResponsesPromises: new Array()
});
return { success: true, chainId };
} else {
Expand Down Expand Up @@ -345,48 +334,6 @@ export function start(configMessage: Config, platformBindings: instance.Platform
}
},

databaseContent: (chainId: number, maxUtf8BytesSize?: number): Promise<string> => {
// Because `databaseContent` is passed as parameter an identifier returned by `addChain`, it
// is always the case that the Wasm instance is already initialized. The only possibility for
// it to not be the case is if the user completely invented the `chainId`.
if (!state.initialized)
throw new Error("Internal error");

if (crashError.error)
throw crashError.error;

console.assert(chains.has(chainId));
const databaseContentPromises = chains.get(chainId)?.databasePromises!;
const promise: Promise<string> = new Promise((resolve, reject) => {
databaseContentPromises.push({ resolve, reject });
});

// Cap `maxUtf8BytesSize` and set a default value.
const twoPower32 = (1 << 30) * 4; // `1 << 31` and `1 << 32` in JavaScript don't give the value that you expect.
const maxSize = maxUtf8BytesSize || (twoPower32 - 1);
const cappedMaxSize = (maxSize >= twoPower32) ? (twoPower32 - 1) : maxSize;

// The value of `maxUtf8BytesSize` is guaranteed to always fit in 32 bits, in
// other words, that `maxUtf8BytesSize < (1 << 32)`.
// We need to perform a conversion in such a way that the the bits of the output of
// `ToInt32(converted)`, when interpreted as u32, is equal to `maxUtf8BytesSize`.
// See ToInt32 here: https://tc39.es/ecma262/#sec-toint32
// Note that the code below has been tested against example values. Please be very careful
// if you decide to touch it. Ideally it would be unit-tested, but since it concerns the FFI
// layer between JS and Rust, writing unit tests would be extremely complicated.
const twoPower31 = (1 << 30) * 2; // `1 << 31` in JavaScript doesn't give the value that you expect.
const converted = (cappedMaxSize >= twoPower31) ?
(cappedMaxSize - twoPower32) : cappedMaxSize;

try {
state.instance.exports.database_content(chainId, converted);
return promise;
} catch (_error) {
console.assert(crashError.error);
throw crashError.error
}
},

startShutdown: () => {
return queueOperation((instance) => {
// `startShutdown` is a bit special in its handling of crashes.
Expand All @@ -409,7 +356,7 @@ export function start(configMessage: Config, platformBindings: instance.Platform

}

interface PromiseFunctions {
interface JsonRpcResponsesPromise {
resolve: (data: string) => void,
reject: (error: Error) => void,
}
1 change: 0 additions & 1 deletion bin/wasm-node/javascript/src/instance/raw-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export interface Config {
onWasmPanic: (message: string) => void,
logCallback: (level: number, target: string, message: string) => void,
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void,
databaseContentCallback: (data: string, chainId: number) => void,
currentTaskCallback?: (taskName: string | null) => void,
cpuRateLimit: number,
}
Expand Down
34 changes: 1 addition & 33 deletions bin/wasm-node/rust/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,6 @@ extern "C" {
/// [`json_rpc_responses_pop`] in order to have the guarantee that this function gets called.
pub fn json_rpc_responses_non_empty(chain_id: u32);

/// This function is called by the client is response to calling [`database_content`].
///
/// The database content is a UTF-8 string found in the memory of the WebAssembly virtual
/// machine at offset `ptr` and with length `len`.
///
/// `chain_id` is the chain that the request was made to. It is guaranteed to always be valid.
/// This function is not called if the chain is removed with [`remove_chain`] while the fetch
/// is in progress.
pub fn database_content_ready(ptr: u32, len: u32, chain_id: u32);

/// Client is emitting a log entry.
///
/// Each log entry is made of a log level (`1 = Error, 2 = Warn, 3 = Info, 4 = Debug,
Expand Down Expand Up @@ -319,7 +309,7 @@ pub extern "C" fn alloc(len: u32) -> u32 {
/// the pointers and lengths (in bytes) as parameter to this function.
///
/// > **Note**: The database content is an opaque string that can be obtained by calling
/// > [`database_content`].
/// > the `chainHead_unstable_finalizedDatabase` JSON-RPC function.
///
/// Similarly, use [`alloc`] to allocate a buffer containing a list of 32-bits-little-endian chain
/// ids. Pass the pointer and number of chain ids (*not* length in bytes of the buffer) to this
Expand Down Expand Up @@ -465,28 +455,6 @@ pub extern "C" fn json_rpc_responses_pop(chain_id: u32) {
super::json_rpc_responses_pop(chain_id)
}

/// Starts generating the content of the database of the chain.
///
/// This function doesn't immediately return the content, but later calls
/// [`database_content_ready`] with the content of the database.
///
/// Calling this function multiple times will lead to multiple calls to [`database_content_ready`],
/// with potentially different values.
///
/// The `max_size` parameter contains the maximum length, in bytes, of the value that will be
/// provided back. Please be aware that passing a `u32` across the FFI boundary can be tricky.
/// From the Wasm perspective, the parameter of this function is actually a `i32` that is then
/// reinterpreted as a `u32`.
///
/// [`database_content_ready`] will not be called if you remove the chain with [`remove_chain`]
/// while the operation is in progress.
///
/// It is forbidden to call this function on an erroneous chain.
#[no_mangle]
pub extern "C" fn database_content(chain_id: u32, max_size: u32) {
super::database_content(chain_id, max_size)
}

/// Must be called in response to [`start_timer`] after the given duration has passed.
#[no_mangle]
pub extern "C" fn timer_finished(timer_id: u32) {
Expand Down
10 changes: 3 additions & 7 deletions bin/wasm-node/rust/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ use std::{
pub(crate) struct Client<TPlat: smoldot_light::platform::Platform, TChain> {
pub(crate) smoldot: smoldot_light::Client<TPlat, TChain>,

pub(crate) new_tasks_spawner: mpsc::UnboundedSender<(String, future::BoxFuture<'static, ()>)>,

/// List of all chains that have been added by the user.
pub(crate) chains: slab::Slab<Chain>,
}
Expand Down Expand Up @@ -197,17 +195,15 @@ pub(crate) fn init<TPlat: smoldot_light::platform::Platform, TChain>(
.unwrap();

let client = smoldot_light::Client::new(smoldot_light::ClientConfig {
tasks_spawner: {
let new_task_tx = new_task_tx.clone();
Box::new(move |name, task| new_task_tx.unbounded_send((name, task)).unwrap())
},
tasks_spawner: Box::new(move |name, task| {
new_task_tx.unbounded_send((name, task)).unwrap()
}),
system_name: env!("CARGO_PKG_NAME").into(),
system_version: env!("CARGO_PKG_VERSION").into(),
});

Client {
smoldot: client,
new_tasks_spawner: new_task_tx,
chains: slab::Slab::with_capacity(8),
}
}
Expand Down
Loading