Skip to content

fix: timeout chrome runtime messages for Ledger #31766

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

Merged
merged 15 commits into from
Apr 24, 2025

Conversation

mikesposito
Copy link
Member

@mikesposito mikesposito commented Apr 9, 2025

Description

This PR fixes the following deadlock issue when the Ledger iframe endpoint is unavailable (timeout or error) while using a chrome MV3: #31436

Iframe messages that require user actions to be resolved (e.g., signing) are intentionally left with no timeout for now.

The equivalent issue for MV2/firefox has been fixed separately

Open in GitHub Codespaces

Related issues

Fixes: #31436

Manual testing steps

To reproduce the issue

  1. Add the following line to your hosts file (/etc/hosts) to force the iframe to fail:
    127.0.0.1 metamask.github.io
  2. Checkout to a branch without this fix (e.g. the main branch)
  3. Build the extension with yarn start, to run an MV3 build
  4. Unlock the wallet
  5. Try to add a new account, and to lock the wallet: you won't be able to do neither of these

To test the fix

  1. Add the following line to your hosts file (/etc/hosts) to force the iframe to fail:
    127.0.0.1 metamask.github.io
  2. Checkout to this branch, run yarn start and reload the extension
  3. Unlock the wallet
  4. Try to add a new account: the operation should be successful now
  5. Try to lock the wallet

Screenshots/Recordings

Before

After

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 marked this pull request as draft April 9, 2025 10:33
@@ -9,12 +9,22 @@ import {
OffscreenCommunicationTarget,
} from '../../../../shared/constants/offscreen-communication';

const DEFAULT_MESSAGE_TIMEOUT = 4000;

const SIGNING_TIMEOUT = 20_000;
Copy link
Member Author

Choose a reason for hiding this comment

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

This should be a large value, or the user won't have time to grab the device and accept the signature request

Copy link
Member Author

@mikesposito mikesposito Apr 9, 2025

Choose a reason for hiding this comment

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

Though also not too large, or we'd wait forever for the user signature, never releasing KeyringController's mutex.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. Tricky problem. I can see it taking more than 20 seconds if someone's closely analyzing the signature.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should leave the confirmation screen open while the approval on the device is pending? And have no timeout, so users have as much time as they need. But if they want to cancel it, they can close the screen and we can cancel the operation and release the mutex then.

Copy link
Member Author

@mikesposito mikesposito Apr 10, 2025

Choose a reason for hiding this comment

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

As there is a risk of UX degradation, I assigned a timeout only for messages that don't require a user action (i.e. attemptMakeApp and updateTransportMethod) in 92cfd72

