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

Implement the keycard flows for the new onbaording #17127

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

jrainville
Copy link
Member

@jrainville jrainville commented Jan 24, 2025

What does the PR do

Fixes #17079

Hooks all the keycard flows for the new onboarding.

Based on top of #17058 to have the fix for required properties

status-keycard-go PR here done by @igor-sirotin : keycard-tech/status-keycard-go#13

Affected areas

The new onboarding (using the feature flag).

This is mostly Nim code, but I had to modify the QML a lot because I needed to make a lot of the keycard calls async, because they can be slow and would freeze the app (most notable case is the setPin).

Architecture compliance

Screenshot of functionality (including design for comparison)

  • I've checked the design and this PR matches it

Create account from scratch:

new-account-basic.webm

Create account with mnemonic:

new-account-mnemonic.webm

"Login" with old keycard:

old-account-keycard.webm

Impact on end user

Nothing without the feature flag.

When it is active, the new onboarding shows and all the flows for the keycard should work when there is no previous account (you need to delete or rename the data dir).

How to test

  1. Enable the feature flag: export FLAG_ONBOARDING_V2_ENABLED=1 && make run
  2. Make sure you have no previous account (delete the data dir)
  3. Use the onboarding. All flows to create an account should work now

Risk

Tick one:

  • Low risk: 2 devs MUST perform testing as specified above and attach their results as comments to this PR before merging.
  • High risk: QA team MUST perform additional testing in the specified affected areas before merging.

Low because the feature flag needs to be enabled.

When enabled, worst case is some flows don't work properly.

@jrainville jrainville requested review from a team, micieslak, caybro and alexjba as code owners January 24, 2025 20:23
@jrainville jrainville requested review from Cuteivist, igor-sirotin and glitchminer and removed request for a team January 24, 2025 20:23
@jrainville jrainville changed the title Feat/status keycard go api v2 Implement the keycard flows for the new onbaording Jan 24, 2025
@status-im-auto
Copy link
Member

status-im-auto commented Jan 24, 2025

Jenkins Builds

