diff --git a/w3sper.js/deno.json b/w3sper.js/deno.json index bd9f871cb..309cb67c3 100644 --- a/w3sper.js/deno.json +++ b/w3sper.js/deno.json @@ -2,6 +2,10 @@ "name": "@dusk/w3sper", "version": "0.1.0-beta.1", "exports": "./src/mod.js", + "imports": { + "@dusk/exu": "jsr:@dusk/exu@0.1.2", + "@dusk/w3sper": "./src/mod.js" + }, "tasks": { "test": "deno test --allow-net --allow-read --allow-write --allow-run --allow-import", "wasm": "cd ../wallet-core && cargo wasm", diff --git a/w3sper.js/src/mod.js b/w3sper.js/src/mod.js index ba6562572..2d2a0cc8d 100644 --- a/w3sper.js/src/mod.js +++ b/w3sper.js/src/mod.js @@ -8,3 +8,4 @@ export * from "./network/mod.js"; export * from "./profile.js"; export * from "./bookkeeper.js"; export * from "./transaction.js"; +export { useAsProtocolDriver } from "./protocol-driver/mod.js"; diff --git a/w3sper.js/src/protocol-driver/mod.js b/w3sper.js/src/protocol-driver/mod.js index 4d61c32b3..b1253861a 100644 --- a/w3sper.js/src/protocol-driver/mod.js +++ b/w3sper.js/src/protocol-driver/mod.js @@ -4,14 +4,14 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -import * as exu from "jsr:@dusk/exu@0.1.2"; +import * as exu from "@dusk/exu"; import { none } from "./none.js"; import { DriverError } from "./error.js"; import * as DataBuffer from "./buffer.js"; import { withAllocator } from "./alloc.js"; -const rng = () => new Uint8Array(32); //crypto.getRandomValues(new Uint8Array(32)); +const rng = () => crypto.getRandomValues(new Uint8Array(32)); const uninit = Object.freeze([ none`No Protocol Driver loaded yet. Call "load" first.`, @@ -36,7 +36,6 @@ export function load(source, importsURL) { } // Parse known globals once. - driverGlobals = protocolDriverModule.task( withAllocator(async function (_exports, allocator) { const { ptr, u32, u64 } = allocator.types; @@ -51,14 +50,30 @@ export function load(source, importsURL) { )(); } -export function unload() { +export async function unload() { if (protocolDriverModule instanceof none || driverGlobals instanceof none) { - return Promise.resolve(); - } else { - return Promise.all([protocolDriverModule, driverGlobals]).then(() => { - [protocolDriverModule, driverGlobals] = uninit; - }); + return; } + + await Promise.all([protocolDriverModule, driverGlobals]); + + [protocolDriverModule, driverGlobals] = uninit; +} + +export function useAsProtocolDriver(source, importsURL) { + load(source, importsURL); + + return { + cleanup() { + return unload(); + }, + then(onFulfilled, onRejected) { + return driverGlobals + .then(() => onFulfilled()) + .catch(onRejected) + .finally(unload); + }, + }; } export async function opening(bytes) { diff --git a/w3sper.js/tests/balances_test.js b/w3sper.js/tests/balances_test.js index a80038dfe..14f5ec1f6 100644 --- a/w3sper.js/tests/balances_test.js +++ b/w3sper.js/tests/balances_test.js @@ -10,14 +10,12 @@ import { Bookkeeper, AddressSyncer, AccountSyncer, -} from "../src/mod.js"; +} from "@dusk/w3sper"; import { test, assert, seeder, Treasury } from "./harness.js"; -test.withLocalWasm = "release"; - test("Account Balance", async () => { - const network = new Network("http://localhost:8080/"); + const network = await Network.connect("http://localhost:8080/"); const user = "oCqYsUMRqpRn2kSabH52Gt6FQCwH5JXj5MtRdYVtjMSJ73AFvdbPf98p3gz98fQwNy9ZBiDem6m9BivzURKFSKLYWP3N9JahSPZs9PnZ996P18rTGAjQTNFsxtbrKx79yWu"; @@ -28,6 +26,8 @@ test("Account Balance", async () => { nonce: 0n, value: 1_001_000_000_000_000n, }); + + await network.disconnect(); }); test("Balances synchronization", async () => { diff --git a/w3sper.js/tests/harness.js b/w3sper.js/tests/harness.js index 91b7e2445..361c67a85 100644 --- a/w3sper.js/tests/harness.js +++ b/w3sper.js/tests/harness.js @@ -19,41 +19,21 @@ const mergeMap = (dest, source, lookup) => { return; }; -import * as ProtocolDriver from "../src/protocol-driver/mod.js"; -import { - test as harnessTest, +export { + test, assert, } from "http://rawcdn.githack.com/mio-mini/test-harness/0.1.0/mod.js"; -import { Bookmark } from "../src/mod.js"; +import { Bookmark } from "@dusk/w3sper"; -export { assert }; +const WASM_RELEASE_PATH = + "../target/wasm32-unknown-unknown/release/wallet_core.wasm"; -export async function test(name, fn) { - let path = ""; - switch (test.withLocalWasm) { - case "debug": - path = "../target/wasm32-unknown-unknown/debug/wallet_core.wasm"; - break; - case "release": - path = "../target/wasm32-unknown-unknown/release/wallet_core.wasm"; - break; - } - - if (path.length > 0 && typeof Deno !== "undefined") { - const testFn = async (...args) => { - const wasm = await Deno.readFile(path); - - ProtocolDriver.load( - wasm, - new URL("./assets/debug-imports.js", import.meta.url), - ); - - await Promise.resolve(fn(...args)).finally(ProtocolDriver.unload); - }; - - return harnessTest(name, testFn); +export function getLocalWasmBuffer() { + if (typeof Deno !== "undefined") { + return Deno.readFile(WASM_RELEASE_PATH); } + return Promise.reject("Can't accesso to file system"); } // Define a seed for deterministic profile generation diff --git a/w3sper.js/tests/index.html b/w3sper.js/tests/index.html index 14175de01..e2b289d4c 100644 --- a/w3sper.js/tests/index.html +++ b/w3sper.js/tests/index.html @@ -15,7 +15,8 @@ diff --git a/w3sper.js/tests/network_test.js b/w3sper.js/tests/network_test.js index 9b4e8aef6..1eed6e620 100644 --- a/w3sper.js/tests/network_test.js +++ b/w3sper.js/tests/network_test.js @@ -4,17 +4,9 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -import { - Network, - ProfileGenerator, - Bookkeeper, - AddressSyncer, - AccountSyncer, -} from "../src/mod.js"; +import { Network } from "@dusk/w3sper"; -import { test, assert, seeder, Treasury } from "./harness.js"; - -test.withLocalWasm = "release"; +import { test, assert } from "./harness.js"; test("Network connection", async () => { const network = new Network("http://localhost:8080/"); diff --git a/w3sper.js/tests/notes_test.js b/w3sper.js/tests/notes_test.js index 8cbf79a13..ef4babd05 100644 --- a/w3sper.js/tests/notes_test.js +++ b/w3sper.js/tests/notes_test.js @@ -3,8 +3,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -import { test, assert } from "./harness.js"; -import { ProfileGenerator, Bookkeeper } from "../src/mod.js"; +import { test, assert, getLocalWasmBuffer } from "./harness.js"; +import { ProfileGenerator, Bookkeeper } from "@dusk/w3sper"; const hex = (bytes) => Array.from(bytes) @@ -18,12 +18,13 @@ const seeder = async () => SEED; const NOTES_RKYV = "./tests/assets/notes.rkyv"; const notesBuffer = await Deno.readFile(NOTES_RKYV); +// NOTE: This tests helps to check some of the protocol driver internals import * as ProtocolDriver from "../src/protocol-driver/mod.js"; -test.withLocalWasm = "release"; - // Test case for default profile test("owened notes balance", async () => { + ProtocolDriver.load(await getLocalWasmBuffer()); + const profiles = new ProfileGenerator(seeder); const owner1 = await Promise.all([ @@ -166,4 +167,6 @@ test("owened notes balance", async () => { picked = await ProtocolDriver.pickNotes(owner4, notes4[0], 1n); assert.equal(picked.size, 0); + + await ProtocolDriver.unload(); }); diff --git a/w3sper.js/tests/profile_test.js b/w3sper.js/tests/profile_test.js index caae5ddf0..f4ca668cf 100644 --- a/w3sper.js/tests/profile_test.js +++ b/w3sper.js/tests/profile_test.js @@ -4,97 +4,87 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -import { - test, - assert, -} from "http://rawcdn.githack.com/mio-mini/test-harness/0.1.0/mod.js"; +import { test, assert, getLocalWasmBuffer } from "./harness.js"; -import { Network, ProfileGenerator } from "../src/mod.js"; +import { ProfileGenerator, useAsProtocolDriver } from "@dusk/w3sper"; // Define a seed for deterministic profile generation const SEED = new Uint8Array(64); const seeder = async () => SEED; +// fetch local wasm +const wasmBuffer = await getLocalWasmBuffer(); + // Test case for initial profile generation test("Initial Profile Generation", async () => { - const network = await Network.connect("http://localhost:8080/"); - const profiles = new ProfileGenerator(seeder); // Verify that the profile list is initially empty assert.equal(profiles.length, 0); - - await network.disconnect(); }); // Test case for default profile -test("Default Profile", async () => { - const network = await Network.connect("http://localhost:8080/"); - - const profiles = new ProfileGenerator(seeder); - const defaultProfile = await profiles.default; - - // Validate the default profile's address and account details - assert.equal( - defaultProfile.address.toString(), - "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN", - ); - - assert.equal( - defaultProfile.account.toString(), - "qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3", - ); - - // Ensure the default profile is indexed correctly - assert.equal(+defaultProfile, 0); - assert.equal(profiles.indexOf(defaultProfile), 0); - assert.equal(await profiles.at(0), defaultProfile); - assert.equal(await profiles.at(1), undefined); - - // Ensure the default profile's keys (address and account) are indexed correctly - assert.equal(+defaultProfile, +defaultProfile.address); - assert.equal(+defaultProfile, +defaultProfile.account); - - // Verify that the profile list has been updated to include the default profile - assert.equal(profiles.length, 1); - - await network.disconnect(); -}); +test("Default Profile", () => + useAsProtocolDriver(wasmBuffer).then(async () => { + const profiles = new ProfileGenerator(seeder); + const defaultProfile = await profiles.default; + + // Validate the default profile's address and account details + assert.equal( + defaultProfile.address.toString(), + "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN", + ); + + assert.equal( + defaultProfile.account.toString(), + "qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3", + ); + + // Ensure the default profile is indexed correctly + assert.equal(+defaultProfile, 0); + assert.equal(profiles.indexOf(defaultProfile), 0); + assert.equal(await profiles.at(0), defaultProfile); + assert.equal(await profiles.at(1), undefined); + + // Ensure the default profile's keys (address and account) are indexed correctly + assert.equal(+defaultProfile, +defaultProfile.address); + assert.equal(+defaultProfile, +defaultProfile.account); + + // Verify that the profile list has been updated to include the default profile + assert.equal(profiles.length, 1); + })); // Test case for generating the next profile -test("Next Profile Generation", async () => { - const network = await Network.connect("http://localhost:8080/"); - - const profiles = new ProfileGenerator(seeder); - - // Generate the next profile - const profile = await profiles.next(); - - // Validate the next profile's address and account details - assert.equal( - profile.address.toString(), - "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8", - ); - - assert.equal( - profile.account.toString(), - "25omWWRyfcMjYNbQZVyc3rypYLi8UqZuthoJHqbCEriRX3z2EmnBaXWZLFL2NvzvDnkoYoHLGiSYQpmupNJj1sSdWNstqzfFEpiqvSNYw7gqvoEiU9FsEHUMG1ZyG3XgL8Rv", - ); - - // Ensure the next profile is indexed correctly - assert.equal(+profile, 1); - assert.equal(profiles.indexOf(profile), 1); - assert.equal(await profiles.at(1), profile); - - // Ensure the next profile's keys (address and account) are indexed correctly - assert.equal(+profile, +profile.address); - assert.equal(+profile, +profile.account); - - // Verify that the profile list has been updated to include the next profile - assert.equal(profiles.length, 2); - - await network.disconnect(); -}); +test("Next Profile Generation", () => + useAsProtocolDriver(wasmBuffer).then(async () => { + const profiles = new ProfileGenerator(seeder); + + // Generate the next profile + const profile = await profiles.next(); + + // Validate the next profile's address and account details + assert.equal( + profile.address.toString(), + "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8", + ); + + assert.equal( + profile.account.toString(), + "25omWWRyfcMjYNbQZVyc3rypYLi8UqZuthoJHqbCEriRX3z2EmnBaXWZLFL2NvzvDnkoYoHLGiSYQpmupNJj1sSdWNstqzfFEpiqvSNYw7gqvoEiU9FsEHUMG1ZyG3XgL8Rv", + ); + + // Ensure the next profile is indexed correctly + assert.equal(+profile, 1); + assert.equal(profiles.indexOf(profile), 1); + assert.equal(await profiles.at(1), profile); + + // Ensure the next profile's keys (address and account) are indexed correctly + assert.equal(+profile, +profile.address); + assert.equal(+profile, +profile.account); + + // Verify that the profile list has been updated to include the next profile + assert.equal(profiles.length, 2); + })); // Test case for ProfileGenerator type checking test("ProfileGenerator Type Checking", () => { diff --git a/w3sper.js/tests/stake_info_test.js b/w3sper.js/tests/stake_info_test.js index 30121e9aa..106cdaaf8 100644 --- a/w3sper.js/tests/stake_info_test.js +++ b/w3sper.js/tests/stake_info_test.js @@ -9,12 +9,10 @@ import { ProfileGenerator, Bookkeeper, AccountSyncer, -} from "../src/mod.js"; +} from "@dusk/w3sper"; import { test, assert, seeder, Treasury } from "./harness.js"; -test.withLocalWasm = "release"; - /** * Tests fetching the stake information using string representations * of accounts, verifying access without requiring instances of @@ -26,7 +24,7 @@ test("stake info without profiles", async () => { "ocXXBAafr7xFqQTpC1vfdSYdHMXerbPCED2apyUVpLjkuycsizDxwA6b9D7UW91kG58PFKqm9U9NmY9VSwufUFL5rVRSnFSYxbiKK658TF6XjHsHGBzavFJcxAzjjBRM4eF", ]; - const network = new Network("http://localhost:8080/"); + const network = await Network.connect("http://localhost:8080/"); const syncer = new AccountSyncer(network); const stakes = await syncer.stakes(users); @@ -52,6 +50,8 @@ test("stake info without profiles", async () => { assert.equal(stakes[1].nonce, 0n); assert.equal(stakes[1].faults, 0); assert.equal(stakes[1].hardFaults, 0); + + await network.disconnect(); }); /** @@ -62,10 +62,11 @@ test("stake info without profiles", async () => { * the use of a decoupled cache to retrieve and store the stake information. */ test("stake info with treasury", async () => { + const network = await Network.connect("http://localhost:8080/"); + const profiles = new ProfileGenerator(seeder); const users = await Promise.all([profiles.default, profiles.next()]); - const network = new Network("http://localhost:8080/"); const accounts = new AccountSyncer(network); const treasury = new Treasury(users); @@ -104,4 +105,6 @@ test("stake info with treasury", async () => { assert.equal(stakes[1].nonce, 0n); assert.equal(stakes[1].faults, 0); assert.equal(stakes[1].hardFaults, 0); + + await network.disconnect(); }); diff --git a/w3sper.js/tests/transfer_test.js b/w3sper.js/tests/transfer_test.js index c002e8869..1f9853388 100644 --- a/w3sper.js/tests/transfer_test.js +++ b/w3sper.js/tests/transfer_test.js @@ -6,49 +6,67 @@ import { Network, - Gas, ProfileGenerator, Bookkeeper, Bookmark, AddressSyncer, AccountSyncer, Transfer, -} from "../src/mod.js"; + useAsProtocolDriver, +} from "@dusk/w3sper"; -import { test, assert, seeder, Treasury } from "./harness.js"; - -test.withLocalWasm = "release"; +import { + test, + assert, + seeder, + Treasury, + getLocalWasmBuffer, +} from "./harness.js"; test("Offline account transfers", async () => { - const profiles = new ProfileGenerator(seeder); - const users = await Promise.all([profiles.default, profiles.next()]); - const to = - "oCqYsUMRqpRn2kSabH52Gt6FQCwH5JXj5MtRdYVtjMSJ73AFvdbPf98p3gz98fQwNy9ZBiDem6m9BivzURKFSKLYWP3N9JahSPZs9PnZ996P18rTGAjQTNFsxtbrKx79yWu"; - - const transfers = await Promise.all( - [77n, 22n].map((amount, nonce) => - new Transfer(users[1]) - .amount(amount) - .to(to) - .nonce(BigInt(nonce)) - .chain(Network.LOCALNET) - .gas({ limit: 500_000_000n }) - .build(), - ), - ); - - assert.equal( - transfers[0].hash, - "72bc75e53d31afec67e32df825e5793594d937ae2c8d5b0726e833dc21db2b0d", - ); - assert.equal(transfers[0].nonce, 1n); - - assert.equal( - transfers[1].hash, - "9b4039406a620b7537ab873e17c0ae5442afa4514a59f77b95644effd293936f", - ); - assert.equal(transfers[1].nonce, 2n); - + // What is inside this block, uses a local protocol driver instead of fetching + // from the network, so it does not need to be connected. + // All transactions are signed locally. + const offlineOperations = useAsProtocolDriver( + await getLocalWasmBuffer(), + ).then(async () => { + const profiles = new ProfileGenerator(seeder); + const to = + "oCqYsUMRqpRn2kSabH52Gt6FQCwH5JXj5MtRdYVtjMSJ73AFvdbPf98p3gz98fQwNy9ZBiDem6m9BivzURKFSKLYWP3N9JahSPZs9PnZ996P18rTGAjQTNFsxtbrKx79yWu"; + + const users = await Promise.all([profiles.default, profiles.next()]); + + const transfers = await Promise.all( + [77n, 22n].map((amount, nonce) => + new Transfer(users[1]) + .amount(amount) + .to(to) + .nonce(BigInt(nonce)) + .chain(Network.LOCALNET) + .gas({ limit: 500_000_000n }) + .build(), + ), + ); + + assert.equal( + transfers[0].hash, + "72bc75e53d31afec67e32df825e5793594d937ae2c8d5b0726e833dc21db2b0d", + ); + assert.equal(transfers[0].nonce, 1n); + + assert.equal( + transfers[1].hash, + "9b4039406a620b7537ab873e17c0ae5442afa4514a59f77b95644effd293936f", + ); + assert.equal(transfers[1].nonce, 2n); + + return { transfers, users }; + }); + + const { transfers, users } = await offlineOperations; + + // Here we gather the transactions generated "offline", we connect to the network, + // and propagate all of them const network = await Network.connect("http://localhost:8080/"); const balances = await new AccountSyncer(network).balances(users);