Skip to content
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
26 changes: 26 additions & 0 deletions .github/workflows/ci-wasm-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: wasm ci

on:
pull_request:
types: [ opened, synchronize, reopened ]
push:
branches:
- "develop"
- "master"
- "pkg/*"

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: full

jobs:
build-test:
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
rustup target add wasm32-unknown-unknown
- name: Build
run: |
cargo build --target wasm32-unknown-unknown --no-default-features
14 changes: 10 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ license = "MIT"
description = "Rust SDK for CKB"
homepage = "https://github.com/nervosnetwork/ckb-sdk-rust"
repository = "https://github.com/nervosnetwork/ckb-sdk-rust"

resolver = "2"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
Expand All @@ -23,7 +23,7 @@ log = "0.4.6"
reqwest = { version = "0.12", default-features = false, features = ["json"] }
secp256k1 = { version = "0.30.0", features = ["recovery"] }
tokio-util = { version = "0.7.7", features = ["codec"] }
tokio = { version = "1" }
tokio = { version = "1", features = ["time"] }
bytes = "1"
futures = "0.3"
jsonrpc-core = "18"
Expand All @@ -50,14 +50,20 @@ rand = { version = "0.7.3", optional = true }
ckb-mock-tx-types = { version = "0.200.0" }
ckb-chain-spec = "0.200.0"

sparse-merkle-tree = { version = "0.6", optional = true}
sparse-merkle-tree = { version = "0.6", optional = true }
async-iterator = "2.3.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.16", features = ["js"] }
web-time = "1.1.0"
tokio_with_wasm = { version = "0.8.2", features = ["time"] }

