Skip to content

fix: wait for ledger offscreen iframe load#26225

Merged
mikesposito merged 13 commits intodevelopfrom
fix/ledger-offscreen-load
Sep 2, 2024
Merged

fix: wait for ledger offscreen iframe load#26225
mikesposito merged 13 commits intodevelopfrom
fix/ledger-offscreen-load

Conversation

@mikesposito
Copy link
Copy Markdown
Member

@mikesposito mikesposito commented Jul 30, 2024

Description

In order to instantiate a functioning communication between the offscreen iframe for Ledger and the LedgerKeyring (through LedgerOffscreenBridge), we need to make sure that the iframe is loaded before sending any message to it.

We currently wait for the offscreen page to load, but the iframe load is completely async and it will most likely be ready after the rest of the offscreen page, leaving messages proxied to the iframe hanging forever.

On a higher level, this is dangerous because everytime we try to send a message to the Ledger iframe the KeyringController controller-level mutex is locked, and any other operation will wait for its release to proceed - this creates a deadlock situation in the case the iframe does not respond to a message.

This PR makes Ledger iframe initialization into a Promise, and the offscreen page will wait for it to be resolved before sending the "ready" signal back to the extension. To avoid waiting forever, the Ledger initialisation promise races with a 5s timeout: in case of timeout, interactions with Ledger accounts will not work during the entire session (until the offscreen page is re-initialised, and another attempt is made to init Ledger).

Note that the UI setup is not affected by this change, since the Offscreen initialisation is awaited only when unlocking the wallet.

Open in GitHub Codespaces

Related issues

Progresses: #26840

Manual testing steps

N/A

Screenshots/Recordings

N/A

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@mikesposito mikesposito requested review from a team as code owners July 30, 2024 13:25
@codecov
Copy link
Copy Markdown

codecov bot commented Aug 12, 2024

Codecov Report

Attention: Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.

Project coverage is 70.13%. Comparing base (0d971b9) to head (cee0470).
Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
app/scripts/offscreen.js 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           develop   #26225   +/-   ##
========================================
  Coverage    70.13%   70.13%           
========================================
  Files         1417     1418    +1     
  Lines        49444    49446    +2     
  Branches     13835    13835           
========================================
+ Hits         34676    34678    +2     
  Misses       14768    14768           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [9387929]
Page Load Metrics (177 ± 213 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint733971236833
domContentLoaded104123105
load432106177443213
domInteractive104123105
Bundle size diffs
  • background: 0 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 0 Bytes (0.00%)

@mikesposito mikesposito requested a review from a team August 20, 2024 13:55
@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [668640b]
Page Load Metrics (79 ± 7 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint711661062110
domContentLoaded5411674168
load5711679157
domInteractive155328105
Bundle size diffs
  • background: 0 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 0 Bytes (0.00%)

Copy link
Copy Markdown
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

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

Mostly looks good, but I noticed the behavior does not seem ideal in the case of a timeout. Adjusting the timeout duration should make things better in that case I think.

Gudahtt
Gudahtt previously approved these changes Aug 27, 2024
Copy link
Copy Markdown
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

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

LGTM!

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [a30faa1]
Page Load Metrics (78 ± 8 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint701541022010
domContentLoaded4211073178
load4911478178
domInteractive10422594
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 21 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 132 Bytes (0.00%)

cryptodev-2s
cryptodev-2s previously approved these changes Aug 28, 2024
Comment on lines +38 to +43
const ledgerInitTimeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Ledger initialization timed out'));
}, OFFSCREEN_LEDGER_INIT_TIMEOUT);
});
await Promise.race([initLedger(), ledgerInitTimeout]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Elegant, succinct, nice :)

Comment on lines +33 to +35
initializePostMessageStream();
initTrezor();
initLattice();
Copy link
Copy Markdown
Contributor

@MajorLift MajorLift Aug 28, 2024

Choose a reason for hiding this comment

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

Just out of curiosity, would my understanding be correct that the post message stream needs to be synchronously the first task to complete, and then the order doesn't matter between trezor, lattice, and ledger?

Would there be any potential performance, feature, or security gain from initializing the hardware wallets concurrently? Feel free to disregard if this doesn't make any sense, since I don't have much of an understanding of this domain.

