Repository: dusk-network/rusk (language: rust)

Issue:
Creating a new wallet causes the sync to start from genesis
**Describe the bug**
This is a regression caused by how we changed the evaluation of the sync starting point.

Currently the "create" flow doesn't send a specific block as in the old workflow the sync would have been skipped entirely after resetting the local cache.



Discussion / hints:

Fix patch:
diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md
index 2b031764f5..4a05434b78 100644
--- a/web-wallet/CHANGELOG.md
+++ b/web-wallet/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+- Fix creation of a new wallet causing the sync to start from genesis [#3362]
+
 ## [1.2.0] - 2025-01-16
 
 ### Added
@@ -537,6 +539,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [#3339]: https://github.com/dusk-network/rusk/issues/3339
 [#3354]: https://github.com/dusk-network/rusk/issues/3354
 [#3356]: https://github.com/dusk-network/rusk/issues/3356
+[#3362]: https://github.com/dusk-network/rusk/issues/3362
 
 <!-- VERSIONS -->
 
diff --git a/web-wallet/src/routes/(welcome)/setup/create/+page.js b/web-wallet/src/routes/(welcome)/setup/create/+page.js
new file mode 100644
index 0000000000..f3df926d83
--- /dev/null
+++ b/web-wallet/src/routes/(welcome)/setup/create/+page.js
@@ -0,0 +1,15 @@
+import { error } from "@sveltejs/kit";
+
+import { networkStore } from "$lib/stores";
+
+/** @type {import("./$types").PageLoad} */
+export async function load() {
+  return {
+    currentBlockHeight: await networkStore.getCurrentBlockHeight().catch(() => {
+      error(
+        500,
+        "Unable to retrieve current block height: the network may be down."
+      );
+    }),
+  };
+}
diff --git a/web-wallet/src/routes/(welcome)/setup/create/+page.svelte b/web-wallet/src/routes/(welcome)/setup/create/+page.svelte
index 3fea8663f2..cc7363aec0 100644
--- a/web-wallet/src/routes/(welcome)/setup/create/+page.svelte
+++ b/web-wallet/src/routes/(welcome)/setup/create/+page.svelte
@@ -19,6 +19,9 @@
   import PasswordSetup from "../PasswordSetup.svelte";
   import { goto } from "$lib/navigation";
 
+  /** @type {import("./$types").PageData} */
+  export let data;
+
   /** @type {boolean} */
   let notice = false;
 
@@ -47,6 +50,7 @@
   let enteredMnemonicPhrase = [];
 
   const { userId } = $settingsStore;
+  const { currentBlockHeight } = data;
 
   $: if (showPasswordSetup) {
     password = showPasswordSetup ? password : "";
@@ -141,7 +145,7 @@
       }}
       nextButton={{
         action: async () => {
-          await initializeWallet(mnemonicPhrase.join(" "));
+          await initializeWallet(mnemonicPhrase.join(" "), currentBlockHeight);
           mnemonicPhrase = [];
         },
         disabled: false,
@@ -151,7 +155,7 @@
         Network<br />
         <mark>Syncing</mark>
       </h2>
-      <NetworkSync />
+      <NetworkSync {currentBlockHeight} />
     </WizardStep>
     <WizardStep
       step={5}
diff --git a/web-wallet/src/routes/(welcome)/setup/create/NetworkSync.svelte b/web-wallet/src/routes/(welcome)/setup/create/NetworkSync.svelte
index e5927999e1..343b837f5a 100644
--- a/web-wallet/src/routes/(welcome)/setup/create/NetworkSync.svelte
+++ b/web-wallet/src/routes/(welcome)/setup/create/NetworkSync.svelte
@@ -5,16 +5,12 @@
   import { DOCUMENTATION_LINKS } from "$lib/constants";
   import { IconHeadingCard } from "$lib/containers/Cards";
   import { createNumberFormatter } from "$lib/dusk/number";
-  import { networkStore, settingsStore } from "$lib/stores";
+  import { settingsStore } from "$lib/stores";
   import { mdiCubeOutline } from "@mdi/js";
   $: ({ language } = $settingsStore);
 
   /** @type {bigint} */
-  let currentBlock;
-
-  networkStore.getCurrentBlockHeight().then((block) => {
-    currentBlock = block;
-  });
+  export let currentBlockHeight;
 
   const numberFormatter = createNumberFormatter(language);
 </script>
@@ -27,9 +23,8 @@
 
   <CopyField
     name="Block Height"
-    displayValue={currentBlock ? numberFormatter(currentBlock) : "Loading..."}
-    rawValue={String(currentBlock)}
-    disabled={!currentBlock}
+    displayValue={numberFormatter(currentBlockHeight)}
+    rawValue={String(currentBlockHeight)}
   />
 
   <small>


Test patch:
diff --git a/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap
index 0c81731ed8..d21d4a4db2 100644
--- a/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap
+++ b/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap
@@ -1787,7 +1787,6 @@ exports[`Create > ensures the Network Syncing step renders as expected 1`] = `
         <button
           aria-label="Copy Address"
           class="dusk-button dusk-button--type--button dusk-button--variant--primary dusk-button--size--default dusk-icon-button copy-field__button"
-          disabled=""
           type="button"
         >
           <svg
diff --git a/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js b/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
index 19fe1739e9..20ab7d06eb 100644
--- a/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
+++ b/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
@@ -1,6 +1,7 @@
 import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
 import { cleanup, fireEvent, render } from "@testing-library/svelte";
 import { tick } from "svelte";
+import * as SvelteKit from "@sveltejs/kit";
 import { getKey, setKey } from "lamb";
 import * as bip39 from "bip39";
 import { ProfileGenerator } from "$lib/vendor/w3sper.js/src/mod";
@@ -12,6 +13,7 @@ import loginInfoStorage from "$lib/services/loginInfoStorage";
 import * as shuffleArray from "$lib/dusk/array";
 
 import Create from "../+page.svelte";
+import { load } from "../+page.js";
 
 /**
  * @param {HTMLElement} input
@@ -53,9 +55,14 @@ describe("Create", async () => {
     .then(getKey("address"))
     .then(String);
 
-  const blockHeightSpy = vi
-    .spyOn(networkStore, "getCurrentBlockHeight")
-    .mockResolvedValue(currentBlockHeight);
+  const baseProps = {
+    data: { currentBlockHeight },
+  };
+  const baseOptions = {
+    props: baseProps,
+    target: document.body,
+  };
+
   const generateMnemonicSpy = vi
     .spyOn(bip39, "generateMnemonic")
     .mockReturnValue(mnemonic);
@@ -72,7 +79,7 @@ describe("Create", async () => {
   afterEach(() => {
     cleanup();
     settingsStore.reset();
-    blockHeightSpy.mockClear();
+
     generateMnemonicSpy.mockClear();
     shuffleArraySpy.mockClear();
     clearAndInitSpy.mockClear();
@@ -82,7 +89,6 @@ describe("Create", async () => {
   });
 
   afterAll(() => {
-    blockHeightSpy.mockRestore();
     generateMnemonicSpy.mockRestore();
     shuffleArraySpy.mockRestore();
     clearAndInitSpy.mockRestore();
@@ -91,22 +97,55 @@ describe("Create", async () => {
     initWalletSpy.mockRestore();
   });
 
+  describe("Create page load", () => {
+    const blockHeightSpy = vi
+      .spyOn(networkStore, "getCurrentBlockHeight")
+      .mockResolvedValue(currentBlockHeight);
+
+    afterEach(() => {
+      blockHeightSpy.mockClear();
+    });
+
+    afterAll(() => {
+      blockHeightSpy.mockRestore();
+    });
+
+    it('should retrieve the current block height and pass it to the page as "page data"', async () => {
+      // @ts-ignore we don't care for load parameters here
+      await expect(load()).resolves.toStrictEqual({ currentBlockHeight });
+    });
+
+    it("should call the svelte kit error if the current block height can't be retrieved", async () => {
+      const svelteKitErrorSpy = vi.spyOn(SvelteKit, "error");
+
+      blockHeightSpy.mockRejectedValueOnce(new Error("some error"));
+
+      // @ts-ignore we don't care for load parameters here
+      await expect(load()).rejects.toThrow();
+
+      expect(svelteKitErrorSpy).toHaveBeenCalledTimes(1);
+      expect(svelteKitErrorSpy).toHaveBeenCalledWith(500, expect.any(String));
+
+      svelteKitErrorSpy.mockRestore();
+    });
+  });
+
   it("should render the Existing Wallet notice step of the Create flow if there is a userId saved in localStorage", () => {
     settingsStore.update(setKey("userId", userId));
 
-    const { container } = render(Create);
+    const { container } = render(Create, baseOptions);
 
     expect(container.firstChild).toMatchSnapshot();
   });
 
   it("should render the Terms of Service step of the Create flow if there is no userId saved in localStorage", () => {
-    const { container } = render(Create);
+    const { container } = render(Create, baseOptions);
 
     expect(container.firstChild).toMatchSnapshot();
   });
 
   it("should render the `Securely store your mnemonic phrase!` agreement step after the ToS", async () => {
-    const { container, getByRole } = render(Create);
+    const { container, getByRole } = render(Create, baseOptions);
 
     const mathRandomSpy = vi.spyOn(Math, "random").mockReturnValue(42);
 
@@ -118,7 +157,7 @@ describe("Create", async () => {
   });
 
   it("should not allow the user proceed unless both agreement checks are selected on the `Securely store your mnemonic phrase!` step", async () => {
-    const { getByRole, getAllByRole } = render(Create);
+    const { getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -149,7 +188,7 @@ describe("Create", async () => {
   });
 
   it("correctly renders the Mnemonic Preview page", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -162,7 +201,7 @@ describe("Create", async () => {
   });
 
   it("correctly renders the Mnemonic Verification page", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -176,7 +215,7 @@ describe("Create", async () => {
   });
 
   it("doesn't let the user proceed if they have entered mismatching Mnemonic", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -204,7 +243,7 @@ describe("Create", async () => {
   });
 
   it("lets the user proceed if they have entered a matching Mnemonic", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -228,7 +267,7 @@ describe("Create", async () => {
   });
 
   it("ensures that the Undo button on the Mnemonic Validate step works as expected", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -254,7 +293,7 @@ describe("Create", async () => {
   });
 
   it("ensures the Password step renders as expected", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -284,7 +323,7 @@ describe("Create", async () => {
   });
 
   it("ensures the Network Syncing step renders as expected", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -303,13 +342,11 @@ describe("Create", async () => {
     await fireEvent.click(getByRole("button", { name: "Next" }));
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
-    expect(blockHeightSpy).toHaveBeenCalledTimes(1);
-
     expect(container.firstChild).toMatchSnapshot();
   });
 
   it("ensures the All Done step renders as expected", async () => {
-    const { container, getByRole, getAllByRole } = render(Create);
+    const { container, getByRole, getAllByRole } = render(Create, baseOptions);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
 
@@ -340,7 +377,7 @@ describe("Create", async () => {
   });
 
   it("should initialize the wallet without setting a password", async () => {
-    const { getByRole, getAllByRole } = render(Create);
+    const { getByRole, getAllByRole } = render(Create, baseOptions);
 
     // ToS step
     await fireEvent.click(getByRole("button", { name: "Accept" }));
@@ -375,14 +412,14 @@ describe("Create", async () => {
 
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
-    expect(initWalletSpy).toHaveBeenCalledWith(mnemonic);
+    expect(initWalletSpy).toHaveBeenCalledWith(mnemonic, currentBlockHeight);
 
     await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
 
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
-      undefined
+      currentBlockHeight
     );
 
     // All Done step
@@ -395,7 +432,10 @@ describe("Create", async () => {
   });
 
   it("should initialize the wallet encrypted mnemonic saved in localStorage", async () => {
-    const { getByPlaceholderText, getByRole, getAllByRole } = render(Create);
+    const { getByPlaceholderText, getByRole, getAllByRole } = render(
+      Create,
+      baseOptions
+    );
 
     // ToS step
     await fireEvent.click(getByRole("button", { name: "Accept" }));
@@ -436,14 +476,14 @@ describe("Create", async () => {
 
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
-    expect(initWalletSpy).toHaveBeenCalledWith(mnemonic);
+    expect(initWalletSpy).toHaveBeenCalledWith(mnemonic, currentBlockHeight);
 
     await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
 
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
-      undefined
+      currentBlockHeight
     );
 
     // All Done step