message: IFrameMessage<TAction>,
timeout = DEFAULT_MESSAGE_TIMEOUT,
): Promise<ResponsePayload> {
if (!this.isDeviceConnected) {
Copy link
Member Author

@mikesposito mikesposito Apr 10, 2025

Choose a reason for hiding this comment

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

Hmm, this.isDeviceConnected seems to always be false

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm removing this check for now.

@metamaskbot
Copy link
Collaborator

Builds ready [92cfd72]
UI Startup Metrics (1218 ± 64 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1218109414156412611321
load10679521202571148990
domContentLoaded10629491197571153986
domInteractive17136081631
firstPaint7531301151415225982
backgroundConnect6413279
firstReactRender18153731923
getState13545878
initialActions003001
loadScripts81971394455852921
setupStore7413278
WebpackHomeuiStartup22161719263619423382479
load17371363214816218212075
domContentLoaded17291359214216018132048
domInteractive181274131459
firstPaint165674356724991
backgroundConnect3510348414164
firstReactRender177553621106286
getState1731832069
initialActions317136
loadScripts17241358214115718102017
setupStore336319613198
FirefoxBrowserifyHomeuiStartup14481258196413615041764
load12881099180813313421586
domContentLoaded12881099180713313421586
domInteractive10237181289097
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect26142342423451
firstReactRender25216872634
getState9569789
initialActions001001
loadScripts12641081177913113221562
setupStore8450678
WebpackHomeuiStartup15541389204312716291772
load13421190179511914111537
domContentLoaded13411189179511914111537
domInteractive10034436418997
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect22154662438
firstReactRender35295653747
getState8531589
initialActions102111
loadScripts13221165176811813941517
setupStore8529489

@mikesposito mikesposito marked this pull request as ready for review April 10, 2025 16:58
@metamaskbot
Copy link
Collaborator

Builds ready [ecfcd13]
UI Startup Metrics (1209 ± 59 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1209110913905912471318
load10549621247521132988
domContentLoaded10489581237521133984
domInteractive17136071629
firstPaint8061301256386238983
backgroundConnect7418279
firstReactRender20165262134
getState13635879
initialActions001001
loadScripts80872499351841888
setupStore7513279
WebpackHomeuiStartup21351673266319222842422
load16391284199815417451871
domContentLoaded16321281197715317381865
domInteractive171167111345
firstPaint181726057222572
backgroundConnect251092152762
firstReactRender205563781175996
getState12453879
initialActions316145
loadScripts16271279195315217341864
setupStore217284322849
FirefoxBrowserifyHomeuiStartup13281143166911013831547
load11871022154711312461399
domContentLoaded11871022154711312461398
domInteractive10438284388798
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect19127991928
firstReactRender23195062435
getState7432479
initialActions001001
loadScripts11691009153511312311387
setupStore6420267
WebpackHomeuiStartup14981337195312015771725
load12861131170011013761503
domContentLoaded12861131170011113751503
domInteractive8236403407696
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect21145672238
firstReactRender35285343645
getState8432379
initialActions102111
loadScripts12661112168610913561460
setupStore8520378

@metamaskbot
Copy link
Collaborator

Builds ready [8d59d2f]
UI Startup Metrics (1209 ± 59 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1209110813625912531313
load104495311835710811155
domContentLoaded103995011755610701150
domInteractive17136161629
firstPaint723128118140810551134
backgroundConnect7421279
firstReactRender20154242131
getState1253271728
initialActions001001
loadScripts80271893556835905
setupStore75162811
WebpackHomeuiStartup21351751266517622812368
load16351338206714417481875
domContentLoaded16291334204514317411869
domInteractive161186121341
firstPaint1836641969218316
backgroundConnect289383382557
firstReactRender19955369117311359
getState123177171520
initialActions317135
loadScripts16241329202114217381844
setupStore227293392143
FirefoxBrowserifyHomeuiStartup13641178172410614181595
load12151053157411012801438
domContentLoaded12151053157411012801438
domInteractive1053728338117174
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2314126162149
firstReactRender23195642328
getState74192812
initialActions001001
loadScripts11951042155011012641420
setupStore64253611
WebpackHomeuiStartup1479133618939715221650
load1263114515558113041428
domContentLoaded1263114515558113041428
domInteractive80313973884137
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2314307292238
firstReactRender34284843642
getState94456928
initialActions002111
loadScripts1244112815348012871410
setupStore95526820
Bundle size diffs [🚀 Bundle size reduced!]
  • background: -127 Bytes (0%)
  • ui: 0 Bytes (0%)
  • common: 0 Bytes (0%)

@metamaskbot
Copy link
Collaborator

Builds ready [a439859]
UI Startup Metrics (1217 ± 65 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1217109514456512521355
load105094512405710791185
domContentLoaded104494212205610731179
domInteractive181383111732
firstPaint679119123441510521119
backgroundConnect74273711
firstReactRender21154962135
getState1253981629
initialActions001000
loadScripts80371295854834919
setupStore85274813
WebpackHomeuiStartup21841824260417922862493
load16721400200014717881928
domContentLoaded16651396197914517811899
domInteractive161169111343
firstPaint1846635457225310
backgroundConnect3610376573075
firstReactRender22056378120336355
getState154204211634
initialActions318146
loadScripts16601395195414417771898
setupStore1875092433
FirefoxBrowserifyHomeuiStartup13371157181811413961583
load11921018166111612631439
domContentLoaded11921017166111612621439
domInteractive1033823334114171
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2414113182260
firstReactRender23194742331
getState7412189
initialActions001001
loadScripts11701003164311412391395
setupStore5414167
WebpackHomeuiStartup14841336182810815521710
load12731118159310113251491
domContentLoaded12731118159310113251491
domInteractive77321482284124
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect21156162133
firstReactRender34295353545
getState84315828
initialActions101011
loadScripts12551103157310013041475
setupStore85314810
Bundle size diffs [🚀 Bundle size reduced!]
  • background: -127 Bytes (0%)
  • ui: 0 Bytes (0%)
  • common: 0 Bytes (0%)

let hasResponse = false;

if (timeout) {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

timer should be cleaned if resolve/reject happens elsewhere (in the chrome.runtime.sendMessage callback). You also won't need the hasResponse variable at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point! I changed the way this it's done in e402758

resolve(response.payload);
} else {
reject(response.payload.error);
reject(response?.payload?.error || 'Unknown Ledger error');
Copy link
Contributor

Choose a reason for hiding this comment

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

reject value is sometimes an Error and sometimes a string. It should always be an Error.

Copy link
Member Author

Choose a reason for hiding this comment

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

as response is any, we don't know what response?.payload?.error really is. I wrapped it in an Error instance in 1508807 so we are sure that we always reject with an Error

Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

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

Makes sense to me.

@mcmire
Copy link
Contributor

mcmire commented Apr 23, 2025

^ Well, other than the CI failures 😞

Copy link
Contributor

@cryptodev-2s cryptodev-2s left a comment

Choose a reason for hiding this comment

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

LGTM!

@mikesposito mikesposito enabled auto-merge April 23, 2025 15:35
@mikesposito mikesposito added this pull request to the merge queue Apr 24, 2025
Merged via the queue into main with commit 7b8d936 Apr 24, 2025
167 of 169 checks passed
@mikesposito mikesposito deleted the mikesposito/fix-ledger-deadlock branch April 24, 2025 12:32
@github-actions github-actions bot locked and limited conversation to collaborators Apr 24, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Lock - Wallet stays loading indefinetly when I try to lock it
6 participants