Skip to content

Commit ba27fd6

Browse files
committed
perf(keyring-controller): do not fire unecessary :stageChange with withKeyring
1 parent 6e8000b commit ba27fd6

File tree

5 files changed

+100
-1
lines changed

5 files changed

+100
-1
lines changed

packages/keyring-controller/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"async-mutex": "^0.5.0",
6161
"ethereumjs-wallet": "^1.0.1",
6262
"immer": "^9.0.6",
63+
"lodash": "^4.17.21",
6364
"ulid": "^2.3.0"
6465
},
6566
"devDependencies": {

packages/keyring-controller/src/KeyringController.test.ts

+66
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import MockEncryptor, {
4141
} from '../tests/mocks/mockEncryptor';
4242
import { MockErc4337Keyring } from '../tests/mocks/mockErc4337Keyring';
4343
import { MockKeyring } from '../tests/mocks/mockKeyring';
44+
import { MockMutableKeyring } from '../tests/mocks/mockMutableKeyring';
4445
import MockShallowGetAccountsKeyring from '../tests/mocks/mockShallowGetAccountsKeyring';
4546
import { buildMockTransaction } from '../tests/mocks/mockTransaction';
4647

@@ -3080,6 +3081,71 @@ describe('KeyringController', () => {
30803081
},
30813082
);
30823083
});
3084+
3085+
it('should update the vault if the keyring is being updated', async () => {
3086+
await withController(
3087+
{ keyringBuilders: [keyringBuilderFactory(MockMutableKeyring)] },
3088+
async ({ controller, messenger }) => {
3089+
const selector = { type: MockMutableKeyring.type };
3090+
3091+
const mockStateChange = jest.fn();
3092+
messenger.subscribe(
3093+
'KeyringController:stateChange',
3094+
mockStateChange,
3095+
);
3096+
3097+
await controller.withKeyring(
3098+
selector,
3099+
async ({ keyring }) => {
3100+
(keyring as MockMutableKeyring).update(); // Update the keyring state.
3101+
},
3102+
{
3103+
createIfMissing: true,
3104+
},
3105+
);
3106+
3107+
expect(mockStateChange).toHaveBeenCalled();
3108+
expect(controller.state.keyrings).toHaveLength(2);
3109+
},
3110+
);
3111+
});
3112+
3113+
it('should not update the vault if the keyring has not been updated', async () => {
3114+
await withController(
3115+
{ keyringBuilders: [keyringBuilderFactory(MockMutableKeyring)] },
3116+
async ({ controller, messenger }) => {
3117+
const selector = { type: MockMutableKeyring.type };
3118+
3119+
const mockStateChange = jest.fn();
3120+
messenger.subscribe(
3121+
'KeyringController:stateChange',
3122+
mockStateChange,
3123+
);
3124+
3125+
// First create the reading before reading from it.
3126+
await controller.withKeyring(
3127+
selector,
3128+
async () => {
3129+
// No-op, but the keyring will still be persisted since we
3130+
// used `createIfMissing`.
3131+
},
3132+
{
3133+
createIfMissing: true,
3134+
},
3135+
);
3136+
3137+
expect(mockStateChange).toHaveBeenCalled();
3138+
expect(controller.state.keyrings).toHaveLength(2);
3139+
3140+
mockStateChange.mockReset();
3141+
await controller.withKeyring(selector, async () => {
3142+
// No-op, keyring state won't be updated.
3143+
});
3144+
3145+
expect(mockStateChange).not.toHaveBeenCalled();
3146+
},
3147+
);
3148+
});
30833149
});
30843150
});
30853151

packages/keyring-controller/src/KeyringController.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type { MutexInterface } from 'async-mutex';
3838
import Wallet, { thirdparty as importers } from 'ethereumjs-wallet';
3939
import type { Patch } from 'immer';
4040
// When generating a ULID within the same millisecond, monotonicFactory provides some guarantees regarding sort order.
41+
import { isEqual } from 'lodash';
4142
import { ulid } from 'ulid';
4243

4344
import { KeyringControllerError } from './constants';
@@ -1551,8 +1552,9 @@ export class KeyringController extends BaseController<
15511552
): Promise<CallbackResult> {
15521553
this.#assertIsUnlocked();
15531554

1554-
return this.#persistOrRollback(async () => {
1555+
return this.#withRollback(async () => {
15551556
let keyring: SelectedKeyring | undefined;
1557+
let forceUpdate = false;
15561558

15571559
if ('address' in selector) {
15581560
keyring = (await this.getKeyringForAccount(selector.address)) as
@@ -1568,6 +1570,9 @@ export class KeyringController extends BaseController<
15681570
selector.type,
15691571
options.createWithData,
15701572
)) as SelectedKeyring;
1573+
1574+
// This is a new keyring, so we force the vault update in that case.
1575+
forceUpdate = true;
15711576
}
15721577
} else if ('id' in selector) {
15731578
keyring = this.#getKeyringById(selector.id) as SelectedKeyring;
@@ -1577,6 +1582,8 @@ export class KeyringController extends BaseController<
15771582
throw new Error(KeyringControllerError.KeyringNotFound);
15781583
}
15791584

1585+
const oldKeyringState = await keyring.serialize();
1586+
15801587
const result = await operation({
15811588
keyring,
15821589
metadata: this.#getKeyringMetadata(keyring),
@@ -1590,6 +1597,11 @@ export class KeyringController extends BaseController<
15901597
throw new Error(KeyringControllerError.UnsafeDirectKeyringAccess);
15911598
}
15921599

1600+
if (forceUpdate || !isEqual(oldKeyringState, await keyring.serialize())) {
1601+
// Keyring has been updated, we need to update the vault.
1602+
await this.#updateVault();
1603+
}
1604+
15931605
return result;
15941606
});
15951607
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { MockKeyring } from './mockKeyring';
2+
3+
export class MockMutableKeyring extends MockKeyring {
4+
static type = 'Mock Mutable Keyring';
5+
6+
public type = 'Mock Mutable Keyring';
7+
8+
#updated: boolean = false;
9+
10+
update() {
11+
this.#updated = true;
12+
}
13+
14+
async serialize() {
15+
return Promise.resolve({
16+
updated: this.#updated,
17+
});
18+
}
19+
}

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3553,6 +3553,7 @@ __metadata:
35533553
immer: "npm:^9.0.6"
35543554
jest: "npm:^27.5.1"
35553555
jest-environment-node: "npm:^27.5.1"
3556+
lodash: "npm:^4.17.21"
35563557
sinon: "npm:^9.2.4"
35573558
ts-jest: "npm:^27.1.4"
35583559
typedoc: "npm:^0.24.8"

0 commit comments

Comments
 (0)