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

refactor api #47

Merged
merged 5 commits into from
Nov 10, 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
2 changes: 1 addition & 1 deletion e2e/__snapshots__/state.test.ts.snap

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions e2e/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Codec } from '@polkadot/types/types'
import { Keyring } from '@polkadot/keyring'
import { beforeAll, beforeEach, expect, vi } from 'vitest'

import { Api } from '../src/api'
import { Blockchain } from '../src/blockchain'
import { BuildBlockMode } from '../src/blockchain/txpool'
import { InherentProviders, SetTimestamp, SetValidationData } from '../src/blockchain/inherents'
Expand Down Expand Up @@ -30,11 +31,11 @@ export const env = {

const setupAll = async ({ endpoint, blockHash, mockSignatureHost }: SetupOption) => {
const wsProvider = new WsProvider(endpoint)
const api = await ApiPromise.create({ provider: wsProvider })
const api = new Api(wsProvider)

await api.isReady

const header = await api.rpc.chain.getHeader(blockHash)
const header = await api.getHeader(blockHash)

return {
async setup() {
Expand All @@ -48,13 +49,13 @@ const setupAll = async ({ endpoint, blockHash, mockSignatureHost }: SetupOption)
const inherents = new InherentProviders(setTimestamp, [new SetValidationData(tasks, 1)])

const chain = new Blockchain({
upstreamApi: api,
api,
tasks,
buildBlockMode: BuildBlockMode.Manual,
inherentProvider: inherents,
header: {
hash: blockHash,
number: header.number.toNumber(),
number: Number(header.number),
},
})

Expand Down
6 changes: 4 additions & 2 deletions e2e/state.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it } from 'vitest'
import { describe, expect, it } from 'vitest'

import { api, env, expectHex, expectJson, setupApi } from './helper'

Expand All @@ -7,7 +7,9 @@ setupApi(env.mandala)
describe('state rpc', () => {
it('getXXX', async () => {
await expectJson(api.rpc.state.getRuntimeVersion()).toMatchSnapshot()
await expectHex(api.rpc.state.getMetadata()).toMatchSnapshot()
await expectHex(api.rpc.state.getMetadata(env.mandala.blockHash)).toMatchSnapshot()
const genesisHash = await api.rpc.chain.getBlockHash(0)
expect(await api.rpc.state.getMetadata(genesisHash)).to.not.be.eq(await api.rpc.state.getMetadata())
})

it.todo('subscribeRuntimeVersion')
Expand Down
28 changes: 27 additions & 1 deletion executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,34 @@ use wasm_bindgen::prelude::*;

mod runner_api;
mod task;

use crate::runner_api::RpcApiClient;
use smoldot::json_rpc::methods::HexString;

#[wasm_bindgen]
pub async fn get_metadata(code: &str) -> Result<JsValue, JsValue> {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
_ = console_log::init_with_level(log::Level::Debug);

let code = HexString(hex::decode(&code[2..]).map_err(|e| e.to_string())?);
let result = task::Task::get_metadata(code).await;

let metadata = result.map_err(|e| e.to_string())?;

Ok(metadata.to_string().into())
}

#[wasm_bindgen]
pub async fn get_runtime_version(code: &str) -> Result<JsValue, JsValue> {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
_ = console_log::init_with_level(log::Level::Debug);

let code = HexString(hex::decode(&code[2..]).map_err(|e| e.to_string())?);
let result = task::Task::runtime_version(code).await;

let runtime_version = result.map_err(|e| e.to_string())?;

Ok(runtime_version.to_string().into())
}

#[wasm_bindgen]
pub async fn start(task_id: u32, ws_url: &str) -> Result<(), JsValue> {
Expand Down
95 changes: 66 additions & 29 deletions executor/src/task.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use core::iter;
use std::collections::BTreeMap;
use jsonrpsee::core::client::Client;
use serde::{Deserialize, Serialize};
use smoldot::{
Expand All @@ -8,8 +7,13 @@ use smoldot::{
runtime_host::{self, RuntimeHostVm},
storage_diff::StorageDiff,
},
json_rpc::methods::HexString, trie::{calculate_root::{root_merkle_value, RootMerkleValueCalculation}, TrieEntryVersion},
json_rpc::methods::HexString,
trie::{
calculate_root::{root_merkle_value, RootMerkleValueCalculation},
TrieEntryVersion,
},
};
use std::collections::BTreeMap;

use crate::runner_api::RpcApiClient;

Expand Down Expand Up @@ -61,7 +65,9 @@ impl Task {
) -> Result<TaskResponse, jsonrpsee::core::Error> {
let resp = match self {
Task::Call(call) => Task::call(task_id, client, call).await,
Task::RuntimeVersion { wasm } => Task::runtime_version(task_id, client, wasm).await,
Task::RuntimeVersion { wasm } => Task::runtime_version(wasm)
.await
.map(TaskResponse::RuntimeVersion),
Task::CalculateStateRoot { entries } => {
Task::calculate_state_root(task_id, client, entries).await
}
Expand All @@ -72,6 +78,42 @@ impl Task {
Ok(resp)
}

#[allow(unused)]
pub async fn get_metadata(wasm: HexString) -> Result<HexString, jsonrpsee::core::Error> {
let vm_proto = HostVmPrototype::new(Config {
module: &wasm,
heap_pages: HeapPages::from(2048),
exec_hint: smoldot::executor::vm::ExecHint::Oneshot,
allow_unresolved_imports: false,
})
.unwrap();

let mut vm = runtime_host::run(runtime_host::Config {
virtual_machine: vm_proto.clone(),
function_to_call: "Metadata_metadata",
parameter: iter::empty::<Vec<u8>>(),
top_trie_root_calculation_cache: None,
storage_top_trie_changes: StorageDiff::empty(),
offchain_storage_changes: StorageDiff::empty(),
})
.unwrap();

let res = loop {
vm = match vm {
RuntimeHostVm::Finished(res) => {
break res;
}
RuntimeHostVm::StorageGet(req) => req.inject_value(Some(iter::empty::<Vec<u8>>())),
RuntimeHostVm::PrefixKeys(req) => req.inject_keys_ordered(iter::empty::<Vec<u8>>()),
RuntimeHostVm::NextKey(req) => req.inject_key(Some(Vec::<u8>::new())),
RuntimeHostVm::SignatureVerification(req) => req.resume_success(),
}
};

res.map(|success| HexString(success.virtual_machine.value().as_ref().to_vec()))
.map_err(|e| jsonrpsee::core::Error::Custom(e.to_string()))
}

async fn call(
task_id: u32,
client: &Client,
Expand Down Expand Up @@ -186,11 +228,7 @@ impl Task {
))
}

async fn runtime_version(
_task_id: u32,
_client: &Client,
wasm: HexString,
) -> Result<TaskResponse, jsonrpsee::core::Error> {
pub async fn runtime_version(wasm: HexString) -> Result<HexString, jsonrpsee::core::Error> {
let vm_proto = HostVmPrototype::new(Config {
module: &wasm,
heap_pages: HeapPages::from(2048),
Expand All @@ -201,34 +239,33 @@ impl Task {

let resp = vm_proto.runtime_version();

Ok(TaskResponse::RuntimeVersion(HexString(
resp.as_ref().to_vec(),
)))
Ok(HexString(resp.as_ref().to_vec()))
}

async fn calculate_state_root(
_task_id: u32,
_client: &Client,
entries: Vec<(HexString, HexString)>,
) -> Result<TaskResponse, jsonrpsee::core::Error> {
let mut calc = root_merkle_value(None);
let map = entries.into_iter().map(|(k, v)| (k.0, v.0)).collect::<BTreeMap<Vec<u8>, Vec<u8>>>();
loop {
match calc {
RootMerkleValueCalculation::Finished { hash, .. } => {
return Ok(TaskResponse::CalculateStateRoot(HexString(hash.to_vec())));
},
RootMerkleValueCalculation::AllKeys(req) => {
calc = req.inject(map.keys().map(|k| k.iter().cloned()));
},
RootMerkleValueCalculation::StorageValue(req) => {
let key = req.key().collect::<Vec<u8>>();
calc = req.inject(TrieEntryVersion::V0, map.get(&key));
},
}
}


let mut calc = root_merkle_value(None);
let map = entries
.into_iter()
.map(|(k, v)| (k.0, v.0))
.collect::<BTreeMap<Vec<u8>, Vec<u8>>>();
loop {
match calc {
RootMerkleValueCalculation::Finished { hash, .. } => {
return Ok(TaskResponse::CalculateStateRoot(HexString(hash.to_vec())));
}
RootMerkleValueCalculation::AllKeys(req) => {
calc = req.inject(map.keys().map(|k| k.iter().cloned()));
}
RootMerkleValueCalculation::StorageValue(req) => {
let key = req.key().collect::<Vec<u8>>();
calc = req.inject(TrieEntryVersion::V0, map.get(&key));
}
}
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
"lint": "tsc --noEmit --project tsconfig.json && eslint . --ext .js,.ts && prettier --check .",
"fix": "eslint . --ext .js,.ts --fix && prettier -w .",
"prepare": "husky install",
"start": "ts-node src/index.ts",
"start": "ts-node --transpile-only src/index.ts",
"build-wasm": "cd executor && wasm-pack --log-level=info build --target nodejs --no-default-features",
"check": "cd executor && cargo check --locked",
"test": "vitest --silent",
"test:native": "EXECUTOR_CMD='cargo run --manifest-path executor/Cargo.toml --' vitest",
"test:dev": "LOG_LEVEL=trace vitest --inspect | pino-pretty",
"dev:base": "LOG_LEVEL=trace node-dev --inspect --notify=false src/index.ts -- dev --config=configs/dev.yml",
"dev:base": "LOG_LEVEL=trace ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/dev.yml",
"dev": "yarn dev:base | pino-pretty",
"dev:native": "yarn dev:base --executor-cmd='cargo run --manifest-path executor/Cargo.toml --' | pino-pretty",
"dev:karura": "node-dev --inspect --notify=false src/index.ts -- dev --config=configs/karura.yml | pino-pretty",
"dev:acala": "node-dev --inspect --notify=false src/index.ts -- dev --config=configs/acala.yml | pino-pretty"
"dev:karura": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/karura.yml | pino-pretty",
"dev:acala": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/acala.yml | pino-pretty"
},
"dependencies": {
"@polkadot/api": "^9.5.2",
Expand Down Expand Up @@ -49,10 +49,10 @@
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"node-dev": "^7.4.3",
"pino-pretty": "^9.1.1",
"prettier": "^2.7.1",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.8.4",
"vitest": "^0.24.3",
"wasm-pack": "^0.10.3"
Expand Down
109 changes: 109 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { HexString } from '@polkadot/util/types'
import { ProviderInterface } from '@polkadot/rpc-provider/types'

type ChainProperties = {
ss58Format?: number
tokenDecimals?: number[]
tokenSymbol?: string[]
}

type Header = {
parentHash: HexString
number: HexString
stateRoot: HexString
extrinsicsRoot: HexString
digest: {
logs: HexString[]
}
}

type SignedBlock = {
block: {
header: Header
extrinsics: HexString[]
}
justifications?: HexString[]
}

export class Api {
#provider: ProviderInterface
#isReady: Promise<void>

#chainProperties: Promise<ChainProperties>

constructor(provider: ProviderInterface) {
this.#provider = provider
this.#isReady = new Promise((resolve, reject) => {
if (this.#provider.isConnected) {
setTimeout(resolve, 500)
} else {
this.#provider.on('connected', () => {
setTimeout(resolve, 500)
})
}
this.#provider.on('error', reject)
})

this.#provider.on('disconnected', () => {
// TODO: reconnect
console.warn('Api disconnected')
})

this.#chainProperties = this.#isReady.then(() => this.getSystemProperties())

this.#provider.connect()
}

async disconnect() {
return this.#provider.disconnect()
}

get isReady() {
return this.#isReady
}

async chainProperties() {
return this.#chainProperties
}

async getSystemName() {
return this.#provider.send<string>('system_name', [])
}

async getSystemProperties() {
return this.#provider.send<ChainProperties>('system_properties', [])
}

async getSystemChain() {
return this.#provider.send<string>('system_chain', [])
}

async getMetadata(hash?: string) {
return this.#provider.send<string>('state_getMetadata', [hash])
}

async getBlockHash(blockNumber?: number) {
return this.#provider.send<string>('chain_getBlockHash', [blockNumber])
}

async getHeader(hash?: string) {
return this.#provider.send<Header>('chain_getHeader', [hash])
}

async getBlock(hash?: string) {
return this.#provider.send<SignedBlock>('chain_getBlock', [hash])
}

async getStorage(key: string, hash?: string) {
return this.#provider.send<string>(hash ? 'state_getStorageAt' : 'state_getStorage', [key, hash])
}

async getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string) {
return this.#provider.send<string[]>(hash ? 'state_getKeysPagedAt' : 'state_getKeysPaged', [
prefix,
pageSize,
startKey,
hash,
])
}
}
Loading