diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f03b1b9e..93c6d18f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +## Changed + +- fix: recalculates body to use a fresh `Expiry` when polling for `read_state` requests. This prevents the request from exceeding the `maximum_ingress_expiry` when the replica is slow to respond. + ## [2.1.2] - 2024-09-30 - fix: revert https://github.com/dfinity/agent-js/pull/923 allow option to set agent replica time - fix: handle v3 traps correctly, pulling the reject_code and message from the certificate in the error response like v2. diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index b6c47bdf..f3aa840e 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -42,6 +42,7 @@ import { Ed25519PublicKey } from '../../public_key'; import { decodeTime } from '../../utils/leb'; import { ObservableLog } from '../../observable'; import { BackoffStrategy, BackoffStrategyFactory, ExponentialBackoff } from '../../polling/backoff'; +import { DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS } from '../../constants'; export * from './transforms'; export { Nonce, makeNonce } from './types'; @@ -54,9 +55,6 @@ export enum RequestStatusResponseStatus { Done = 'done', } -// Default delta for ingress expiry is 5 minutes. -const DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS = 5 * 60 * 1000; - // Root public key for the IC, encoded as hex export const IC_ROOT_KEY = '308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100814' + @@ -779,7 +777,6 @@ export class HttpAgent implements Agent { const requestId = await requestIdOf(request); - // TODO: remove this any. This can be a Signed or UnSigned request. // eslint-disable-next-line @typescript-eslint/no-explicit-any let transformedRequest: HttpAgentRequest = await this._transform({ request: { @@ -946,9 +943,8 @@ export class HttpAgent implements Agent { } const sender = id?.getPrincipal() || Principal.anonymous(); - // TODO: remove this any. This can be a Signed or UnSigned request. // eslint-disable-next-line @typescript-eslint/no-explicit-any - const transformedRequest: any = await this._transform({ + const transformedRequest = await this._transform({ request: { method: 'POST', headers: { @@ -979,7 +975,14 @@ export class HttpAgent implements Agent { const canister = typeof canisterId === 'string' ? Principal.fromText(canisterId) : canisterId; const transformedRequest = request ?? (await this.createReadStateRequest(fields, identity)); - const body = cbor.encode(transformedRequest.body); + + // With read_state, we should always use a fresh expiry, even beyond the point where the initial request would have expired + const bodyWithAdjustedExpiry = { + ...transformedRequest.body, + ingress_expiry: new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS), + }; + + const body = cbor.encode(bodyWithAdjustedExpiry); this.log.print( `fetching "/api/v2/canister/${canister}/read_state" with request:`, diff --git a/packages/agent/src/constants.ts b/packages/agent/src/constants.ts new file mode 100644 index 00000000..b7182105 --- /dev/null +++ b/packages/agent/src/constants.ts @@ -0,0 +1,2 @@ +// Default delta for ingress expiry is 5 minutes. +export const DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS = 5 * 60 * 1000; diff --git a/packages/agent/src/polling/index.ts b/packages/agent/src/polling/index.ts index ccd399eb..1518992e 100644 --- a/packages/agent/src/polling/index.ts +++ b/packages/agent/src/polling/index.ts @@ -1,11 +1,12 @@ import { Principal } from '@dfinity/principal'; -import { Agent, RequestStatusResponseStatus } from '../agent'; +import { Agent, Expiry, RequestStatusResponseStatus } from '../agent'; import { Certificate, CreateCertificateOptions, lookupResultToBuffer } from '../certificate'; import { RequestId } from '../request_id'; import { toHex } from '../utils/buffer'; export * as strategy from './strategy'; import { defaultStrategy } from './strategy'; +import { DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS } from '../constants'; export { defaultStrategy } from './strategy'; export type PollStrategy = ( canisterId: Principal, @@ -38,6 +39,10 @@ export async function pollForResponse( }> { const path = [new TextEncoder().encode('request_status'), requestId]; const currentRequest = request ?? (await agent.createReadStateRequest?.({ paths: [path] })); + + // Use a fresh expiry for the readState call. + currentRequest.body.content.ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS); + const state = await agent.readState(canisterId, { paths: [path] }, undefined, currentRequest); if (agent.rootKey == null) throw new Error('Agent root key not initialized before polling'); const cert = await Certificate.create({