Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Added support for a dedicated prover endpoint (`VITE_PROVER_URL`) separated from node URL routing [#936]

### Removed

### Fixed
Expand Down Expand Up @@ -647,6 +649,7 @@ dusk-network/rusk#3982: https://github.com/dusk-network/rusk/issues/3982
dusk-network/rusk#3984: https://github.com/dusk-network/rusk/issues/3984
[#928]: https://github.com/dusk-network/web-wallet/issues/928
[#932]: https://github.com/dusk-network/web-wallet/issues/932
[#936]: https://github.com/dusk-network/web-wallet/issues/936

<!-- VERSIONS -->

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ VITE_SYNC_INTERVAL=300000
VITE_MODE_MAINTENANCE=false
VITE_REOWN_PROJECT_ID="" # the ID of the EVM project (as on Reown Cloud)
VITE_NODE_URL="" # connect to a specific node
VITE_PROVER_URL="" # connect to a specific prover
```

To run a local node different steps are needed, so please read the [related section](#running-a-local-rusk-node).
Expand Down
34 changes: 34 additions & 0 deletions src/lib/stores/__tests__/networkStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,40 @@ describe("Network store", async () => {
vi.unstubAllEnvs();
});

it("should send prove requests to the dedicated prover URL when configured", async () => {
vi.stubEnv("VITE_NODE_URL", "https://nodes.dusk.network/");
vi.stubEnv("VITE_PROVER_URL", "https://provers.dusk.network/");
vi.resetModules();

const store = (await import("..")).networkStore;
const network = await store.connect();

const originalFetch = globalThis.fetch;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier with:

const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(...);

const fetchMock = vi
.fn()
.mockResolvedValueOnce(
new Response(new Uint8Array([1, 2, 3]).buffer, { status: 200 })
);
globalThis.fetch = fetchMock;

try {
await expect(
network.prove(new Uint8Array([1, 2, 3]))
).resolves.toBeInstanceOf(ArrayBuffer);

expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More idiomatic if you use:

expect(fetchMock).toHaveBeenNthCalledWith(
  1, 
  "https://provers.dusk.network/on/prover/prove"
);

"https://provers.dusk.network/on/prover/prove"
);
} finally {
globalThis.fetch = originalFetch;
await store.disconnect();
}

vi.resetModules();
vi.unstubAllEnvs();
});

it("should expose a method to connect to the network and update the store's connection status", async () => {
const store = (await import("..")).networkStore;

Expand Down
52 changes: 50 additions & 2 deletions src/lib/stores/networkStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@dusk/w3sper";

import { rejectAfter } from "$lib/dusk/promise";
import { makeNodeUrl } from "$lib/url";
import { makeNodeUrl, makeProverUrl } from "$lib/url";

import wasmPath from "$lib/vendor/wallet_core-1.3.0.wasm?url";

Expand All @@ -25,10 +25,58 @@ function getNetworkUrl() {
}
}

function getProverUrl() {
if (browser) {
return makeProverUrl();
} else {
return (
(import.meta.env.VITE_PROVER_URL &&
new URL(import.meta.env.VITE_PROVER_URL)) ||
getNetworkUrl()
);
}
}

const networkUrl = getNetworkUrl();
const proverUrl = getProverUrl();

class NetworkWithProver extends Network {
#proverUrl;

/**
* @param {string | URL} url
* @param {string | URL} prover
*/
constructor(url, prover) {
super(url);
this.#proverUrl = prover;
}

/**
* @param {Uint8Array<ArrayBuffer>} circuits
* @returns {Promise<ArrayBuffer>}
*/
async prove(circuits) {
const response = await fetch(
new URL("/on/prover/prove", this.#proverUrl).toString(),
{
body: circuits,
headers: { "Content-Type": "application/octet-stream" },
method: "POST",
}
);

if (!response.ok) {
const body = await response.text().catch(() => "");
throw new Error(body || `HTTP ${response.status}`);
}

return response.arrayBuffer();
}
}

/** @type {Network} */
const network = new Network(networkUrl);
const network = new NetworkWithProver(networkUrl, proverUrl);

/** @type {NetworkStoreContent} */
const initialState = {
Expand Down
68 changes: 68 additions & 0 deletions src/lib/url/__tests__/makeProverUrl.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { makeProverUrl } from "..";

const protocol = "https://";

beforeEach(async () => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});

describe("makeProverUrl", () => {
const localhostString = window.location.hostname;

it("should return a local URL when VITE_PROVER_URL is not set", () => {
vi.stubEnv("VITE_PROVER_URL", "");
expect(makeProverUrl().hostname).toBe(localhostString);
});

it("should return a local URL path when VITE_PROVER_URL is not set and a path is provided", () => {
vi.stubEnv("VITE_PROVER_URL", "");
expect(makeProverUrl("/on/prover/prove").hostname).toBe(localhostString);
expect(makeProverUrl("/on/prover/prove").pathname).toBe("/on/prover/prove");
});

it("should return the devnet prover URL when the hostname starts with 'apps.staging.devnet'", () => {
const hostname = "apps.staging.devnet.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("devnet.provers.dusk.network");
});

it("should return the devnet prover URL when the hostname starts with 'apps.devnet'", () => {
const hostname = "apps.devnet.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("devnet.provers.dusk.network");
});

it("should return the testnet prover URL when the hostname starts with 'apps.staging.testnet'", () => {
const hostname = "apps.staging.testnet.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("testnet.provers.dusk.network");
});

it("should return the testnet prover URL when the hostname starts with 'apps.testnet'", () => {
const hostname = "apps.testnet.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("testnet.provers.dusk.network");
});

it("should return the mainnet prover URL when the hostname starts with 'apps.staging'", () => {
const hostname = "apps.staging.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("provers.dusk.network");
});

it("should return the mainnet prover URL when the hostname starts with 'apps'", () => {
const hostname = "apps.dusk.network";
vi.stubGlobal("location", { hostname, protocol });
expect(makeProverUrl().hostname).toBe("provers.dusk.network");
});

it("should use VITE_PROVER_URL on custom hostnames", () => {
const hostname = "my.custom.domain";
vi.stubGlobal("location", { hostname, protocol });
vi.stubEnv("VITE_PROVER_URL", "https://example-prover.network");

expect(makeProverUrl().hostname).toBe("example-prover.network");
});
});
1 change: 1 addition & 0 deletions src/lib/url/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as makeNodeUrl } from "./makeNodeUrl";
export { default as makeProverUrl } from "./makeProverUrl";
57 changes: 57 additions & 0 deletions src/lib/url/makeProverUrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Constructs a prover URL based on the current subdomain
*
* @param {string} path
* @returns {URL} proverUrl
*/
function makeProverUrl(path = "") {
if (path !== "" && !path.startsWith("/")) {
throw new Error("A path must start with a '/'.");
}

const subDomains = window.location.hostname.split(".");
const hostedNodeDomain = subDomains.slice(-2).join(".");
const proverBaseUrl = import.meta.env.VITE_PROVER_URL
? import.meta.env.VITE_PROVER_URL.replace(/\/+$/, "")
: "";

/**
* @param {string} base
* @returns {URL}
*/
const buildHostedProverUrl = (base) =>
new URL(`${window.location.protocol}${base}${hostedNodeDomain}${path}`);

let proverUrl;

switch (`${subDomains[0]}.${subDomains[1]}.${subDomains[2]}`) {
case "apps.dusk.network":
case "apps.staging.dusk":
proverUrl = buildHostedProverUrl("provers.");
break;
case "apps.devnet.dusk":
case "apps.staging.devnet":
proverUrl = buildHostedProverUrl("devnet.provers.");
break;
case "apps.testnet.dusk":
case "apps.staging.testnet":
proverUrl = buildHostedProverUrl("testnet.provers.");
break;
default:
if (proverBaseUrl) {
proverUrl = new URL(`${proverBaseUrl}${path}`);
} else {
const locationHost = window.location.port
? `${window.location.hostname}:${window.location.port}`
: window.location.hostname;
const fallbackBase = `${window.location.protocol}//${locationHost}`;

proverUrl = new URL(path || "/", fallbackBase);
}
break;
}

return proverUrl;
}

export default makeProverUrl;
2 changes: 2 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default defineConfig(({ mode }) => {
VITE_GAS_PRICE_LOWER: env.VITE_GAS_PRICE_LOWER,
VITE_GAS_PRICE_UPPER: env.VITE_GAS_PRICE_UPPER,
VITE_NODE_URL: env.VITE_NODE_URL,
VITE_PROVER_URL: env.VITE_PROVER_URL,
VITE_REOWN_PROJECT_ID: env.VITE_REOWN_PROJECT_ID,
VITE_SYNC_INTERVAL: env.VITE_SYNC_INTERVAL,
},
Expand Down Expand Up @@ -94,6 +95,7 @@ export default defineConfig(({ mode }) => {
VITE_GAS_PRICE_DEFAULT: "1",
VITE_GAS_PRICE_LOWER: "1",
VITE_NODE_URL: "",
VITE_PROVER_URL: "",
VITE_SYNC_INTERVAL: "300000",
},
environment: "jsdom",
Expand Down