Copy link
Copy Markdown
Member

@Gudahtt Gudahtt Aug 28, 2024

Choose a reason for hiding this comment

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

They are effectively executed concurrently in this case, because we're not await-ing anything here. Good question though, in case we did want to await these other steps as well for similar reasons as for Ledger. I think we do want them initializing concurrently to speed up the process.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh that's super cool.😎 I believe these 3 lines would be processed by the call stack instead of the task queue so they would at least always be executed sequentially and in order. I wonder if we could get true parallelism with web workers for this or other similar collections of tasks where speed gains matter. Interesting food for thought!

Copy link
Copy Markdown
Member Author

@mikesposito mikesposito Aug 29, 2024

Choose a reason for hiding this comment

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

I believe they all either spawn an iframe in the offscreen page (i.e. Snaps, Ledger) or setup page event listeners (i.e. Trezor, Lattice): they are all executed sequentially, though we don't wait for the Snap iframe to be fully loaded to move on and init the rest.

The order is not strict, but I thought it made sense to load Ledger as last since it is the only one we explicitly wait for, and this gives a chance to the Snaps iframe to load in the meantime - while if we, for example, were to load Ledger as first, the rest would have to wait for it to either resolve or timeout.

Copy link
Copy Markdown
Member Author

@mikesposito mikesposito Aug 29, 2024

Choose a reason for hiding this comment

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

Good question though, in case we did want to await these other steps as well for similar reasons as for Ledger

Since Trezor and Lattice only require an event listener to be set on chrome.runtime, they are effectively already sync steps that we can't really parallelize (and they should be already fast).

The only component we are initializing out of band is ProxySnapExecutor

Copy link
Copy Markdown
Member Author

@mikesposito mikesposito Aug 29, 2024

Choose a reason for hiding this comment

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

It would be nice to explore the possibility of only setting an event listener for Ledger as well, and spawning the iframe later in time only in case a Ledger Keyring is added to the wallet, and so when the iframe is really needed - because we currently add a Ledger iframe and a LedgerKeyring to the wallet even if the user never interacted with a ledger device for no clear reason

MajorLift
MajorLift previously approved these changes Aug 28, 2024
@mikesposito mikesposito dismissed stale reviews from cryptodev-2s, MajorLift, and Gudahtt via e764bdf August 29, 2024 11:35
@mikesposito mikesposito requested a review from MajorLift August 29, 2024 11:51
@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [e764bdf]
Page Load Metrics (2224 ± 232 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint35431191896715343
domContentLoaded156630822198489235
load157531112224483232
domInteractive15200564220
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 21 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 132 Bytes (0.00%)

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Sep 2, 2024

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [cee0470]
Page Load Metrics (1673 ± 54 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint26920101608326156
domContentLoaded15142001165210953
load15232009167311254
domInteractive226436136
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 21 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 132 Bytes (0.00%)

});
}

chrome.runtime.sendMessage({
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.

Hmm, this still makes extension bootup and every other service using the offscreen document dependent on the Ledger iframe. You also would not be able to unlock before 4s has passed or the Ledger iframe loads.

Wondering if we should be emitting a separate event for Ledger and awaiting that in the keyring somewhere not on init as the longer-term solution.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we should evaluate how useful it is to always add a Ledger keyring to the user's keychain on unlock, even if the user never interacted with a ledger device. If we establish that this is not needed, then as you suggested the best thing to do would be to initialize Ledger only when needed (e.g. when the Keyring is explicitly added by a user), so that we don't have to wait for it before unlocking

@mikesposito mikesposito merged commit 7d50c39 into develop Sep 2, 2024
@mikesposito mikesposito deleted the fix/ledger-offscreen-load branch September 2, 2024 11:55
@github-actions github-actions bot locked and limited conversation to collaborators Sep 2, 2024
@metamaskbot metamaskbot added the release-12.6.0 Issue or pull request that will be included in release 12.6.0 label Sep 2, 2024
@gauthierpetetin gauthierpetetin added release-12.5.0 Issue or pull request that will be included in release 12.5.0 and removed release-12.6.0 Issue or pull request that will be included in release 12.6.0 labels Sep 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-12.5.0 Issue or pull request that will be included in release 12.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants