Skip to content

Commit 01312e6

Browse files
committed
fix(agent): sync time if ingress expiry is invalid in read_state
1 parent 06c274f commit 01312e6

File tree

4 files changed

+47
-1
lines changed

4 files changed

+47
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ See the [v5 upgrading guide](https://js.icp.build/core/latest/upgrading/v5/) for
2424
- fix(agent): check if canister is in ranges for certificates without delegation
2525
- fix(agent): verify all query signatures instead of only the first one
2626
- fix(agent): sync time if ingress expiry is invalid in queries
27+
- fix(agent): sync time if ingress expiry is invalid in read_state
2728
- fix(agent): sync time before retrying if query signature is outdated
2829
- fix(agent,identity/secp256k1): remove `console.*` statements
2930
- refactor(agent): only declare IC URLs once in the `HttpAgent` class

docs/src/content/docs/upgrading/v5.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ The `@dfinity/assets` package has been deprecated. Its functionality is now part
217217

218218
## Fixes
219219

220-
This release includes minor security improvements to how query responses are validated, strengthening certificate verification and query signature checks.
220+
This release includes minor security improvements to how `query` and `read_state` responses are validated, strengthening certificate verification and query/read_state signature checks.
221221

222222
---
223223

e2e/node/basic/syncTime.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,45 @@ describe('syncTime', () => {
279279
expect(mockReplica.getV3ReadStateSpy(canisterId.toString())).toHaveBeenCalledTimes(4);
280280
expect(agent.hasSyncedTime()).toBe(true);
281281
});
282+
283+
it('should sync time when the local time does not match the subnet time (read_state)', async () => {
284+
const agent = await HttpAgent.create({
285+
host: mockReplica.address,
286+
rootKey: rootSubnetKeyPair.publicKeyDer,
287+
identity,
288+
});
289+
290+
mockReplica.setV3ReadStateSpyImplOnce(canisterId.toString(), (_req, res) => {
291+
res.status(400).send(new TextEncoder().encode(INVALID_EXPIRY_ERROR));
292+
});
293+
294+
await mockSyncTimeResponse({
295+
mockReplica,
296+
rootSubnetKeyPair,
297+
keyPair,
298+
canisterId: ICP_LEDGER,
299+
});
300+
301+
const { responseBody: readStateResponse } = await prepareV3ReadStateResponse({
302+
nodeIdentity,
303+
canisterRanges: [[canisterId.toUint8Array(), canisterId.toUint8Array()]],
304+
rootSubnetKeyPair,
305+
keyPair,
306+
date,
307+
});
308+
mockReplica.setV3ReadStateSpyImplOnce(canisterId.toString(), (_req, res) => {
309+
res.status(200).send(readStateResponse);
310+
});
311+
312+
const response = await agent.readState(canisterId, {
313+
paths: [[new TextEncoder().encode('time')]],
314+
});
315+
316+
expect(response.certificate).toBeDefined();
317+
expect(mockReplica.getV3ReadStateSpy(canisterId.toString())).toHaveBeenCalledTimes(2);
318+
expect(mockReplica.getV3ReadStateSpy(ICP_LEDGER)).toHaveBeenCalledTimes(3);
319+
expect(agent.hasSyncedTime()).toBe(true);
320+
});
282321
});
283322

284323
describe('on async creation', () => {

packages/core/src/agent/agent/http/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,12 @@ export class HttpAgent implements Agent {
11901190
} catch (error) {
11911191
let readStateError: AgentError;
11921192
if (error instanceof AgentError) {
1193+
if (error.hasCode(IngressExpiryInvalidErrorCode) && !this.#hasSyncedTime) {
1194+
// if there is an ingress expiry error and the time has not been synced yet,
1195+
// sync time with the network and try again
1196+
await this.syncTime();
1197+
return await this.#readStateInner(url, transformedRequest, requestId);
1198+
}
11931199
// override the error code to include the request details
11941200
error.code.requestContext = {
11951201
requestId: requestId ?? requestIdOf(transformedRequest),

0 commit comments

Comments
 (0)