Skip to content

Commit

Permalink
feat: introduces callAndPoll utility returning the full undecoded cer…
Browse files Browse the repository at this point in the history
…tificate from the response
  • Loading branch information
krpeacock committed Oct 4, 2024
1 parent a57d8a3 commit 9d0ab85
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- feat: new `callAndPoll` function for an agent to call a canister and poll for the response

## [2.1.2] - 2024-09-30
- fix: revert https://github.com/dfinity/agent-js/pull/923 allow option to set agent replica time

Expand Down
17 changes: 17 additions & 0 deletions e2e/node/basic/callAndPoll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { HttpAgent, fromHex, callAndPoll } from '@dfinity/agent';
import { expect, describe, it, vi } from 'vitest';
describe('call and poll', () => {
it('should handle call and poll', async () => {
vi.useRealTimers();

const options = {
canisterId: 'tnnnb-2yaaa-aaaab-qaiiq-cai',
methodName: 'inc_read',
agent: await HttpAgent.create({ host: 'https://icp-api.io' }),
arg: fromHex('4449444c0000'),
};

const certificate = await callAndPoll(options);
expect(certificate instanceof ArrayBuffer).toBe(true);
});
});
2 changes: 2 additions & 0 deletions packages/agent/src/certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface CreateCertificateOptions {

export class Certificate {
public cert: Cert;
public readonly rawCert: ArrayBuffer;

/**
* Create a new instance of a certificate, automatically verifying it. Throws a
Expand Down Expand Up @@ -191,6 +192,7 @@ export class Certificate {
// Default to 5 minutes
private _maxAgeInMinutes: number = 5,
) {
this.rawCert = certificate;
this.cert = cbor.decode(new Uint8Array(certificate));
}

Expand Down
1 change: 1 addition & 0 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './der';
export * from './fetch_candid';
export * from './public_key';
export * from './request_id';
export * from './utils/callAndPoll';
export * from './utils/bls';
export * from './utils/buffer';
export * from './utils/random';
Expand Down
54 changes: 54 additions & 0 deletions packages/agent/src/utils/callAndPoll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Principal } from '@dfinity/principal';
import { Agent, Certificate, bufFromBufLike, polling } from '..';
import { AgentError } from '../errors';

/**
* Call a canister using the v3 api and either return the response or fall back to polling
* @param options - The options to use when calling the canister
* @param options.canisterId - The canister id to call
* @param options.methodName - The method name to call
* @param options.agent - The agent to use to make the call
* @param options.arg - The argument to pass to the canister
* @returns The certificate response from the canister (which includes the reply)
*/
export async function callAndPoll(options: {
canisterId: Principal | string;
methodName: string;
agent: Agent;
arg: ArrayBuffer;
}): Promise<ArrayBuffer> {
const { canisterId, methodName, agent, arg } = options;
const cid = Principal.from(options.canisterId);

const { defaultStrategy } = polling.strategy;

if (agent.rootKey == null) throw new Error('Agent root key not initialized before making call');

const { requestId, response } = await agent.call(cid, {
methodName,
arg,
effectiveCanisterId: cid,
});

let certificate: Certificate;
if (response.body && response.body.certificate) {
const cert = response.body.certificate;
// Create certificate to validate the responses
certificate = await Certificate.create({
certificate: bufFromBufLike(cert),
rootKey: agent.rootKey,
canisterId: Principal.from(canisterId),
});
} else {
throw new AgentError('unexpected call error: no certificate in response');
}
// Fall back to polling if we recieve an Accepted response code
if (response.status === 202) {
const pollStrategy = defaultStrategy();
// Contains the certificate and the reply from the boundary node
const response = await polling.pollForResponse(agent, cid, requestId, pollStrategy);
certificate = response.certificate;
}

return certificate.rawCert;
}

0 comments on commit 9d0ab85

Please sign in to comment.