Skip to content

Commit

Permalink
all certificates now read from agent.replicaTime
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock committed Sep 10, 2024
1 parent eec61ee commit 113f352
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 77 deletions.
80 changes: 78 additions & 2 deletions e2e/node/basic/mainnet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
fromHex,
polling,
requestIdOf,
ReplicaTimeError,
} from '@dfinity/agent';
import { IDL } from '@dfinity/candid';
import { Ed25519KeyIdentity } from '@dfinity/identity';
Expand All @@ -21,7 +22,7 @@ const createWhoamiActor = async (identity: Identity) => {
const idlFactory = () => {
return IDL.Service({
whoami: IDL.Func([], [IDL.Principal], ['query']),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as unknown as any;
};
vi.useFakeTimers();
Expand Down Expand Up @@ -142,7 +143,6 @@ describe('call forwarding', () => {
}, 15_000);
});


test('it should allow you to set an incorrect root key', async () => {
const agent = HttpAgent.createSync({
rootKey: new Uint8Array(31),
Expand All @@ -159,3 +159,79 @@ test('it should allow you to set an incorrect root key', async () => {

expect(actor.whoami).rejects.toThrowError(`Invalid certificate:`);
});

test('it should throw an error when the clock is out of sync during a query', async () => {
const canisterId = 'ivcos-eqaaa-aaaab-qablq-cai';
const idlFactory = () => {
return IDL.Service({
whoami: IDL.Func([], [IDL.Principal], ['query']),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as unknown as any;
};
vi.useRealTimers();

// set date to long ago
vi.spyOn(Date, 'now').mockImplementation(() => {
return new Date('2021-01-01T00:00:00Z').getTime();
});
// vi.setSystemTime(new Date('2021-01-01T00:00:00Z'));

const agent = await HttpAgent.create({ host: 'https://icp-api.io', fetch: globalThis.fetch });

const actor = Actor.createActor(idlFactory, {
agent,
canisterId,
});
try {
// should throw an error
await actor.whoami();
} catch (err) {
// handle the replica time error
if (err.name === 'ReplicaTimeError') {
const error = err as ReplicaTimeError;
// use the replica time to sync the agent
error.agent.replicaTime = error.replicaTime;
}
}
// retry the call
const result = await actor.whoami();
expect(Principal.from(result)).toBeInstanceOf(Principal);
});

test('it should throw an error when the clock is out of sync during an update', async () => {
const canisterId = 'ivcos-eqaaa-aaaab-qablq-cai';
const idlFactory = () => {
return IDL.Service({
whoami: IDL.Func([], [IDL.Principal], []),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as unknown as any;
};
vi.useRealTimers();

// set date to long ago
vi.spyOn(Date, 'now').mockImplementation(() => {
return new Date('2021-01-01T00:00:00Z').getTime();
});
// vi.setSystemTime(new Date('2021-01-01T00:00:00Z'));

const agent = await HttpAgent.create({ host: 'https://icp-api.io', fetch: globalThis.fetch });

const actor = Actor.createActor(idlFactory, {
agent,
canisterId,
});
try {
// should throw an error
await actor.whoami();
} catch (err) {
// handle the replica time error
if (err.name === 'ReplicaTimeError') {
const error = err as ReplicaTimeError;
// use the replica time to sync the agent
error.agent.replicaTime = error.replicaTime;
// retry the call
const result = await actor.whoami();
expect(Principal.from(result)).toBeInstanceOf(Principal);
}
}
});
9 changes: 8 additions & 1 deletion packages/agent/src/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Buffer } from 'buffer/';
import {
Agent,
getDefaultAgent,
HttpAgent,
HttpDetailsResponse,
QueryResponseRejected,
QueryResponseStatus,
Expand Down Expand Up @@ -535,13 +536,19 @@ function _createActorMethod(
});
let reply: ArrayBuffer | undefined;
let certificate: Certificate | undefined;
const certTime = (agent as HttpAgent).replicaTime
? (agent as HttpAgent).replicaTime
: undefined;

certTime;

if (response.body && response.body.certificate) {
const cert = response.body.certificate;
certificate = await Certificate.create({
certificate: bufFromBufLike(cert),
rootKey: agent.rootKey,
canisterId: Principal.from(canisterId),
blsVerify,
certTime,
});
const path = [new TextEncoder().encode('request_status'), requestId];
const status = new TextDecoder().decode(
Expand Down
74 changes: 0 additions & 74 deletions packages/agent/src/agent/http/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,77 +815,3 @@ test('it should log errors to console if the option is set', async () => {
});

jest.setTimeout(5000);
test('it should sync time with the replica for a query', async () => {
const canisterId = 'ivcos-eqaaa-aaaab-qablq-cai';
const idlFactory = () => {
return IDL.Service({
whoami: IDL.Func([], [IDL.Principal], ['query']),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as unknown as any;
};
jest.useRealTimers();

// set date to long ago
jest.spyOn(Date, 'now').mockImplementation(() => {
return new Date('2021-01-01T00:00:00Z').getTime();
});
// jest.setSystemTime(new Date('2021-01-01T00:00:00Z'));

const agent = await HttpAgent.create({ host: 'https://icp-api.io', fetch: globalThis.fetch });

const actor = Actor.createActor(idlFactory, {
agent,
canisterId,
});
try {
// should throw an error
await actor.whoami();
} catch (err) {
// handle the replica time error
if (err.name === 'ReplicaTimeError') {
const error = err as ReplicaTimeError;
// use the replica time to sync the agent
error.agent.replicaTime = error.replicaTime;
}
}
// retry the call
const result = await actor.whoami();
expect(Principal.from(result)).toBeInstanceOf(Principal);
});
test('it should sync time with the replica for an update', async () => {
const canisterId = 'ivcos-eqaaa-aaaab-qablq-cai';
const idlFactory = () => {
return IDL.Service({
whoami: IDL.Func([], [IDL.Principal], []),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as unknown as any;
};
jest.useRealTimers();

// set date to long ago
jest.spyOn(Date, 'now').mockImplementation(() => {
return new Date('2021-01-01T00:00:00Z').getTime();
});
// jest.setSystemTime(new Date('2021-01-01T00:00:00Z'));

const agent = await HttpAgent.create({ host: 'https://icp-api.io', fetch: globalThis.fetch });

const actor = Actor.createActor(idlFactory, {
agent,
canisterId,
});
try {
// should throw an error
await actor.whoami();
} catch (err) {
// handle the replica time error
if (err.name === 'ReplicaTimeError') {
const error = err as ReplicaTimeError;
// use the replica time to sync the agent
error.agent.replicaTime = error.replicaTime;
// retry the call
const result = await actor.whoami();
expect(Principal.from(result)).toBeInstanceOf(Principal);
}
}
});
1 change: 1 addition & 0 deletions packages/agent/src/agent/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,7 @@ export class HttpAgent implements Agent {
/**
* Allows agent to sync its time with the network. Can be called during intialization or mid-lifecycle if the device's clock has drifted away from the network time. This is necessary to set the Expiry for a request
* @param {Principal} canisterId - Pass a canister ID if you need to sync the time with a particular replica. Uses the management canister by default
* @throws {ReplicaTimeError} - this method is not guaranteed to work if the device's clock is off by more than 30 seconds. In such cases, the agent will throw an error.
*/
public async syncTime(canisterId?: Principal): Promise<void> {
const CanisterStatus = await import('../../canisterStatus');
Expand Down
2 changes: 2 additions & 0 deletions packages/agent/src/canisterStatus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,12 @@ export const request = async (options: {
const response = await agent.readState(canisterId, {
paths: [encodedPaths[index]],
});
const certTime = agent.replicaTime ? agent.replicaTime : undefined;
const cert = await Certificate.create({
certificate: response.certificate,
rootKey: agent.rootKey,
canisterId: canisterId,
certTime,
});

const lookup = (cert: Certificate, path: Path) => {
Expand Down
6 changes: 6 additions & 0 deletions packages/assets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
compare,
getDefaultAgent,
HashTree,
HttpAgent,
lookup_path,
lookupResultToBuffer,
LookupStatus,
Expand Down Expand Up @@ -530,10 +531,15 @@ class Asset {
return false;
}

const replicaTime = (agent as HttpAgent).replicaTime
? (agent as HttpAgent).replicaTime
: undefined;

const cert = await Certificate.create({
certificate: new Uint8Array(certificate),
rootKey: agent.rootKey,
canisterId,
certTime: replicaTime,
}).catch(() => Promise.resolve());

if (!cert) {
Expand Down

0 comments on commit 113f352

Please sign in to comment.