Skip to content

Conversation

@mikesposito
Copy link
Member

@mikesposito mikesposito commented Apr 20, 2024

Explanation

Part of KeyringController responsibilies is ensuring each operation is mutually exclusive and atomic, updating keyring instances and the vault (or rolling them back) in the same mutex lock.

However, the ability of clients to have direct access to a keyring instance represents a loophole, as with the current implementation they don’t have to comply with the rules enforced by the controller: we should provide a way for clients to interact with a keyring instance through safeguards provided by KeyringController.

The current behavior is this one:

  1. Client obtains a keyring instance through getKeyringForAccount
  2. Client interacts with the instance
  3. Client calls persistAllKeyrings

We should, instead, have something like this:

  1. Client calls a withKeyring method, passing a keyring selector and a callback
  2. KeyringController selects the keyring instance and calls the callback with it, after locking the controller operation mutex
  3. Client interacts with the keyring instance safely, inside the callback
  4. KeyringController, after the callback execution, internally updates the vault or rolls back changes in case of error, and then releases the mutex lock

References

Changelog

@metamask/keyring-controller

  • ADDED: Added withKeyring method
  • DEPRECATED: Deprecated persistAllKeyrings method
    • Use withKeyring instead

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate

@mikesposito mikesposito force-pushed the feat/keyring-controller-atomic-operations branch from 81d5e29 to 6d13430 Compare April 22, 2024 08:59
@mikesposito mikesposito force-pushed the feat/keyring-controller-atomic-operations branch 2 times, most recently from 808ec49 to 871c5fe Compare April 22, 2024 09:32
@mikesposito mikesposito force-pushed the feat/with-keyring branch 4 times, most recently from 784eee9 to 7109a5d Compare April 23, 2024 11:31
@mikesposito mikesposito force-pushed the feat/keyring-controller-atomic-operations branch from 871c5fe to 6bc84be Compare April 24, 2024 16:02
@mikesposito mikesposito force-pushed the feat/keyring-controller-atomic-operations branch from 6bc84be to c3e6a34 Compare April 24, 2024 17:14
@mikesposito mikesposito force-pushed the feat/keyring-controller-atomic-operations branch from 36347ad to 20fd344 Compare April 26, 2024 12:22
Base automatically changed from feat/keyring-controller-atomic-operations to main April 26, 2024 12:39
@mikesposito
Copy link
Member Author

The KeyringController:withKeyring action will be added in #4226.

As there are some issues when using action handlers with generic types, we can add the withKeyring method while we find a solution for the action in a separate PR.

@mikesposito mikesposito marked this pull request as ready for review April 29, 2024 10:37
@mikesposito mikesposito requested a review from a team April 29, 2024 10:37

const result = await fn(keyring);

if (Object.is(result, keyring)) {
Copy link
Member

Choose a reason for hiding this comment

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

Good idea 👍

Gudahtt
Gudahtt previously approved these changes Apr 29, 2024
Copy link
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! Just a few nits left

Copy link
Member

@gantunesr gantunesr left a comment

Choose a reason for hiding this comment

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

Great improvement!!

Comment on lines +372 to +374
| {
address: Hex;
};
Copy link
Member

Choose a reason for hiding this comment

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

This comment is beyond the scope of the PR

How should we ensure the correct execution of an operation when we have multiple keyrings with the same address/account? @Gudahtt, and I believe others, have already pointed this particular issue regarding the removeAccount method

Copy link
Member Author

@mikesposito mikesposito Apr 30, 2024

Choose a reason for hiding this comment

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

Currently every time a new keyring is added (or deserialized from vault) this.#checkForDuplicates is called, but not when adding a new account on a keyring.

This means that a user would be able to add an account that is already on another keyring without issues, but it would then get errors when unlocking the wallet, because the keyrings would be added along with all the accounts already, and checkForDuplicates in #newKeyring would throw.

Perhaps we could check for duplicates also when adding new accounts? it would solve the root issue also regarding removeAccount as well

Copy link
Member

@Gudahtt Gudahtt Apr 30, 2024

Choose a reason for hiding this comment

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

Perhaps we could check for duplicates also when adding new accounts?

This sounds like a good solution for now, for as long as we're using the address as the unique identifier. Eventually we'll be moving away from that idea hopefully

Copy link
Member Author

@mikesposito mikesposito Apr 30, 2024

Choose a reason for hiding this comment

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

Perhaps we could check for duplicates also when adding new accounts? it would solve the root issue also regarding removeAccount as well

Hmm, I'm thinking that this could lead to interesting scenarios as well: if a user imports an account which can also be derived with e.g. index + 1 of the HD keyring, it would end up in a situation where no more HD accounts can be added until the imported one is removed, and the user would not receive clear errors or warnings about that.

Same applies for Snaps: I'm wondering if a Snap could potentially block account additions on other keyrings, or the other way around

Copy link
Member

Choose a reason for hiding this comment

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

for as long as we're using the address as the unique identifier. Eventually we'll be moving away from that idea hopefully

The accounts-controller is introducing the concept of account ID (an UUID) in the clients, it would be good to also use this identifier inside the keyring-controller

I'm wondering if a Snap could potentially block account additions on other keyrings, or the other way around

Yes, this is a possible scenario, I think the long term solution is to use the regular IDs instead of addresses as the identifier. It's a good UX to be able to sign with different keyrings if the user wants to so I don't think we should block the account duplication at a keyring-controller but simplify it at a UI/UX level

@mikesposito mikesposito merged commit ffd16d4 into main Apr 30, 2024
@mikesposito mikesposito deleted the feat/with-keyring branch April 30, 2024 11:33
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.

[keyring-controller] Provide a safe way to interact with keyring instances

4 participants