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

feat: WASM bindings #2221

Merged
merged 7 commits into from
Oct 14, 2024
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
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 @@ -33,5 +33,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
Loading