Click to see older builds (68)
Commit #️⃣ Finished (UTC) Duration Platform Result
d711fb4 #1 2025-01-24 20:29:32 ~6 min macos/aarch64 📄log
d711fb4 #1 2025-01-24 20:31:29 ~8 min macos/x86_64 📄log
✔️ d711fb4 #1 2025-01-24 20:31:45 ~8 min tests/nim 📄log
d711fb4 #1 2025-01-24 20:32:45 ~9 min linux/x86_64 📄log
d711fb4 #1 2025-01-24 20:36:03 ~12 min tests/ui 📄log
d711fb4 #1 2025-01-24 20:37:40 ~14 min windows/x86_64 📄log
✔️ d711fb4 #1 2025-01-24 20:49:12 ~25 min linux-nix/x86_64 📦tgz
6b598ce #3 2025-01-27 16:16:18 ~6 min macos/aarch64 📄log
6b598ce #3 2025-01-27 16:21:00 ~11 min windows/x86_64 📄log
6b598ce #3 2025-01-27 16:22:14 ~12 min macos/x86_64 📄log
6b598ce #3 2025-01-27 16:26:32 ~16 min linux/x86_64 📄log
✔️ 6b598ce #3 2025-01-27 16:28:46 ~18 min tests/nim 📄log
✔️ 6b598ce #3 2025-01-27 16:36:30 ~26 min linux-nix/x86_64 📦tgz
6b598ce #3 2025-01-27 19:53:58 ~13 min tests/ui 📄log
51baa19 #4 2025-01-28 20:20:33 ~6 min macos/x86_64 📄log
51baa19 #4 2025-01-28 20:21:19 ~7 min linux/x86_64 📄log
✔️ 51baa19 #4 2025-01-28 20:22:44 ~8 min tests/nim 📄log
51baa19 #4 2025-01-28 20:26:10 ~11 min windows/x86_64 📄log
51baa19 #4 2025-01-28 20:26:22 ~12 min tests/ui 📄log
✔️ 51baa19 #4 2025-01-28 20:37:02 ~22 min linux-nix/x86_64 📦tgz
3beb47e #5 2025-01-28 21:10:43 ~4 min macos/x86_64 📄log
3beb47e #5 2025-01-28 21:13:17 ~7 min linux/x86_64 📄log
✔️ 3beb47e #5 2025-01-28 21:15:17 ~8 min tests/nim 📄log
3beb47e #5 2025-01-28 21:15:52 ~9 min windows/x86_64 📄log
3beb47e #5 2025-01-28 21:18:36 ~12 min tests/ui 📄log
✔️ 3beb47e #5 2025-01-28 21:27:34 ~21 min linux-nix/x86_64 📦tgz
f25d096 #6 2025-01-29 00:57:57 ~5 min macos/x86_64 📄log
✔️ f25d096 #6 2025-01-29 01:00:22 ~8 min tests/nim 📄log
f25d096 #6 2025-01-29 01:00:38 ~8 min linux/x86_64 📄log
f25d096 #6 2025-01-29 01:02:05 ~9 min windows/x86_64 📄log
f25d096 #6 2025-01-29 01:03:41 ~11 min tests/ui 📄log
✔️ f25d096 #6 2025-01-29 01:10:27 ~18 min linux-nix/x86_64 📦tgz
✔️ f25d096 #6 2025-01-29 08:10:02 ~7 hr 17 min macos/aarch64 🍎dmg
✔️ b3c028a #7 2025-01-29 15:41:41 ~5 min macos/aarch64 🍎dmg
b3c028a #7 2025-01-29 15:42:11 ~5 min macos/x86_64 📄log
b3c028a #7 2025-01-29 15:43:35 ~7 min linux/x86_64 📄log
✔️ b3c028a #7 2025-01-29 15:44:28 ~8 min tests/nim 📄log
b3c028a #7 2025-01-29 15:48:49 ~12 min windows/x86_64 📄log
b3c028a #7 2025-01-29 15:50:13 ~13 min tests/ui 📄log
✔️ b3c028a #7 2025-01-29 15:57:21 ~21 min linux-nix/x86_64 📦tgz
65416c6 #8 2025-01-29 17:25:37 ~4 min macos/x86_64 📄log
✔️ 65416c6 #8 2025-01-29 17:26:46 ~5 min macos/aarch64 🍎dmg
65416c6 #8 2025-01-29 17:28:13 ~7 min linux/x86_64 📄log
✔️ 65416c6 #8 2025-01-29 17:29:56 ~8 min tests/nim 📄log
65416c6 #8 2025-01-29 17:30:38 ~9 min windows/x86_64 📄log
65416c6 #8 2025-01-29 17:34:41 ~13 min tests/ui 📄log
✔️ 65416c6 #8 2025-01-29 17:42:39 ~21 min linux-nix/x86_64 📦tgz
78e4617 #9 2025-01-29 18:10:47 ~4 min macos/x86_64 📄log
✔️ 78e4617 #9 2025-01-29 18:11:26 ~5 min macos/aarch64 🍎dmg
78e4617 #9 2025-01-29 18:13:05 ~7 min linux/x86_64 📄log
✔️ 78e4617 #9 2025-01-29 18:14:43 ~8 min tests/nim 📄log
78e4617 #9 2025-01-29 18:16:00 ~9 min windows/x86_64 📄log
✔️ 78e4617 #9 2025-01-29 18:18:28 ~12 min tests/ui 📄log
✔️ 78e4617 #9 2025-01-29 18:26:42 ~20 min linux-nix/x86_64 📦tgz
ed4446e #10 2025-01-29 20:01:29 ~4 min macos/x86_64 📄log
✔️ ed4446e #10 2025-01-29 20:03:18 ~6 min macos/aarch64 🍎dmg
ed4446e #10 2025-01-29 20:04:17 ~7 min linux/x86_64 📄log
✔️ ed4446e #10 2025-01-29 20:06:04 ~9 min tests/nim 📄log
ed4446e #10 2025-01-29 20:06:58 ~9 min windows/x86_64 📄log
✔️ ed4446e #10 2025-01-29 20:13:45 ~16 min tests/ui 📄log
✔️ ed4446e #10 2025-01-29 20:18:10 ~21 min linux-nix/x86_64 📦tgz
cc6c8ac #12 2025-02-02 13:53:18 ~6 min linux/x86_64 📄log
✔️ cc6c8ac #12 2025-02-02 13:54:25 ~7 min macos/aarch64 🍎dmg
✔️ cc6c8ac #12 2025-02-02 13:55:06 ~8 min tests/nim 📄log
cc6c8ac #12 2025-02-02 13:55:29 ~8 min macos/x86_64 📄log
cc6c8ac #12 2025-02-02 13:56:08 ~9 min tests/ui 📄log
cc6c8ac #12 2025-02-02 13:59:11 ~12 min windows/x86_64 📄log
✔️ cc6c8ac #12 2025-02-02 14:05:22 ~18 min linux-nix/x86_64 📦tgz
Commit #️⃣ Finished (UTC) Duration Platform Result
6dacf2f #13 2025-02-03 11:41:48 ~6 min macos/x86_64 📄log
✔️ 6dacf2f #13 2025-02-03 11:41:55 ~6 min macos/aarch64 🍎dmg
6dacf2f #13 2025-02-03 11:45:10 ~9 min linux/x86_64 📄log
✔️ 6dacf2f #13 2025-02-03 11:45:18 ~9 min tests/nim 📄log
6dacf2f #13 2025-02-03 11:48:31 ~12 min windows/x86_64 📄log
✔️ 6dacf2f #13 2025-02-03 11:54:12 ~18 min tests/ui 📄log
✔️ 6dacf2f #13 2025-02-03 11:56:40 ~21 min linux-nix/x86_64 📦tgz
✔️ 19c138a #14 2025-02-03 13:32:03 ~5 min macos/aarch64 🍎dmg
✔️ 19c138a #14 2025-02-03 13:36:22 ~9 min tests/nim 📄log
✔️ 19c138a #14 2025-02-03 13:39:12 ~12 min macos/x86_64 🍎dmg
✔️ 19c138a #14 2025-02-03 13:39:28 ~12 min tests/ui 📄log
✔️ 19c138a #14 2025-02-03 13:49:13 ~22 min linux-nix/x86_64 📦tgz
✔️ 19c138a #14 2025-02-03 13:49:14 ~22 min linux/x86_64 📦tgz
✔️ 19c138a #14 2025-02-03 13:53:35 ~26 min windows/x86_64 💿exe

Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

