Skip to content

Commit 2e1bf7e

Browse files
authored
Refactor remote location hint handling (#712)
This set of changes refactors how remote location hints are handled. Previously, hints were carried by each remote message send. This was concerning because the hint information is (a) potentially quite bulky, (b) highly redundant from one message to another, and (c) changes relatively rarely. The bulk is also a concern once we start persisting the remote message queues, which this is a precursor to doing (plus, once the message queues are persisted, storing hints on a per-message basis risks inconsistencies if the hint information changes while the messages are on disk). This PR adds a new request to the platform services API, `registerLocationHints`, which is used prior to sending the first message to a remote plus can be called any time the hint information is updated, and then removes the hint information from the message transmission flow entirely.
1 parent a5042e7 commit 2e1bf7e

31 files changed

+898
-564
lines changed

packages/kernel-browser-runtime/src/PlatformServicesClient.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -297,16 +297,7 @@ describe('PlatformServicesClient', () => {
297297

298298
describe('sendRemoteMessage', () => {
299299
it('sends message to remote peer via RPC', async () => {
300-
const result = client.sendRemoteMessage('peer-456', 'hello', [
301-
'/dns4/relay.example/tcp/443/wss/p2p/relayPeer',
302-
]);
303-
await delay(10);
304-
await stream.receiveInput(makeNullReply('m1'));
305-
expect(await result).toBeUndefined();
306-
});
307-
308-
it('works with empty hints array', async () => {
309-
const result = client.sendRemoteMessage('peer-789', 'goodbye');
300+
const result = client.sendRemoteMessage('peer-456', 'hello');
310301
await delay(10);
311302
await stream.receiveInput(makeNullReply('m1'));
312303
expect(await result).toBeUndefined();
@@ -331,6 +322,18 @@ describe('PlatformServicesClient', () => {
331322
});
332323
});
333324

325+
describe('registerLocationHints', () => {
326+
it('sends registerLocationHints request and resolves', async () => {
327+
const result = client.registerLocationHints('peer-123', [
328+
'hint1',
329+
'hint2',
330+
]);
331+
await delay(10);
332+
await stream.receiveInput(makeNullReply('m1'));
333+
expect(await result).toBeUndefined();
334+
});
335+
});
336+
334337
describe('reconnectPeer', () => {
335338
it('sends reconnectPeer request with hints and resolves', async () => {
336339
const result = client.reconnectPeer('peer-456', [

packages/kernel-browser-runtime/src/PlatformServicesClient.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,10 @@ export class PlatformServicesClient implements PlatformServices {
216216
*
217217
* @param to - The peer ID to send the message to.
218218
* @param message - The message to send.
219-
* @param hints - Optional hints for the message.
220219
* @returns A promise that resolves when the message has been sent.
221220
*/
222-
async sendRemoteMessage(
223-
to: string,
224-
message: string,
225-
hints: string[] = [],
226-
): Promise<void> {
227-
await this.#rpcClient.call('sendRemoteMessage', { to, message, hints });
221+
async sendRemoteMessage(to: string, message: string): Promise<void> {
222+
await this.#rpcClient.call('sendRemoteMessage', { to, message });
228223
}
229224

230225
/**
@@ -238,6 +233,16 @@ export class PlatformServicesClient implements PlatformServices {
238233
await this.#rpcClient.call('closeConnection', { peerId });
239234
}
240235

236+
/**
237+
* Take note of where a peer might be.
238+
*
239+
* @param peerId - The peer ID to whom this information applies.
240+
* @param hints - An array of location hint strings.
241+
*/
242+
async registerLocationHints(peerId: string, hints: string[]): Promise<void> {
243+
await this.#rpcClient.call('registerLocationHints', { peerId, hints });
244+
}
245+
241246
/**
242247
* Manually reconnect to a peer after intentional close.
243248
* Clears the intentional close flag and initiates reconnection.

packages/kernel-browser-runtime/src/PlatformServicesServer.test.ts

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
const mockSendRemoteMessage = vi.fn(async () => undefined);
2323
const mockStop = vi.fn(async () => undefined);
2424
const mockCloseConnection = vi.fn(async () => undefined);
25+
const mockRegisterLocationHints = vi.fn(async () => undefined);
2526
const mockReconnectPeer = vi.fn(async () => undefined);
2627
let capturedRemoteMessageHandler:
2728
| ((from: string, message: string) => Promise<string>)
@@ -47,6 +48,7 @@ vi.mock('@metamask/ocap-kernel', () => ({
4748
sendRemoteMessage: mockSendRemoteMessage,
4849
stop: mockStop,
4950
closeConnection: mockCloseConnection,
51+
registerLocationHints: mockRegisterLocationHints,
5052
reconnectPeer: mockReconnectPeer,
5153
};
5254
},
@@ -104,11 +106,10 @@ const makeSendRemoteMessageMessageEvent = (
104106
messageId: `m${number}`,
105107
to: string,
106108
message: string,
107-
hints: string[],
108109
): MessageEvent =>
109110
makeMessageEvent(messageId, {
110111
method: 'sendRemoteMessage',
111-
params: { to, message, hints },
112+
params: { to, message },
112113
});
113114

114115
const makeStopRemoteCommsMessageEvent = (
@@ -128,6 +129,16 @@ const makeCloseConnectionMessageEvent = (
128129
params: { peerId },
129130
});
130131

132+
const makeRegisterLocationHintsMessageEvent = (
133+
messageId: `m${number}`,
134+
peerId: string,
135+
hints: string[],
136+
): MessageEvent =>
137+
makeMessageEvent(messageId, {
138+
method: 'registerLocationHints',
139+
params: { peerId, hints },
140+
});
141+
131142
const makeReconnectPeerMessageEvent = (
132143
messageId: `m${number}`,
133144
peerId: string,
@@ -383,6 +394,7 @@ describe('PlatformServicesServer', () => {
383394
mockSendRemoteMessage.mockClear();
384395
mockStop.mockClear();
385396
mockCloseConnection.mockClear();
397+
mockRegisterLocationHints.mockClear();
386398
mockReconnectPeer.mockClear();
387399
capturedRemoteMessageHandler = undefined;
388400
capturedRemoteGiveUpHandler = undefined;
@@ -592,24 +604,21 @@ describe('PlatformServicesServer', () => {
592604

593605
// Now send a message
594606
await stream.receiveInput(
595-
makeSendRemoteMessageMessageEvent('m1', 'peer-123', 'hello', [
596-
'/dns4/relay.example/tcp/443/wss/p2p/relayPeer',
597-
]),
607+
makeSendRemoteMessageMessageEvent('m1', 'peer-123', 'hello'),
598608
);
599609
await delay(10);
600610

601611
expect(mockSendRemoteMessage).toHaveBeenCalledWith(
602612
'peer-123',
603613
'hello',
604-
['/dns4/relay.example/tcp/443/wss/p2p/relayPeer'],
605614
);
606615
});
607616

608617
it('throws error if remote comms not initialized', async () => {
609618
const errorSpy = vi.spyOn(logger, 'error');
610619

611620
await stream.receiveInput(
612-
makeSendRemoteMessageMessageEvent('m0', 'peer-456', 'test', []),
621+
makeSendRemoteMessageMessageEvent('m0', 'peer-456', 'test'),
613622
);
614623
await delay(10);
615624

@@ -711,6 +720,51 @@ describe('PlatformServicesServer', () => {
711720
});
712721
});
713722

723+
describe('registerLocationHints', () => {
724+
it('registers location hints via network layer', async () => {
725+
// First initialize remote comms
726+
await stream.receiveInput(
727+
makeInitializeRemoteCommsMessageEvent('m0', '0xabcd', {
728+
relays: ['/dns4/relay.example/tcp/443/wss/p2p/relayPeer'],
729+
}),
730+
);
731+
await delay(10);
732+
733+
// Now register some hints
734+
await stream.receiveInput(
735+
makeRegisterLocationHintsMessageEvent('m1', 'peer-123', [
736+
'hint1',
737+
'hint2',
738+
]),
739+
);
740+
await delay(10);
741+
742+
expect(mockRegisterLocationHints).toHaveBeenCalledWith('peer-123', [
743+
'hint1',
744+
'hint2',
745+
]);
746+
});
747+
748+
it('throws error if remote comms not initialized', async () => {
749+
const errorSpy = vi.spyOn(logger, 'error');
750+
751+
await stream.receiveInput(
752+
makeRegisterLocationHintsMessageEvent('m0', 'peer-456', [
753+
'hint1',
754+
'hint2',
755+
]),
756+
);
757+
await delay(10);
758+
759+
expect(errorSpy).toHaveBeenCalledWith(
760+
'Error handling "registerLocationHints" request:',
761+
expect.objectContaining({
762+
message: 'remote comms not initialized',
763+
}),
764+
);
765+
});
766+
});
767+
714768
describe('reconnectPeer', () => {
715769
it('reconnects peer via network layer', async () => {
716770
// First initialize remote comms

packages/kernel-browser-runtime/src/PlatformServicesServer.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export class PlatformServicesServer {
6161

6262
#closeConnectionFunc: ((peerId: string) => Promise<void>) | null = null;
6363

64+
#registerLocationHintsFunc:
65+
| ((peerId: string, hints: string[]) => void)
66+
| null = null;
67+
6468
#reconnectPeerFunc:
6569
| ((peerId: string, hints?: string[]) => Promise<void>)
6670
| null = null;
@@ -105,10 +109,11 @@ export class PlatformServicesServer {
105109
launch: this.#launch.bind(this),
106110
terminate: this.#terminate.bind(this),
107111
terminateAll: this.#terminateAll.bind(this),
108-
initializeRemoteComms: this.#initializeRemoteComms.bind(this),
109112
sendRemoteMessage: this.#sendRemoteMessage.bind(this),
113+
initializeRemoteComms: this.#initializeRemoteComms.bind(this),
110114
stopRemoteComms: this.#stopRemoteComms.bind(this),
111115
closeConnection: this.#closeConnection.bind(this),
116+
registerLocationHints: this.#registerLocationHints.bind(this),
112117
reconnectPeer: this.#reconnectPeer.bind(this),
113118
});
114119

@@ -256,16 +261,22 @@ export class PlatformServicesServer {
256261
if (this.#sendRemoteMessageFunc) {
257262
throw Error('remote comms already initialized');
258263
}
259-
const { sendRemoteMessage, stop, closeConnection, reconnectPeer } =
260-
await initNetwork(
261-
keySeed,
262-
options,
263-
this.#handleRemoteMessage.bind(this),
264-
this.#handleRemoteGiveUp.bind(this),
265-
);
264+
const {
265+
sendRemoteMessage,
266+
stop,
267+
closeConnection,
268+
registerLocationHints,
269+
reconnectPeer,
270+
} = await initNetwork(
271+
keySeed,
272+
options,
273+
this.#handleRemoteMessage.bind(this),
274+
this.#handleRemoteGiveUp.bind(this),
275+
);
266276
this.#sendRemoteMessageFunc = sendRemoteMessage;
267277
this.#stopRemoteCommsFunc = stop;
268278
this.#closeConnectionFunc = closeConnection;
279+
this.#registerLocationHintsFunc = registerLocationHints;
269280
this.#reconnectPeerFunc = reconnectPeer;
270281
return null;
271282
}
@@ -283,6 +294,7 @@ export class PlatformServicesServer {
283294
this.#sendRemoteMessageFunc = null;
284295
this.#stopRemoteCommsFunc = null;
285296
this.#closeConnectionFunc = null;
297+
this.#registerLocationHintsFunc = null;
286298
this.#reconnectPeerFunc = null;
287299
return null;
288300
}
@@ -301,6 +313,21 @@ export class PlatformServicesServer {
301313
return null;
302314
}
303315

316+
/**
317+
* Take note of where a peer might be.
318+
*
319+
* @param peerId - The peer ID to whom this information applies.
320+
* @param hints - An array of location hints
321+
* @returns A promise that resolves when the connection has been closed.
322+
*/
323+
async #registerLocationHints(peerId: string, hints: string[]): Promise<null> {
324+
if (!this.#registerLocationHintsFunc) {
325+
throw Error('remote comms not initialized');
326+
}
327+
this.#registerLocationHintsFunc(peerId, hints);
328+
return null;
329+
}
330+
304331
/**
305332
* Manually reconnect to a peer after intentional close.
306333
*
@@ -321,18 +348,13 @@ export class PlatformServicesServer {
321348
*
322349
* @param to - The peer ID to send the message to.
323350
* @param message - The message to send.
324-
* @param hints - Optional hints for the message.
325351
* @returns A promise that resolves when the message has been sent.
326352
*/
327-
async #sendRemoteMessage(
328-
to: string,
329-
message: string,
330-
hints: string[] = [],
331-
): Promise<null> {
353+
async #sendRemoteMessage(to: string, message: string): Promise<null> {
332354
if (!this.#sendRemoteMessageFunc) {
333355
throw Error('remote comms not initialized');
334356
}
335-
await this.#sendRemoteMessageFunc(to, message, hints);
357+
await this.#sendRemoteMessageFunc(to, message);
336358
return null;
337359
}
338360

@@ -349,7 +371,7 @@ export class PlatformServicesServer {
349371
message,
350372
});
351373
if (possibleReply !== '') {
352-
await this.#sendRemoteMessage(from, possibleReply, []);
374+
await this.#sendRemoteMessage(from, possibleReply);
353375
}
354376
return '';
355377
}

packages/kernel-test/src/remote-comms.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ class DirectNetworkService {
130130
return Promise.resolve();
131131
},
132132

133+
async registerLocationHints(_peerId: string, _hints: string[]) {
134+
return Promise.resolve();
135+
},
136+
133137
async reconnectPeer(_peerId: string, _hints: string[] = []) {
134138
// Mock implementation - in direct network, connections are always available
135139
return Promise.resolve();

0 commit comments

Comments
 (0)