diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index bc1ee723..7245f73f 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -191,6 +191,7 @@ test('it should throw an error when the clock is out of sync during a query', as const error = err as ReplicaTimeError; // use the replica time to sync the agent error.agent.replicaTime = error.replicaTime; + error.agent.overrideSystemTime = true; } } // retry the call @@ -229,6 +230,7 @@ test('it should throw an error when the clock is out of sync during an update', const error = err as ReplicaTimeError; // use the replica time to sync the agent error.agent.replicaTime = error.replicaTime; + error.agent.overrideSystemTime = true; // retry the call const result = await actor.whoami(); expect(Principal.from(result)).toBeInstanceOf(Principal); diff --git a/package-lock.json b/package-lock.json index 1a4cb8ac..07ba26ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13154,7 +13154,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true, "license": "ISC" }, @@ -13268,7 +13270,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -13287,8 +13291,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -13296,6 +13300,8 @@ }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -15431,7 +15437,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -16697,13 +16705,15 @@ } }, "node_modules/vite": { - "version": "5.3.5", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -16722,6 +16732,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -16739,6 +16750,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/packages/agent/src/__snapshots__/actor.test.ts.snap b/packages/agent/src/__snapshots__/actor.test.ts.snap index 81dccdbd..b0ca2e6f 100644 --- a/packages/agent/src/__snapshots__/actor.test.ts.snap +++ b/packages/agent/src/__snapshots__/actor.test.ts.snap @@ -24,7 +24,7 @@ exports[`makeActor should enrich actor interface with httpDetails 2`] = ` "__principal__": "2chl6-4hpzw-vqaaa-aaaaa-c", }, "ingress_expiry": Expiry { - "_value": 1200000000000n, + "_value": 1260000000000n, }, "method_name": "greet_update", "nonce": Uint8Array [], diff --git a/packages/agent/src/actor.test.ts b/packages/agent/src/actor.test.ts index d15483d6..e5575071 100644 --- a/packages/agent/src/actor.test.ts +++ b/packages/agent/src/actor.test.ts @@ -315,7 +315,7 @@ describe('makeActor', () => { "__principal__": "2chl6-4hpzw-vqaaa-aaaaa-c", }, "ingress_expiry": Expiry { - "_value": 1200000000000n, + "_value": 1260000000000n, }, "method_name": "greet", "request_type": "query", diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index f447da3e..25177b02 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -540,8 +540,6 @@ function _createActorMethod( ? (agent as HttpAgent).replicaTime : undefined; - certTime; - if (response.body && response.body.certificate) { const cert = response.body.certificate; certificate = await Certificate.create({ diff --git a/packages/agent/src/agent/http/http.test.ts b/packages/agent/src/agent/http/http.test.ts index 0cf25bc9..54bb098b 100644 --- a/packages/agent/src/agent/http/http.test.ts +++ b/packages/agent/src/agent/http/http.test.ts @@ -618,6 +618,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds behind', asy const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); await agent.syncTime(); + agent.overrideSystemTime = true; await agent .call(Principal.managementCanister(), { @@ -629,7 +630,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds behind', asy const requestBody: any = cbor.decode(mockFetch.mock.calls[0][1].body); - expect(requestBody.content.ingress_expiry).toMatchInlineSnapshot(`1260000000000`); + expect(requestBody.content.ingress_expiry).toMatchInlineSnapshot(`1320000000000`); jest.resetModules(); }); @@ -655,6 +656,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds ahead', asyn const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); await agent.syncTime(); + agent.overrideSystemTime = true; await agent .call(Principal.managementCanister(), { @@ -666,7 +668,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds ahead', asyn const requestBody: any = cbor.decode(mockFetch.mock.calls[0][1].body); - expect(requestBody.content.ingress_expiry).toMatchInlineSnapshot(`1200000000000`); + expect(requestBody.content.ingress_expiry).toMatchInlineSnapshot(`1260000000000`); jest.resetModules(); }); diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index 07c0b956..b3d6b4ea 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -232,7 +232,7 @@ interface V1HttpAgentInterface { _isAgent: true; } -/** +/** * A HTTP agent allows users to interact with a client of the internet computer using the available methods. It exposes an API that closely follows the public view of the internet computer, and is not intended to be exposed @@ -264,6 +264,7 @@ export class HttpAgent implements Agent { // Manage the time offset between the client and the replica #initialClientTime: Date = new Date(Date.now()); #initialReplicaTime: Date = new Date(Date.now()); + public overrideSystemTime: boolean = false; get replicaTime(): Date { const offset = Date.now() - this.#initialClientTime.getTime(); return new Date(this.#initialReplicaTime.getTime() + offset); @@ -432,7 +433,7 @@ export class HttpAgent implements Agent { identity?: Identity | Promise, ): Promise { const callSync = options.callSync ?? true; - const id = await (identity !== undefined ? await identity : await this.#identity); + const id = await (identity !== undefined ? identity : this.#identity); if (!id) { throw new IdentityInvalidError( "This identity has expired due this application's security policy. Please refresh your authentication.", @@ -445,13 +446,10 @@ export class HttpAgent implements Agent { const sender: Principal = id.getPrincipal() || Principal.anonymous(); - let ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS); - - // If the value is off by more than 30 seconds, reconcile system time with the network - const timeDiffMsecs = this.replicaTime && this.replicaTime.getTime() - Date.now(); - if (Math.abs(timeDiffMsecs) > 1_000 * 30) { - ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + timeDiffMsecs); - } + const ingress_expiry = new Expiry( + DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS, + this.overrideSystemTime ? this.replicaTime.getTime() : Date.now() + ); const submit: CallRequest = { request_type: SubmitRequestType.Call, @@ -789,13 +787,10 @@ export class HttpAgent implements Agent { const canister = Principal.from(canisterId); const sender = id?.getPrincipal() || Principal.anonymous(); - let ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS); - - // If the value is off by more than 30 seconds, reconcile system time with the network - const timeDiffMsecs = this.replicaTime && this.replicaTime.getTime() - Date.now(); - if (Math.abs(timeDiffMsecs) > 1_000 * 30) { - ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + timeDiffMsecs); - } + const ingress_expiry = new Expiry( + DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS, + this.overrideSystemTime ? this.replicaTime.getTime() : Date.now() + ); const request: QueryRequest = { request_type: ReadRequestType.Query, diff --git a/packages/agent/src/agent/http/transforms.test.ts b/packages/agent/src/agent/http/transforms.test.ts index 7521309c..6323db7f 100644 --- a/packages/agent/src/agent/http/transforms.test.ts +++ b/packages/agent/src/agent/http/transforms.test.ts @@ -6,10 +6,10 @@ test('it should round down to the nearest minute', () => { jest.setSystemTime(new Date(1619459231314)); const expiry = new Expiry(5 * 60 * 1000); - expect(expiry['_value']).toEqual(BigInt(1619459460000000000)); + expect(expiry['_value']).toEqual(BigInt(1619459520000000000)); const expiry_as_date_string = new Date( Number(expiry['_value'] / BigInt(1_000_000)), ).toISOString(); - expect(expiry_as_date_string).toBe('2021-04-26T17:51:00.000Z'); + expect(expiry_as_date_string).toBe('2021-04-26T17:52:00.000Z'); }); diff --git a/packages/agent/src/agent/http/transforms.ts b/packages/agent/src/agent/http/transforms.ts index 934dfbfb..16447964 100644 --- a/packages/agent/src/agent/http/transforms.ts +++ b/packages/agent/src/agent/http/transforms.ts @@ -11,15 +11,13 @@ import { const NANOSECONDS_PER_MILLISECONDS = BigInt(1_000_000); -const REPLICA_PERMITTED_DRIFT_MILLISECONDS = 60 * 1000; - export class Expiry { private readonly _value: bigint; - constructor(deltaInMSec: number) { + constructor(deltaInMSec: number, systemTime: number = Date.now()) { // Use bigint because it can overflow the maximum number allowed in a double float. const raw_value = - BigInt(Math.floor(Date.now() + deltaInMSec - REPLICA_PERMITTED_DRIFT_MILLISECONDS)) * + BigInt(Math.floor(systemTime + deltaInMSec)) * NANOSECONDS_PER_MILLISECONDS; // round down to the nearest second