[features]
default = ["default-tls"]
default-tls = ["reqwest/default-tls"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
rustls-tls = ["reqwest/rustls-tls"]
test = []
test = ["rce", "rand", "default-tls"]
rce = ["sparse-merkle-tree"]

[dev-dependencies]
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ cargo build --examples

For more use cases of building transactions with CKB node, please refer to [these examples](./examples/) and [unit tests](./src/tests/).

## Wasm

`ckb-sdk-rust` has limited support for `wasm32-unknown-unknown`. Some trait are not implemented on `wasm32`, such as `impl CellDataProvider for &dyn TransactionDependencyProvider`. To use on wasm, default features must be disabled

## License

The SDK is available as open source under the terms of the [MIT License](./LICENSE).
Expand Down
3 changes: 2 additions & 1 deletion examples/script_unlocker_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use std::collections::HashMap;
/// [CapacityDiff]: https://github.com/doitian/ckb-sdk-examples-capacity-diff
struct CapacityDiffUnlocker {}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl ScriptUnlocker for CapacityDiffUnlocker {
// This works for any args
fn match_args(&self, _args: &[u8]) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.81.0
1.85.0
16 changes: 15 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@ pub mod test_util;
#[cfg(test)]
mod tests;

pub use rpc::{CkbRpcAsyncClient, CkbRpcClient, IndexerRpcAsyncClient, IndexerRpcClient, RpcError};
pub use rpc::{CkbRpcAsyncClient, IndexerRpcAsyncClient, RpcError};
#[cfg(not(target_arch = "wasm32"))]
pub use rpc::{CkbRpcClient, IndexerRpcClient};
pub use types::{
Address, AddressPayload, AddressType, CodeHashIndex, HumanCapacity, NetworkInfo, NetworkType,
OldAddress, OldAddressFormat, ScriptGroup, ScriptGroupType, ScriptId, Since, SinceType,
TransactionWithScriptGroups,
};

pub use ckb_crypto::secp::SECP256K1;

#[cfg(target_arch = "wasm32")]
mod target_specific {
pub trait MaybeSend {}
impl<T> MaybeSend for T {}
}
#[cfg(not(target_arch = "wasm32"))]
mod target_specific {
pub trait MaybeSend: Send {}
impl<T> MaybeSend for T where T: Send {}
}
pub use target_specific::MaybeSend;
6 changes: 3 additions & 3 deletions src/rpc/ckb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use ckb_types::{core::Cycle, H256};
use super::{ckb_indexer::CellsCapacity, ResponseFormatGetter};

pub use super::ckb_indexer::{Cell, Order, Pagination, SearchKey, Tip, Tx};

#[cfg(not(target_arch = "wasm32"))]
crate::jsonrpc!(pub struct CkbRpcClient {
// Chain
pub fn get_block(&self, hash: H256) -> Option<BlockView>;
Expand Down Expand Up @@ -211,7 +211,7 @@ fn transform_cycles(cycles: Option<Vec<ckb_jsonrpc_types::Cycle>>) -> Vec<Cycle>
.map(|c| c.into_iter().map(Into::into).collect())
.unwrap_or_default()
}

#[cfg(not(target_arch = "wasm32"))]
impl From<&CkbRpcClient> for CkbRpcAsyncClient {
fn from(value: &CkbRpcClient) -> Self {
Self {
Expand All @@ -220,7 +220,7 @@ impl From<&CkbRpcClient> for CkbRpcAsyncClient {
}
}
}

#[cfg(not(target_arch = "wasm32"))]
impl CkbRpcClient {
pub fn get_packed_block(&self, hash: H256) -> Result<Option<JsonBytes>, crate::RpcError> {
self.post("get_block", (hash, Some(Uint32::from(0u32))))
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/ckb_indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub struct Pagination<T> {
pub objects: Vec<T>,
pub last_cursor: JsonBytes,
}

#[cfg(not(target_arch = "wasm32"))]
crate::jsonrpc!(pub struct IndexerRpcClient {
pub fn get_indexer_tip(&self) -> Option<Tip>;
pub fn get_cells(&self, search_key: SearchKey, order: Order, limit: Uint32, after: Option<JsonBytes>) -> Pagination<Cell>;
Expand All @@ -202,7 +202,7 @@ crate::jsonrpc_async!(pub struct IndexerRpcAsyncClient {
pub fn get_transactions(&self, search_key: SearchKey, order: Order, limit: Uint32, after: Option<JsonBytes>) -> Pagination<Tx>;
pub fn get_cells_capacity(&self, search_key: SearchKey) -> Option<CellsCapacity>;
});

#[cfg(not(target_arch = "wasm32"))]
impl From<&IndexerRpcClient> for IndexerRpcAsyncClient {
fn from(value: &IndexerRpcClient) -> Self {
Self {
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/ckb_light_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub struct PeerSyncState {
/// Proved best known header of remote peer.
pub proved_best_known_header: Option<HeaderView>,
}

#[cfg(not(target_arch = "wasm32"))]
crate::jsonrpc!(pub struct LightClientRpcClient {
// BlockFilter
pub fn set_scripts(&self, scripts: Vec<ScriptStatus>, command: Option<SetScriptsCommand>) -> ();
Expand Down Expand Up @@ -199,7 +199,7 @@ crate::jsonrpc_async!(pub struct LightClientRpcAsyncClient {
pub fn get_peers(&self) -> Vec<RemoteNode>;
pub fn local_node_info(&self) -> LocalNode;
});

#[cfg(not(target_arch = "wasm32"))]
impl From<&LightClientRpcClient> for LightClientRpcAsyncClient {
fn from(value: &LightClientRpcClient) -> Self {
Self {
Expand Down
41 changes: 36 additions & 5 deletions src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ pub mod ckb_indexer;
pub mod ckb_light_client;

use anyhow::anyhow;
pub use ckb::{CkbRpcAsyncClient, CkbRpcClient};
pub use ckb_indexer::{IndexerRpcAsyncClient, IndexerRpcClient};
use ckb_jsonrpc_types::{JsonBytes, ResponseFormat};
pub use ckb_light_client::{LightClientRpcAsyncClient, LightClientRpcClient};

#[cfg(not(target_arch = "wasm32"))]
pub use ckb::CkbRpcClient;
#[cfg(not(target_arch = "wasm32"))]
pub use ckb_indexer::IndexerRpcClient;
#[cfg(not(target_arch = "wasm32"))]
pub use ckb_light_client::LightClientRpcClient;

pub use ckb::CkbRpcAsyncClient;
pub use ckb_indexer::IndexerRpcAsyncClient;
use ckb_jsonrpc_types::{JsonBytes, ResponseFormat};
pub use ckb_light_client::LightClientRpcAsyncClient;
#[cfg(not(target_arch = "wasm32"))]
use std::future::Future;
use thiserror::Error;

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn block_on<F: Send>(future: impl Future<Output = F> + Send) -> F {
match tokio::runtime::Handle::try_current() {
Ok(h)
Expand Down Expand Up @@ -55,6 +64,7 @@ pub enum RpcError {
Other(#[from] anyhow::Error),
}

#[cfg(not(target_arch = "wasm32"))]
#[macro_export]
macro_rules! jsonrpc {
(
Expand Down Expand Up @@ -159,7 +169,7 @@ macro_rules! jsonrpc_async {
pub fn new(uri: &str) -> Self {
$struct_name { id: 0.into(), client: $crate::rpc::RpcClient::new(uri), }
}

#[cfg(not(target_arch="wasm32"))]
pub fn post<PARAM, RET>(&self, method:&str, params: PARAM)->impl std::future::Future<Output =Result<RET, $crate::rpc::RpcError>> + Send + 'static
where
PARAM:serde::ser::Serialize + Send + 'static,
Expand All @@ -181,7 +191,28 @@ macro_rules! jsonrpc_async {
self.client.post(params_fn)

}
#[cfg(target_arch="wasm32")]
pub fn post<PARAM, RET>(&self, method:&str, params: PARAM)->impl std::future::Future<Output =Result<RET, $crate::rpc::RpcError>> + 'static
where
PARAM:serde::ser::Serialize + Send + 'static,
RET: serde::de::DeserializeOwned + Send + 'static,
{
let id = self.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let method = serde_json::json!(method);

let params_fn = move || -> Result<_,_> {
let params = serde_json::to_value(params)?;
let mut req_json = serde_json::Map::new();
req_json.insert("id".to_owned(), serde_json::json!(id));
req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
req_json.insert("method".to_owned(), method);
req_json.insert("params".to_owned(), params);
Ok(req_json)
};

self.client.post(params_fn)

}
$(
$(#[$attr])*
pub fn $method(&$selff $(, $arg_name: $arg_ty)*) -> impl std::future::Future<Output =Result<$return_ty, $crate::rpc::RpcError>> {
Expand Down
9 changes: 6 additions & 3 deletions src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,8 @@ impl Context {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl TransactionDependencyProvider for Context {
// For verify certain cell belong to certain transaction
async fn get_transaction_async(
Expand Down Expand Up @@ -451,7 +452,8 @@ impl TransactionDependencyProvider for Context {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl HeaderDepResolver for Context {
async fn resolve_by_tx_async(
&self,
Expand Down Expand Up @@ -520,7 +522,8 @@ impl CellDepResolver for Context {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl CellCollector for LiveCellsContext {
async fn collect_live_cells_async(
&mut self,
Expand Down
3 changes: 2 additions & 1 deletion src/tests/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const CYCLE_BIN: &[u8] = include_bytes!("../test-data/cycle");
pub struct CycleUnlocker {
loops: u64,
}
#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl ScriptUnlocker for CycleUnlocker {
fn match_args(&self, _args: &[u8]) -> bool {
true
Expand Down
24 changes: 15 additions & 9 deletions src/traits/default_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ impl DefaultHeaderDepResolver {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl HeaderDepResolver for DefaultHeaderDepResolver {
async fn resolve_by_tx_async(
&self,
Expand Down Expand Up @@ -277,7 +278,7 @@ impl DefaultCellCollector {
pub fn set_acceptable_indexer_leftbehind(&mut self, value: u64) {
self.acceptable_indexer_leftbehind = value;
}

#[cfg(not(target_arch = "wasm32"))]
/// wrapper check_ckb_chain_async future
pub fn check_ckb_chain(&mut self) -> Result<(), CellCollectorError> {
crate::rpc::block_on(self.check_ckb_chain_async())
Expand All @@ -301,7 +302,10 @@ impl DefaultCellCollector {
if tip_number.value()
> block_number.value() + self.acceptable_indexer_leftbehind
{
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(Duration::from_millis(50)).await;
#[cfg(target_arch = "wasm32")]
tokio_with_wasm::time::sleep(Duration::from_millis(50)).await;
} else {
return Ok(());
}
Expand All @@ -319,7 +323,8 @@ impl DefaultCellCollector {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl CellCollector for DefaultCellCollector {
async fn collect_live_cells_async(
&mut self,
Expand Down Expand Up @@ -461,7 +466,7 @@ impl DefaultTransactionDependencyProvider {
inner: Arc::new(Mutex::new(inner)),
}
}

#[cfg(not(target_arch = "wasm32"))]
pub fn apply_tx(
&mut self,
tx: Transaction,
Expand All @@ -479,7 +484,7 @@ impl DefaultTransactionDependencyProvider {
inner.offchain_cache.apply_tx(tx, tip_block_number)?;
Ok(())
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_cell_with_data(
&self,
out_point: &OutPoint,
Expand Down Expand Up @@ -517,7 +522,8 @@ impl DefaultTransactionDependencyProvider {
}
}

#[async_trait::async_trait]
#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl TransactionDependencyProvider for DefaultTransactionDependencyProvider {
async fn get_transaction_async(
&self,
Expand All @@ -528,7 +534,7 @@ impl TransactionDependencyProvider for DefaultTransactionDependencyProvider {
return Ok(tx.clone());
}
let ret: Result<TransactionView, TransactionDependencyError> =
inner.offchain_cache.get_transaction(tx_hash);
inner.offchain_cache.get_transaction_async(tx_hash).await;
if ret.is_ok() {
return ret;
}
Expand Down Expand Up @@ -559,7 +565,7 @@ impl TransactionDependencyProvider for DefaultTransactionDependencyProvider {
) -> Result<CellOutput, TransactionDependencyError> {
{
let inner = self.inner.lock().await;
let ret = inner.offchain_cache.get_cell(out_point);
let ret = inner.offchain_cache.get_cell_async(out_point).await;
if ret.is_ok() {
return ret;
}
Expand All @@ -574,7 +580,7 @@ impl TransactionDependencyProvider for DefaultTransactionDependencyProvider {
) -> Result<Bytes, TransactionDependencyError> {
{
let inner = self.inner.lock().await;
let ret = inner.offchain_cache.get_cell_data(out_point);
let ret = inner.offchain_cache.get_cell_data_async(out_point).await;
if ret.is_ok() {
return ret;
}
Expand Down
Loading