-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Restore multiple consecutive accounts with balances. #4898
Conversation
Feedback is appreciated. I've got 4 failing tests in MetaMaskController due the 2000ms timeouts. I'd love any suggestions on how to deal with that properly. |
ce7e90c
to
678a096
Compare
89da9bc
to
c0ab438
Compare
This is ready for review. I'm not getting the errors that CI is seeing locally and without config access of the ability to rerun the build, I'm not sure what I can do to green these in CI. |
YES. Excited for this. However, I suppose this warrants a few design questions. I apologize if some of these qs are self-explaining in the code--I haven't touched the codebase in awhile:
|
Thanks @Zanibas. No apologies needed. I pretty much stuck to the letter of the original issue.
|
I'd like as second opinion from either @danjm or @danfinlay, but this as an MVP sounds sufficient, but would like to open an additional issue for issue #1 and to tackle the edge case described in #2. The more common case I can imagine for #2 is a case where people are leaving their first account blank and instead populating the second account (as a light countermeasure to anyone snooping on their phrase). |
@Zanibas @danfinlay Hope you guys had a good weekend. I'd love to wrap this bounty up this week. How's the code looking after some reflection time? |
@@ -26,8 +24,7 @@ describe('Using MetaMask with an existing account', function () { | |||
|
|||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' | |||
const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC' | |||
const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6' | |||
const tinyDelayMs = 500 | |||
const testPrivateKey2 = 'f5e21e00974fa60353d003938363db0296542d2d24208e88699536940f18fc80' |
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.
Not sure if this is because of the merge conflict. If not, why is testPrivateKey2
changed 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.
testPrivateKey2
has been changed here because the tests that use it, 'Imports an account with private key', are attempting to import an account which is not already present in the accounts list. Because all of the previous private keys have been given balances for one reason or another in the previous tests, they are part of the accounts list and can't be imported a second time.
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 change is no longer required now that the network switches at the beginning of the 'Imports an account with private key' test chain
c0ab438
to
ee9d40e
Compare
@Zanibas @danjm @danfinlay The merge conflict has been resolved and allowed me to revert the private key change. The two failing CI checks are still passing locally. I've tried it on two different machines and both pass. I did notice that if I'm running too many applications on my laptop when I run the tests, they both fail due to timeout errors, so the failures in CircleCI might just be because the VMs are underpowered. |
@Zanibas @danfinlay got 3 minutes to let @dmvt nkow how he's doing here? |
@dmvt hey, thanks for this! (And apologies for the slow review.) Would you mind rebasing this on |
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 PR looks pretty good, I've left a few comments.
Additionally, we should add a test case with the situation @Zanibas described above where we have an account without a balance sandwiched between accounts with balances. Having this test will document that our stopping at the first account with a zero balance is intentional.
app/scripts/metamask-controller.js
Outdated
* Get an account balance from the AccountTracker or request it directly from the network. | ||
* @param {string} account - The account address | ||
*/ | ||
getBalance (account) { |
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.
Nit: Can we rename this to address
?
app/scripts/metamask-controller.js
Outdated
|
||
if (cached && cached.balance) { | ||
resolve(cached.balance) | ||
} else { |
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 think we can return
inside the if
block and then we won't need to nest the rest of the fn inside else
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 no... the accountTracker is not necessarily up to date at this point in the code. Many times it does not yet have a balance
.
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.
Hmm, what I was getting at is that if we add a return statement after resolving the promise we don't need to have the rest of the function inside an else block since there isn't anything after this conditional.
It's certainly not a blocking comment though, more a small code-flow thing.
app/scripts/metamask-controller.js
Outdated
this.networkController.ethQuery.getBalance(account, (error, balance) => { | ||
if (error) { | ||
reject(error) | ||
console.error(error) |
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.
There is a log
logger that we use instead of the using console
directly—can we switch this line here to use that?
@@ -134,13 +136,23 @@ describe('MetaMaskController', function () { | |||
describe('#createNewVaultAndRestore', function () { | |||
it('should be able to call newVaultAndRestore despite a mistake.', async function () { | |||
const password = 'what-what-what' | |||
sandbox.stub(metamaskController, 'getBalance') | |||
metamaskController.getBalance.callsFake(() => { | |||
return new Promise((resolve) => { resolve('0x0') }) |
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 might be able to simplify this by using Promise.resolve()
:
metamaskController.getBalance.callsFake(() => Promise.resolve('0x0'))
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.
Similarly for the instances below
it('should ask the network for a balance when not known by accountTracker', async () => { | ||
const accounts = {} | ||
const balance = '0x14ced5122ce0a000' | ||
const realNetworkController = metamaskController.networkController |
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.
Instead of managing this ourselves we can use sinon here:
const ethQueryGetBalance = sinon.stub(metamaskController.networkController.ethQuery, "getBalance").callsFake(/* the fake impl that's below */)
(I think this should work.)
And then we can call ethQueryGetBalance.restore()
below and maybe even assert that it was indeed called.
On fixing the e2e conflicts, we might be able to defer to what's in |
Thanks @whymarrh. I'll get those changes and the rebase in later today. |
7c22874
to
6be670b
Compare
I believe that addresses all of your comments @whymarrh. |
app/scripts/metamask-controller.js
Outdated
const vault = await this.keyringController.createNewVaultAndRestore(password, seed) | ||
const vault = await keyringController.createNewVaultAndRestore(password, seed) | ||
|
||
const ethQuery = new EthQuery(this.networkController._provider) |
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 should be able to use this.provider
here instead of reaching into the network controller
@dmvt thanks! Two last comments (I think), one about the provider and additionally, we should add a test case with the situation @Zanibas described above where we have an account without a balance sandwiched between accounts with balances. Having this test will document that our stopping at the first account with a zero balance is intentional. |
6be670b
to
df799d7
Compare
@whymarrh Good catch on the it('should restore any consecutive accounts with balances', async () => {
sandbox.stub(metamaskController, 'getBalance')
metamaskController.getBalance.withArgs(TEST_ADDRESS).callsFake(() => {
return Promise.resolve('0x14ced5122ce0a000')
})
metamaskController.getBalance.withArgs(TEST_ADDRESS_2).callsFake(() => {
return Promise.resolve('0x0')
})
// If we were continuing past the above zero balance the deepEqual would fail because of this stub:
metamaskController.getBalance.withArgs(TEST_ADDRESS_3).callsFake(() => {
return Promise.resolve('0x14ced5122ce0a000')
})
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
assert.deepEqual(metamaskController.getState().identities, {
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
[TEST_ADDRESS_2]: { address: TEST_ADDRESS_2, name: DEFAULT_LABEL_2 },
})
}) |
Aw shoot, you're right. My bad. |
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 looks good, thanks @dmvt!
Thanks for all the hard work, sorry I fell behind on this @dmvt! Great job on this, it's going to save a lot of people stress when restoring! |
Happy to help! |
Fixes #654
Solution/Spec
When first restoring MetaMask from a previous seed phrase, in a loop:
Submitting a pull request
When you're done with your project / bugfix / feature and ready to submit a PR, there are a couple guidelines we ask you to follow:
tests
folder.CHANGELOG.md
with a link to your PR.Fixes #$ISSUE_NUMBER
.develop
: Submit your PR against thedevelop
branch. This is where we merge new features so they get some time to receive extra testing before being pushed tomaster
for production. If your PR is a hot-fix that needs to be published urgently, you may submit a PR against themaster
branch, but this PR will receive tighter scrutiny before merging.:thumbsup
,:+1
, orLGTM
from a user with aMember
badge before merging.