Skip to content

Commit

Permalink
Merge pull request maidsafe#2221 from b-zee/feat-wasm-bindings
Browse files Browse the repository at this point in the history
feat: WASM bindings
  • Loading branch information
b-zee authored Oct 14, 2024
2 parents 04e13f2 + c3a3f3b commit ae32c84
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 295 deletions.
579 changes: 292 additions & 287 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion autonomi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ homepage = "https://maidsafe.net"
readme = "README.md"
repository = "https://github.com/maidsafe/safe_network"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["data"]
full = ["data", "registers", "vault"]
Expand Down Expand Up @@ -45,23 +48,30 @@ tracing = { version = "~0.1.26" }
walkdir = "2.5.0"
xor_name = "5.0.0"
futures = "0.3.30"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
serde-wasm-bindgen = "0.6.5"

[dev-dependencies]
eyre = "0.6.5"
sha2 = "0.10.6"
sn_logging = { path = "../sn_logging", version = "0.2.33" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
sn_peers_acquisition = { path = "../sn_peers_acquisition", version = "0.5.2" }
# Do not specify the version field. Release process expects even the local dev deps to be published.
# Removing the version field is a workaround.
test_utils = { path = "../test_utils" }
tiny_http = "0.11"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
wasm-bindgen-test = "0.3.43"

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
evmlib = { path = "../evmlib", version = "0.1", features = ["wasm-bindgen"] }
# See https://github.com/sebcrozet/instant/blob/7bd13f51f5c930239fddc0476a837870fb239ed7/README.md#using-instant-for-a-wasm-platform-where-performancenow-is-not-available
instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] }
js-sys = "0.3.70"
test_utils = { path = "../test_utils" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-web = "0.1.3"

[lints]
Expand Down
48 changes: 48 additions & 0 deletions autonomi/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<!-- credits: https://rustwasm.github.io/docs/wasm-bindgen/examples/without-a-bundler.html -->
<script type="module">
import init, { Client, getFundedWallet, logInit } from './pkg/autonomi.js';

async function run() {
document.getElementById("btn-run").disabled = true;
const peer_addr = document.getElementById('peer_id').value;

await init();

logInit("sn_networking=debug,autonomi=trace");

console.log("connecting...");
const client = await new Client([peer_addr]);
console.log("connected");

console.log("getting wallet...");
const wallet = getFundedWallet();
console.log("wallet retrieved");

const data = new Uint8Array([1, 2, 3]);
console.log("our data: ", data);

console.log("calculating cost...");
let result = await client.dataCost(data, wallet);
console.log("calculated cost: ", result.toString());

console.log("putting...");
const dataAddr = await client.dataPut(data, wallet);
console.log("put done: ", dataAddr.toString());

console.log("getting...");
const data_get = await client.dataGet(dataAddr);
console.log("get done: ", data_get, " (original data: ", data, ")");
}

document.getElementById ("btn-run").addEventListener("click", run, false);
</script>

<label for="peer_id">Peer ID: <input type="text" id="peer_id" /></label>
<button id="btn-run">Run</button>
</body>
</html>
2 changes: 1 addition & 1 deletion autonomi/src/client/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl Client {

/// Get the estimated cost of storing a piece of data.
pub async fn data_cost(&self, data: Bytes) -> Result<AttoTokens, PayError> {
let now = std::time::Instant::now();
let now = sn_networking::target_arch::Instant::now();
let (data_map_chunk, chunks) = encrypt(data)?;

debug!("Encryption took: {:.2?}", now.elapsed());
Expand Down
3 changes: 3 additions & 0 deletions autonomi/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub mod registers;
#[cfg(feature = "vault")]
pub mod vault;

#[cfg(target_arch = "wasm32")]
pub mod wasm;

// private module with utility functions
mod utils;

Expand Down
105 changes: 105 additions & 0 deletions autonomi/src/client/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use libp2p::Multiaddr;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Client(super::Client);

#[wasm_bindgen]
pub struct ChunkAddr(xor_name::XorName);

#[wasm_bindgen]
pub struct DataAddr(xor_name::XorName);
#[wasm_bindgen]
impl DataAddr {
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
crate::client::address::addr_to_str(self.0)
}
}

#[wasm_bindgen]
pub struct AttoTokens(sn_evm::AttoTokens);
#[wasm_bindgen]
impl AttoTokens {
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.0.to_string()
}
}

#[wasm_bindgen]
impl Client {
#[wasm_bindgen(constructor)]
pub async fn connect(peers: Vec<String>) -> Result<Client, JsError> {
let peers = peers
.into_iter()
.map(|peer| peer.parse())
.collect::<Result<Vec<Multiaddr>, _>>()?;

let client = super::Client::connect(&peers).await?;

Ok(Client(client))
}

#[wasm_bindgen(js_name = chunkPut)]
pub async fn chunk_put(&self, _data: Vec<u8>, _wallet: Wallet) -> Result<ChunkAddr, JsError> {
async { unimplemented!() }.await
}

#[wasm_bindgen(js_name = chunkGet)]
pub async fn chunk_get(&self, addr: ChunkAddr) -> Result<Vec<u8>, JsError> {
let chunk = self.0.chunk_get(addr.0).await?;
Ok(chunk.value().to_vec())
}

#[wasm_bindgen(js_name = dataPut)]
pub async fn data_put(&self, data: Vec<u8>, wallet: Wallet) -> Result<DataAddr, JsError> {
let data = crate::Bytes::from(data);
let xorname = self.0.data_put(data, &wallet.0).await?;
Ok(DataAddr(xorname))
}

#[wasm_bindgen(js_name = dataGet)]
pub async fn data_get(&self, addr: DataAddr) -> Result<Vec<u8>, JsError> {
let data = self.0.data_get(addr.0).await?;
Ok(data.to_vec())
}

#[wasm_bindgen(js_name = dataCost)]
pub async fn data_cost(&self, data: Vec<u8>) -> Result<AttoTokens, JsValue> {
let data = crate::Bytes::from(data);
let cost = self.0.data_cost(data).await.map_err(JsError::from)?;

Ok(AttoTokens(cost))
}
}

#[wasm_bindgen]
pub struct Wallet(evmlib::wallet::Wallet);

/// Get a funded wallet for testing. This either uses a default private key or the `EVM_PRIVATE_KEY`
/// environment variable that was used during the build process of this library.
#[wasm_bindgen(js_name = getFundedWallet)]
pub fn funded_wallet() -> Wallet {
Wallet(test_utils::evm::get_funded_wallet())
}

/// Enable tracing logging in the console.
///
/// A level could be passed like `trace` or `warn`. Or set for a specific module/crate
/// with `sn_networking=trace,autonomi=info`.
#[wasm_bindgen(js_name = logInit)]
pub fn log_init(directive: String) {
use tracing_subscriber::prelude::*;

console_error_panic_hook::set_once();

let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(false) // Only partially supported across browsers
.without_time() // std::time is not available in browsers
.with_writer(tracing_web::MakeWebConsoleWriter::new()); // write events to the console
tracing_subscriber::registry()
.with(fmt_layer)
.with(tracing_subscriber::EnvFilter::new(directive))
.init();
}
3 changes: 3 additions & 0 deletions sn_evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ tempfile = "3.10.1"
[dev-dependencies]
tokio = { version = "1.32.0", features = ["macros", "rt"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasmtimer = { version = "0.2.0", features = ["serde"] }

[lints]
workspace = true
13 changes: 7 additions & 6 deletions sn_evm/src/data_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use evmlib::{
};
use libp2p::{identity::PublicKey, PeerId};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[cfg(not(target_arch = "wasm32"))]
pub use std::time::SystemTime;
#[cfg(target_arch = "wasm32")]
pub use wasmtimer::std::SystemTime;
use xor_name::XorName;

/// The time in seconds that a quote is valid for
Expand All @@ -24,7 +27,7 @@ pub const QUOTE_EXPIRATION_SECS: u64 = 3600;
const LIVE_TIME_MARGIN: u64 = 10;

/// The proof of payment for a data payment
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ProofOfPayment {
/// The Quote we're paying for
pub quote: PaymentQuote,
Expand Down Expand Up @@ -76,9 +79,7 @@ impl Default for QuotingMetrics {
/// A payment quote to store data given by a node to a client
/// Note that the PaymentQuote is a contract between the node and itself to make sure the clients aren’t mispaying.
/// It is NOT a contract between the client and the node.
#[derive(
Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, custom_debug::Debug,
)]
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, custom_debug::Debug)]
pub struct PaymentQuote {
/// the content paid for
pub content: XorName,
Expand Down Expand Up @@ -199,7 +200,7 @@ impl PaymentQuote {

/// Returns true) if the quote has not yet expired
pub fn has_expired(&self) -> bool {
let now = std::time::SystemTime::now();
let now = SystemTime::now();

let dur_s = match now.duration_since(self.timestamp) {
Ok(dur) => dur.as_secs(),
Expand Down

0 comments on commit ae32c84

Please sign in to comment.