-
-
Notifications
You must be signed in to change notification settings - Fork 200
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
Merge EthKeyringController
into KeyringController
#3830
Conversation
49d7b61
to
bd80fe1
Compare
e087e79
to
d11a19a
Compare
/** | ||
* Update the controller state with its current keyrings. | ||
*/ | ||
async #updateKeyringsInState(): Promise<void> { | ||
const keyrings = await Promise.all(this.#keyrings.map(displayForKeyring)); | ||
this.update((state) => { | ||
state.keyrings = keyrings; | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the equivalent of EthKeyringController.updateMemStoreKeyrings
: https://github.com/MetaMask/KeyringController/blob/0134de248cc16126b9dcb880a4d96776f8dbc043/src/KeyringController.ts#L358
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps #updateStateKeyrings
would reflect the old name better, but I'm not sure what would be the best naming for this
/** | |
* Update the controller state with its current keyrings. | |
*/ | |
async #updateKeyringsInState(): Promise<void> { | |
const keyrings = await Promise.all(this.#keyrings.map(displayForKeyring)); | |
this.update((state) => { | |
state.keyrings = keyrings; | |
}); | |
} | |
/** | |
* Update the controller state with its current keyrings. | |
*/ | |
async #updateStateKeyrings(): Promise<void> { | |
const keyrings = await Promise.all(this.#keyrings.map(displayForKeyring)); | |
this.update((state) => { | |
state.keyrings = keyrings; | |
}); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like updateKeyringsInState
over updateStateKeyrings
as it reads better. However it might be nice to convey that it's not taking a set of keyrings but rather it's persisting the keyrings that have already been saved. So perhaps persistKeyrings
? I don't know if we already use "persist" elsewhere to mean "write to state", but it seems to fit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately we already have a method called persistAllKeyrings
, which saves the contents of the #keyrings
array to the encrypted (and persisted) storage - the vault
This function instead, syncs the Controller state with the contents of the #keyrings
array, so we are not persisting keyrings anywhere, but we are updating their representation in the controller state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, gotcha. Okay, updateKeyringsInState
seems fine then.
88ab32e
to
a5ed982
Compare
@@ -35,13 +34,13 @@ export default class MockEncryptor implements ExportableKeyEncryptor { | |||
throw new Error(INVALID_PASSWORD_ERROR); | |||
} | |||
|
|||
return cacheVal ?? {}; | |||
return JSON.parse(cacheVal) ?? {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is due to an interesting discovery regarding the QRKeyring. Apparently, the serialize
method of the keyring does not return an object compatible with what we internally call "serializable" (i.e. Json
type), as it contains keys with undefined
as value
The only reason why this does not break production is that the encryptor
calls JSON.stringify
, which removes any key with undefined as value.
Without this, the unlock operation would be broken after adding the QRKeyring, as the isSerializedKeyringArray
assertion would fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to this, I'm also realising that we make sure that what is unlocked from the vault is a correct serializable object, but we don't do that when locking and saving the keyrings to the vault (the other way around): this means that:
- we are not sure that we are saving a proper object in the vault, but we expect it to be when reading from it.
- we are effectively relying on the fact that the object that we retrieve from the vault is not the same that we saved in it
Perhaps we could add the same check in persistAllKeyrings
, even if it is not required type-wise. This would ensure that each keyring implements serialize
properly (given that this is our requirement)
* Checks for duplicate keypairs, using the the first account in the given | ||
* array. Rejects if a duplicate is found. | ||
* | ||
* Only supports 'Simple Key Pair'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhmm, we probably want to avoid account duplication with any keyring type, but I'm wondering if we want to change this behavior in this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah perhaps not, but good eye. I wonder if this was done intentionally for simplicity or was added before the other keyring types were added and hasn't been updated since.
if (type === KeyringTypes.hd && (!isObject(opts) || !opts.mnemonic)) { | ||
if (!keyring.generateRandomMnemonic) { | ||
throw new Error( | ||
KeyringControllerError.UnsupportedGenerateRandomMnemonic, | ||
); | ||
} | ||
|
||
keyring.generateRandomMnemonic(); | ||
await keyring.addAccounts(1); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most likely unrelated to this PR: I feel like this should be the responsibility of a hypothetical init
method to be added on HDKeyring
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR refactors tests related to user operations methods, to align them with the structure used for other methods. It also adds test coverage for keyrings that do not support user operations ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? For example: * Fixes #12345 * Related to #67890 --> * Related to #3830 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> No functional changes ## 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
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR adds additional tests for edge cases, useful for #3830. Some `istanbul ignore if` statements are also being removed ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? For example: * Fixes #12345 * Related to #67890 --> * Fixes #3767 * See #3830 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> No functional changes ## 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
18c9f9a
to
84d9cf4
Compare
c26aba4
to
83a94cd
Compare
/** | ||
* Create keyring. | ||
* Create a new keyring, ensuring that the first account is | ||
* also created. | ||
* | ||
* - Creates a new vault. | ||
* - Creates a new keyring with at least one account. | ||
* - Makes the first account the selected account. | ||
* @param type - Keyring type to instantiate. | ||
* @param opts - Optional parameters required to instantiate the keyring. | ||
* @returns A promise that resolves if the operation was successful. | ||
* @returns A promise that resolves if the operation is successful. | ||
*/ | ||
async #createKeyring(type: string, opts?: unknown) { | ||
async #createKeyringWithFirstAccount(type: string, opts?: unknown) { | ||
const keyring = (await this.addNewKeyring(type, opts)) as EthKeyring<Json>; | ||
|
||
const [firstAccount] = await keyring.getAccounts(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method has been renamed to better match what it is used for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now realizing the new naming might also be incorrect since the first account is not explicitly created here, but only expected to be created - and this is a case only for HD keyring because we explicitly check for that in the addNewAccount
method.
I think there might be a better way to do all this (see also my previous comment: #3830 (comment))
if (!keyring) { | ||
throw new Error(KeyringControllerError.NoKeyring); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is not possible, even in case the keyring builder is somehow broken and returns undefined
, as #newKeyring
would throw an error.
b2f6d4e
to
a9d1f1a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked every method in eth-keyring-controller
and everything seems to be represented here. Nice! All of my comments are either thoughts or minor suggestions.
@@ -0,0 +1 @@ | |||
declare module '@metamask/eth-hd-keyring'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that adding these files will make it possible to build @metamask/keyring-controller
. But these type augmentations won't get included in the compiled version of the package, so in order to use the package, consumers may have to add these to their project as well.
However... I see that we copied these from eth-keyring-controller
. And obviously we haven't had the problem I described above, because otherwise we would have had to handle it already. So maybe we don't need to do anything right now, but we should probably make a note to add type definition files to these packages (even if they're empty) so we don't have to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhm good point. We should tackle the typescript migration for these keyring packages, and that might be part of the effort to align them all with the Keyring API - but in the meantime, adding blank type definition files to these packages sounds like a good short-term solution.
return res.concat(arr); | ||
}, []); | ||
|
||
// Cast to `Hex[]` here is safe here because `addresses` has no nullish |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought for later: it would be nice if we could add an overload to normalize
so it returns Hex if given something non-nullish, so we don't have to cast here.
/** | ||
* Update the controller state with its current keyrings. | ||
*/ | ||
async #updateKeyringsInState(): Promise<void> { | ||
const keyrings = await Promise.all(this.#keyrings.map(displayForKeyring)); | ||
this.update((state) => { | ||
state.keyrings = keyrings; | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like updateKeyringsInState
over updateStateKeyrings
as it reads better. However it might be nice to convey that it's not taking a set of keyrings but rather it's persisting the keyrings that have already been saved. So perhaps persistKeyrings
? I don't know if we already use "persist" elsewhere to mean "write to state", but it seems to fit.
* Checks for duplicate keypairs, using the the first account in the given | ||
* array. Rejects if a duplicate is found. | ||
* | ||
* Only supports 'Simple Key Pair'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah perhaps not, but good eye. I wonder if this was done intentionally for simplicity or was added before the other keyring types were added and hasn't been updated since.
* | ||
* @param error - Error message or Error object to throw. | ||
*/ | ||
export function throwError(error: string | Error): never { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like such a simple function that I'm curious if we need it, but if it was copied from somewhere else, then I suppose that's fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We copied this one from eth-keyring-controller
, but it is used in only three occurrences, and all of them just pass a string - so I think this is just adding cognitive overload.
removed in a9712ca
a9712ca
to
9985f69
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
9985f69
to
dd28464
Compare
Explanation
This PR moves the logic implemented in
EthKeyringController
toKeyringController
, leaving KeyringController's API unchangedReferences
EthKeyringController
intoKeyringController
#3766Changelog
@metamask/keyring-controller
keyringBuilderFactory
utilGenericEncryptor
,ExportableKeyEncryptor
andSerializedKeyring
types@metamask/eth-keyring-controller
dep@metamask/eth-keyring-controller
@metamask/eth-hd-keyring
@metamask/eth-simple-keyring
@metamask/eth-sig-util
@metamask/browser-passworder
Checklist