Need to digest, and test :)

storybook/pages/OnboardingLayoutPage.qml Show resolved Hide resolved
ui/StatusQ/src/onboarding/enums.h Show resolved Hide resolved
ui/StatusQ/src/onboarding/enums.h Show resolved Hide resolved
try {
const seedwords = root.getSeedWords()
d.seedWords = JSON.parse(seedwords)
root.seedphraseSubmitted(d.seedWords)
Copy link
Member

Choose a reason for hiding this comment

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

This should be emitted only after the user has confirmed and moved to the next page

Copy link
Member

Choose a reason for hiding this comment

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

Earlier we were assuming, that seed words are generated locally, with no interaction with the keycard. So we assumed it's fast, sync operation which never fails. So it was convenient to pass it just as a property.

But if we obtain the seed words from the keycard, the flow is different, with different assumptions.

In the proposed implementation the call remains synchronous. But it may fail (the easiest option is to disconnect keycard in the meantime). Then the UI is left in invalid state, the situation is not handled at all. It makes the flow incomplete.

As it's synchronous, the question is really should be like that. There is communication with the device, probably we should be prepared for some delay without blocking the UI. Now in case of delay we have just freeze.

Next thing is emitting seedphraseSubmitted signal:

const seedwords = root.getSeedWords()
d.seedWords = JSON.parse(seedwords)
root.seedphraseSubmitted(d.seedWords)

This probably doesn't make much sense. The backend knows that seed words were fetched because the function getSeedWords was called.

The problem starts probably in the Figma design, where the case of fetching seed words in not covered at all because wrong assumptions we initially had.

The order of pin setting and seed words fetching is also not updated, making the flow harder to analyze.

@jrainville @caybro wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. GenerateMnemonic (which is called by getSeedWords is a pretty fast procedure. But I agree that even in this case it should be done asyncronously.
    I think it's fine to merge as is and improve after, wdyt?

  2. Not sure what you mean is wrong here? 🤔
    I don't get why we need to JSON.parse, I'll double check and remove it.

cc @micieslak

ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml Outdated Show resolved Hide resolved
@@ -147,7 +166,9 @@ Page {
syncState: root.onboardingStore.syncState
addKeyPairState: root.onboardingStore.addKeyPairState

seedWords: root.onboardingStore.getMnemonic().split(" ")
getSeedWords: function () {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a function now?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can only get the mnemonic when the keycard is authorized or initialized, so I pass the function to be called later when we have the right to call it.

onboardingModuleInst.obtainingPasswordSuccess.connect(root.obtainingPasswordSuccess)
onboardingModuleInst.obtainingPasswordError.connect(root.obtainingPasswordError)
d.onboardingModuleInst.appLoaded.connect(root.appLoaded)
// TODO implement the following signals
Copy link
Member

Choose a reason for hiding this comment

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

Even if not implemented yet, this can stay here uncommented, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since the view functions don't exist, any connect put below those don't activate because the function throws an error.
It felt safer to comment those for now as it confused me for a while why my signals didn't work.

Copy link
Member

Choose a reason for hiding this comment

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

Just declare those signals?

Copy link
Member

@micieslak micieslak left a comment

Choose a reason for hiding this comment

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

Nice to see the first version working!

However I think we should rethink several points raised in the review, especially how we handle the async operations and keep good separation between pure components UI and the underlying logic.

signal keycardPinCreated(string pin)
signal setPinFailed(string error)
Copy link
Member

Choose a reason for hiding this comment

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

The general rule regarding signals/functions is that signals are used to notify change/event generated internally within the component. So in vast majority of changes they are called (emitted) from the component, not from outside.

I we want to interact with the component from outside, function is proper choice.

More details can be found there: https://github.com/Furkanzmc/QML-Coding-Guide?tab=readme-ov-file#sh-2-when-to-use-functions-and-signals

try {
const seedwords = root.getSeedWords()
d.seedWords = JSON.parse(seedwords)
root.seedphraseSubmitted(d.seedWords)
Copy link
Member

Choose a reason for hiding this comment

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

Earlier we were assuming, that seed words are generated locally, with no interaction with the keycard. So we assumed it's fast, sync operation which never fails. So it was convenient to pass it just as a property.

But if we obtain the seed words from the keycard, the flow is different, with different assumptions.

In the proposed implementation the call remains synchronous. But it may fail (the easiest option is to disconnect keycard in the meantime). Then the UI is left in invalid state, the situation is not handled at all. It makes the flow incomplete.

As it's synchronous, the question is really should be like that. There is communication with the device, probably we should be prepared for some delay without blocking the UI. Now in case of delay we have just freeze.

Next thing is emitting seedphraseSubmitted signal:

const seedwords = root.getSeedWords()
d.seedWords = JSON.parse(seedwords)
root.seedphraseSubmitted(d.seedWords)

This probably doesn't make much sense. The backend knows that seed words were fetched because the function getSeedWords was called.

The problem starts probably in the Figma design, where the case of fetching seed words in not covered at all because wrong assumptions we initially had.

The order of pin setting and seed words fetching is also not updated, making the flow harder to analyze.

@jrainville @caybro wdyt?

if (root.keycardState === Onboarding.KeycardState.NotEmpty) {
if (d.withNewSeedphrase) {
// Need to authorize before getting a seedphrase
root.authorizationRequested()
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look correct. If the authorization doesn't require any input from from the user from this page and is not caused by any action triggered there, that request shouldn't be generated be the UI. In other words, it should not be the responsibility of the UI, which should be unaware of this process.

signal keycardPinCreated(string pin)
signal setPinFailed(string error)
Copy link
Member

Choose a reason for hiding this comment

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

For async operations like adding key pair we have dedicated enum defined (AddKeyPairState in enums.h) which is used for handling that ansyc operation. Similarly for syncing. Here the approach is different, UI emits keycardPinCreatedand expects that at some point in the futuresetPinFailed` will be called or some other thing will happen which means success (btw, how we may know that pin setting succeeded)?

@caybro
Copy link
Member

caybro commented Jan 27, 2025

@jrainville needs a rebase, and more importantly, updating the QML tests. For that, I'd suggest first updating the individual QML pages we have for the various flows as well

Base automatically changed from feat/new-onboarding-metrics to master January 27, 2025 16:06
@jrainville jrainville force-pushed the feat/status-keycard-go-api-v2 branch from d711fb4 to 6b598ce Compare January 27, 2025 16:09
@jrainville jrainville force-pushed the feat/status-keycard-go-api-v2 branch from 6b598ce to 51baa19 Compare January 28, 2025 20:14
Copy link
Member Author

@jrainville jrainville left a comment

Choose a reason for hiding this comment

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

Thanks for the reviews. I answered the questions and also refactored the code to use states instead of signal

storybook/pages/OnboardingLayoutPage.qml Show resolved Hide resolved
ui/StatusQ/src/onboarding/enums.h Show resolved Hide resolved
@@ -147,7 +166,9 @@ Page {
syncState: root.onboardingStore.syncState
addKeyPairState: root.onboardingStore.addKeyPairState

seedWords: root.onboardingStore.getMnemonic().split(" ")
getSeedWords: function () {
Copy link
Member Author

Choose a reason for hiding this comment

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

We can only get the mnemonic when the keycard is authorized or initialized, so I pass the function to be called later when we have the right to call it.

onboardingModuleInst.obtainingPasswordSuccess.connect(root.obtainingPasswordSuccess)
onboardingModuleInst.obtainingPasswordError.connect(root.obtainingPasswordError)
d.onboardingModuleInst.appLoaded.connect(root.appLoaded)
// TODO implement the following signals
Copy link
Member Author

Choose a reason for hiding this comment

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

Since the view functions don't exist, any connect put below those don't activate because the function throws an error.
It felt safer to comment those for now as it confused me for a while why my signals didn't work.

Copy link
Contributor

@igor-sirotin igor-sirotin left a comment

Choose a reason for hiding this comment

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

Good job! 👍
I didn't check out the QML, I see guys reviewed it already.

src/app/modules/onboarding/controller.nim Outdated Show resolved Hide resolved
src/app/modules/onboarding/module.nim Outdated Show resolved Hide resolved
password = "", # For keycard it will be substituted with`encryption.publicKey` in status-go
seedPhrase,
recoverAccount = false,
keycardInstanceUID = keycardEvent.keycardInfo.instanceUID,
Copy link
Contributor

Choose a reason for hiding this comment

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

We're using this last received keycardEvent, which I think can be a bit wrong.

I think we should kinda hold/remember the active InstanceUID, and in case a keycard was replaced with a different one, we should start any operations from the beginning.

This would basically ensure that any further operations are executed at the expected keycard, not just the one that's currently inserted. The time window for such mistakes is short and it's difficult to repro, but anyway should be important to do.

src/app/modules/onboarding/module.nim Outdated Show resolved Hide resolved
src/app_service/service/accounts/service.nim Outdated Show resolved Hide resolved
src/app/modules/onboarding/module.nim Show resolved Hide resolved
src/app_service/service/keycardV2/service.nim Outdated Show resolved Hide resolved
src/app_service/service/keycardV2/service.nim Outdated Show resolved Hide resolved
src/app_service/service/transaction/dto.nim Outdated Show resolved Hide resolved
src/app_service/service/transaction/service.nim Outdated Show resolved Hide resolved
@igor-sirotin igor-sirotin force-pushed the feat/status-keycard-go-api-v2 branch from ed4446e to 0061e2b Compare February 2, 2025 13:45
@igor-sirotin
Copy link
Contributor

igor-sirotin commented Feb 2, 2025

I've tested all keycard flows, seems to be working fine.


A few minor things I've noted, not sure if this is expected.

  1. Keycard is not empty screen doesn't automatically go back to Insert keycard.
    I already have removed the keycard, but still see this screen:
    image

    Here's the video. I am removing the keycard before going back and never insert it again:
    https://github.com/user-attachments/assets/1450a7c4-9193-40a0-b402-fc3b363e2f0f

  2. The keycard session is started together with the service, even when we're not yet working with the keycard.
    I think it would be better to keep the service running for as little as possible, but this can be improved later.

  3. DisplayName is stil 3-words alias. This is being fixed by Mobile team on status-go side.

    image

Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

@igor-sirotin can you rebase please to fix the builds?

@igor-sirotin
Copy link
Contributor

igor-sirotin commented Feb 3, 2025

@igor-sirotin can you rebase please to fix the builds?

I fixed the rebase issues yesterday, just forgot to push 🤦

@caybro
Copy link
Member

caybro commented Feb 3, 2025

@igor-sirotin can you rebase please to fix the builds?

I fixed the rebase issues yesterday, just forgot to push 🤦

Still failing :/

@igor-sirotin
Copy link
Contributor

Still failing :/

@caybro, I will take care of the builds. Would be cool if you can review meanwhile 🙂

@igor-sirotin igor-sirotin requested a review from caybro February 3, 2025 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Onboarding] Integrate Keycard flows for the new onboarding
6 participants