Repository navigation
feat(multichain-account-service): add option to decide what to await for when creating group #6759
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
Changes from all commits
d686ee9
fcb0343
ec1b52e
403d744
80fdc2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -347,9 +347,32 @@ describe('MultichainAccountWallet', () => { | |
| expect( | ||
| await wallet.createMultichainAccountGroup(groupIndex), | ||
| ).toBeDefined(); | ||
| await new Promise(process.nextTick); | ||
| expect(mockSolProviderError).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('fails to create an account group if any of the provider fails to create its account and waitForAllProvidersToFinishCreatingAccounts is true', async () => { | ||
| const groupIndex = 1; | ||
|
|
||
| const mockEvmAccount = MockAccountBuilder.from(MOCK_HD_ACCOUNT_1) | ||
| .withEntropySource(MOCK_HD_KEYRING_1.metadata.id) | ||
| .withGroupIndex(0) | ||
| .get(); | ||
| const { wallet, providers } = setup({ | ||
| accounts: [[mockEvmAccount]], // 1 provider | ||
| }); | ||
| const [provider] = providers; | ||
| provider.createAccounts.mockRejectedValueOnce( | ||
| new Error('Unable to create accounts'), | ||
| ); | ||
|
|
||
| await expect( | ||
| wallet.createMultichainAccountGroup(groupIndex, { | ||
| waitForAllProvidersToFinishCreatingAccounts: true, | ||
| }), | ||
| ).rejects.toThrow( | ||
| 'Unable to create multichain account group for index: 1', | ||
| ); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Multichain Account Creation Tests FailThe |
||
| }); | ||
|
|
||
| describe('createNextMultichainAccountGroup', () => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -296,11 +296,22 @@ export class MultichainAccountWallet< | |
| * NOTE: This operation WILL lock the wallet's mutex. | ||
| * | ||
| * @param groupIndex - The group index to use. | ||
| * @throws If any of the account providers fails to create their accounts. | ||
| * @param options - Options to configure the account creation. | ||
| * @param options.waitForAllProvidersToFinishCreatingAccounts - Whether to wait for all | ||
| * account providers to finish creating their accounts before returning. If `false`, only | ||
| * the EVM provider will be awaited, while all other providers will create their accounts | ||
| * in the background. Defaults to `false`. | ||
| * @throws If any of the account providers fails to create their accounts and | ||
| * the `waitForAllProvidersToFinishCreatingAccounts` option is set to `true`. If `false`, | ||
| * errors from non-EVM providers will be logged but ignored, and only errors from the | ||
| * EVM provider will be thrown. | ||
| * @returns The multichain account group for this group index. | ||
| */ | ||
| async createMultichainAccountGroup( | ||
| groupIndex: number, | ||
| options: { | ||
| waitForAllProvidersToFinishCreatingAccounts?: boolean; | ||
| } = { waitForAllProvidersToFinishCreatingAccounts: false }, | ||
| ): Promise<MultichainAccountGroup<Account>> { | ||
| return await this.#withLock('in-progress:create-accounts', async () => { | ||
| const nextGroupIndex = this.getNextGroupIndex(); | ||
|
|
@@ -324,41 +335,72 @@ export class MultichainAccountWallet< | |
|
|
||
| this.#log(`Creating new group for index ${groupIndex}...`); | ||
|
|
||
| // Extract the EVM provider from the list of providers. | ||
| // We will only await the EVM provider to create its accounts, while | ||
| // all other providers will be started in the background. | ||
| const [evmProvider, ...otherProviders] = this.#providers; | ||
| assert( | ||
| evmProvider instanceof EvmAccountProvider, | ||
| 'EVM account provider must be first', | ||
| ); | ||
| if (options?.waitForAllProvidersToFinishCreatingAccounts) { | ||
| // Create account with all providers and await them. | ||
| const results = await Promise.allSettled( | ||
| this.#providers.map((provider) => | ||
| provider.createAccounts({ | ||
| entropySource: this.#entropySource, | ||
| groupIndex, | ||
| }), | ||
| ), | ||
| ); | ||
|
|
||
| // Create account with the EVM provider first and await it. | ||
| // If it fails, we don't start creating accounts with other providers. | ||
| try { | ||
| await evmProvider.createAccounts({ | ||
| entropySource: this.#entropySource, | ||
| groupIndex, | ||
| }); | ||
| } catch (error) { | ||
| const errorMessage = `Unable to create multichain account group for index: ${groupIndex} with provider "${evmProvider.getName()}". Error: ${(error as Error).message}`; | ||
| this.#log(`${ERROR_PREFIX} ${errorMessage}:`, error); | ||
| throw new Error(errorMessage); | ||
| } | ||
| // If any of the provider failed to create their accounts, then we consider the | ||
| // multichain account group to have failed too. | ||
| if (results.some((result) => result.status === 'rejected')) { | ||
| // NOTE: Some accounts might still have been created on other account providers. We | ||
| // don't rollback them. | ||
| const error = `Unable to create multichain account group for index: ${groupIndex}`; | ||
|
|
||
| let message = `${error}:`; | ||
| for (const result of results) { | ||
| if (result.status === 'rejected') { | ||
| message += `\n- ${result.reason}`; | ||
| } | ||
| } | ||
| this.#log(`${WARNING_PREFIX} ${message}`); | ||
| console.warn(message); | ||
|
|
||
| throw new Error(error); | ||
| } | ||
| } else { | ||
| // Extract the EVM provider from the list of providers. | ||
| // We will only await the EVM provider to create its accounts, while | ||
| // all other providers will be started in the background. | ||
| const [evmProvider, ...otherProviders] = this.#providers; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that assume EVM provider is always first? Can't the provider order change in theory?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe overkill 😋
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not needed IMO. We could have an extra |
||
| assert( | ||
| evmProvider instanceof EvmAccountProvider, | ||
| 'EVM account provider must be first', | ||
| ); | ||
|
|
||
| // Create account with other providers in the background | ||
| otherProviders.forEach((provider) => { | ||
| provider | ||
| .createAccounts({ | ||
| // Create account with the EVM provider first and await it. | ||
| // If it fails, we don't start creating accounts with other providers. | ||
| try { | ||
| await evmProvider.createAccounts({ | ||
| entropySource: this.#entropySource, | ||
| groupIndex, | ||
| }) | ||
| .catch((error) => { | ||
| // Log errors from background providers but don't fail the operation | ||
| const errorMessage = `Could not to create account with provider "${provider.getName()}" for multichain account group index: ${groupIndex}`; | ||
| this.#log(`${WARNING_PREFIX} ${errorMessage}:`, error); | ||
| }); | ||
| }); | ||
| } catch (error) { | ||
| const errorMessage = `Unable to create multichain account group for index: ${groupIndex} with provider "${evmProvider.getName()}". Error: ${(error as Error).message}`; | ||
| this.#log(`${ERROR_PREFIX} ${errorMessage}:`, error); | ||
| throw new Error(errorMessage); | ||
| } | ||
|
|
||
| // Create account with other providers in the background | ||
| otherProviders.forEach((provider) => { | ||
| provider | ||
| .createAccounts({ | ||
|
Comment on lines
+391
to
+393
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we track background promises too? Or is it the meaning to ignore failures here and future alignments will fix it? or do I miss something? 🤔
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes exactly, we willingly ignore failures for providers creating accounts in the background
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that's expected to not throw in that case, mostly because would not be any way to catch them truly Though, we make sure the If any of the other providers fail, then yes we would need an alignment for this (unfortunately, we don't have any state to tell us a group is misaligned for now, and we might need that in the near future IMO) |
||
| entropySource: this.#entropySource, | ||
| groupIndex, | ||
| }) | ||
| .catch((error) => { | ||
| // Log errors from background providers but don't fail the operation | ||
| const errorMessage = `Could not to create account with provider "${provider.getName()}" for multichain account group index: ${groupIndex}`; | ||
| this.#log(`${WARNING_PREFIX} ${errorMessage}:`, error); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| // -------------------------------------------------------------------------------- | ||
| // READ THIS CAREFULLY: | ||
|
|
@@ -419,7 +461,9 @@ export class MultichainAccountWallet< | |
| async createNextMultichainAccountGroup(): Promise< | ||
| MultichainAccountGroup<Account> | ||
| > { | ||
| return this.createMultichainAccountGroup(this.getNextGroupIndex()); | ||
| return this.createMultichainAccountGroup(this.getNextGroupIndex(), { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a breaking change isn't it? Do we have to point that out in changelog? 👀
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Last change we made around this method was also altering the behavior a bit, but we did not treat it as a breaking change either. I think, it could be seen as such, but for now, the only guarantee that we have for the multichain account group is that we ALWAYS HAVE at least an EVM account, and that still holds true with the previous change we made AND this one IMO. Lastly, for simplicity, I would like to avoid breaking change in those packages to avoid bubbling up to other dependents controllers (we need this fix to be merged ASAP). If we change our mind, we'll make a new 2.0.0 and amend the changelog if really needed later! |
||
| waitForAllProvidersToFinishCreatingAccounts: true, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
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 case only uses 1 provider but claims to test "all providers" behavior, do I read that right? Should it use multiple providers to properly test instead?
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.
It does claim that "if any of the provider fails", so the test is ok.
Though, I think we could have used the scenario EVM + Solana provider where Solana fails, that could have been clearer indeed 👍