Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock committed Apr 12, 2024
1 parent 643a650 commit d06103d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 19 deletions.
5 changes: 3 additions & 2 deletions e2e/node/basic/mainnet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ const createWhoamiActor = async (identity: Identity) => {

describe('certified query', () => {
vi.useRealTimers();
it('should verify a query certificate', async () => {
it.only('should verify a query certificate', async () => {
const actor = await createWhoamiActor(new AnonymousIdentity());

let agent = Actor.agentOf(actor) as HttpAgent;

Check failure on line 37 in e2e/node/basic/mainnet.test.ts

View workflow job for this annotation

GitHub Actions / test (8.8.4, release-0.16, 20)

'agent' is never reassigned. Use 'const' instead
agent.log.subscribe(console.log);
const result = await actor.whoami();
expect(Principal.from(result)).toBeInstanceOf(Principal);
}, 10_000);
Expand Down
5 changes: 0 additions & 5 deletions e2e/node/basic/watermark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ test('replay attack', async () => {
verifyQuerySignatures: true,
fetch: fetchProxy.fetch.bind(fetchProxy),
retryTimes: 3,
backoffStrategy: () => {
return {
next: () => 0,
};
},
});

const agent = Actor.agentOf(actor) as HttpAgent;
Expand Down
61 changes: 49 additions & 12 deletions packages/agent/src/agent/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export class HttpAgent implements Agent {
private readonly _host: URL;
private readonly _credentials: string | undefined;
private _rootKeyFetched = false;
private readonly _retryTimes; // Retry requests N times before erroring by default
#retryTimes; // Retry requests N times before erroring by default
#backoffStrategy: BackoffStrategyFactory;
public readonly _isAgent = true;

Expand Down Expand Up @@ -274,11 +274,11 @@ export class HttpAgent implements Agent {
this.#verifyQuerySignatures = options.verifyQuerySignatures;
}
// Default is 3
this._retryTimes = options.retryTimes ?? 3;
this.#retryTimes = options.retryTimes ?? 3;
// Delay strategy for retries. Default is exponential backoff
const defaultBackoffFactory = () =>
new ExponentialBackoff({
maxIterations: this._retryTimes,
maxIterations: this.#retryTimes,
});
this.#backoffStrategy = options.backoffStrategy ?? defaultBackoffFactory;
// Rewrite to avoid redirects
Expand Down Expand Up @@ -422,6 +422,7 @@ export class HttpAgent implements Agent {
body,
}),
backoff,
tries: 0,
});

const [response, requestId] = await Promise.all([request, requestIdOf(submit)]);
Expand Down Expand Up @@ -449,14 +450,21 @@ export class HttpAgent implements Agent {
body: ArrayBuffer;
requestId: RequestId;
backoff: BackoffStrategy;
tries: number;
}): Promise<ApiQueryResponse> {
const { ecid, transformedRequest, body, requestId, backoff } = args;
const { ecid, transformedRequest, body, requestId, backoff, tries } = args;

const delay = backoff.next();

console.log('Backoff count: ', backoff.count);
console.log('Backoff ellapsedTime: ', backoff.ellapsedTimeInMsec);
console.log('Delay: ', delay);

if (delay === null) {
throw new AgentError(
`Timestamp failed to pass the watermark after retrying the configured ${this._retryTimes} times. We cannot guarantee the integrity of the response since it could be a replay attack.`,
`Timestamp failed to pass the watermark after retrying the configured ${
this.#retryTimes
} times. We cannot guarantee the integrity of the response since it could be a replay attack.`,
);
}

Expand All @@ -467,6 +475,10 @@ export class HttpAgent implements Agent {
let response: ApiQueryResponse;
// Make the request and retry if it throws an error
try {
this.log(
`fetching "/api/v2/canister/${ecid.toString()}/query" with request:`,
transformedRequest,
);
const fetchResponse = await this._fetch(
'' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this._host),
{
Expand Down Expand Up @@ -501,14 +513,15 @@ export class HttpAgent implements Agent {
);
}
} catch (error) {
if (delay === null) {
if (tries < this.#retryTimes && delay !== null) {
this.log.warn(
`Caught exception while attempting to make query:\n` +
` ${error}\n` +
` Retrying query.`,
);
return await this.#requestAndRetryQuery(args);
return await this.#requestAndRetryQuery({ ...args, tries: tries + 1 });
}

throw error;
}

Expand Down Expand Up @@ -540,6 +553,16 @@ export class HttpAgent implements Agent {
timestamp,
waterMark: this.waterMark,
});
if (tries < this.#retryTimes) {
return await this.#requestAndRetryQuery({ ...args, tries: tries + 1 });
}
{
throw new AgentError(
`Timestamp failed to pass the watermark after retrying the configured ${
this.#retryTimes
} times. We cannot guarantee the integrity of the response since it could be a replay attack.`,
);
}
}

return response;
Expand All @@ -548,19 +571,30 @@ export class HttpAgent implements Agent {
async #requestAndRetry(args: {
request: () => Promise<Response>;
backoff: BackoffStrategy;
tries: number;
}): Promise<Response> {
const { request, backoff } = args;
const { request, backoff, tries } = args;
const delay = backoff.next();

let response: Response;
try {
response = await request();
} catch (error) {
// Delay the request by the configured backoff strategy
if (this.#retryTimes > tries) {
this.log.warn(
`Caught exception while attempting to make request:\n` +
` ${error}\n` +
` Retrying request.`,
);
// Delay the request by the configured backoff strategy
return await this.#requestAndRetry({ request, backoff, tries: tries + 1 });
}

if (delay === null) {
throw new AgentError(
`Timestamp failed to pass the watermark after retrying the configured ${this._retryTimes} times. We cannot guarantee the integrity of the response since it could be a replay attack.`,
`Timestamp failed to pass the watermark after retrying the configured ${
this.#retryTimes
} times. We cannot guarantee the integrity of the response since it could be a replay attack.`,
);
}
await new Promise(resolve => setTimeout(resolve, delay));
Expand All @@ -570,7 +604,7 @@ export class HttpAgent implements Agent {
` ${error}\n` +
` Retrying request.`,
);
return await this.#requestAndRetry({ request, backoff });
return await this.#requestAndRetry({ request, backoff, tries: tries + 1 });
}
if (response.ok) {
return response;
Expand All @@ -592,7 +626,7 @@ export class HttpAgent implements Agent {
}

this.log.warn(errorMessage + ` Retrying request.`);
return await this.#requestAndRetry({ request, backoff });
return await this.#requestAndRetry({ request, backoff, tries: tries + 1 });
}

public async query(
Expand Down Expand Up @@ -656,6 +690,7 @@ export class HttpAgent implements Agent {
body,
requestId,
backoff,
tries: 0,
};

return await this.#requestAndRetryQuery(args);
Expand Down Expand Up @@ -840,6 +875,7 @@ export class HttpAgent implements Agent {
},
),
backoff,
tries: 0,
});

if (!response.ok) {
Expand Down Expand Up @@ -930,6 +966,7 @@ export class HttpAgent implements Agent {
backoff,
request: () =>
this._fetch('' + new URL(`/api/v2/status`, this._host), { headers, ...this._fetchOptions }),
tries: 0,
});
return cbor.decode(await response.arrayBuffer());
}
Expand Down

0 comments on commit d06103d

Please sign in to comment.