Skip to content

Commit

Permalink
Merge branch 'main' into kyle/SDK-1232-retry-logic
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock authored Sep 29, 2023
2 parents ca54b90 + 62ba61b commit 67e6255
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 31 deletions.
5 changes: 5 additions & 0 deletions docs/generated/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ <h1>Agent-JS Changelog</h1>
<h2>Version x.x.x</h2>
<ul>
<li>feat: retry logic will catch and retry for thrown errors</li>
<li>
feat!: adds certificate logic to decode subnet and node key paths from the hashtree.
Changes the interface for `lookup_path` to allow returning a HashTree, but also constrains
`lookup` response to an ArrayBuffer using a new `lookupResultToBuffer` export
</li>
</ul>
<h2>Version 0.19.3</h2>
<ul>
Expand Down

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions packages/agent/src/agent/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
SubmitRequestType,
} from './types';
import { AgentHTTPResponseError } from './errors';
import { request } from '../../canisterStatus';
import { SubnetStatus } from '../../certificate';

export * from './transforms';
export { Nonce, makeNonce } from './types';
Expand Down Expand Up @@ -178,6 +180,8 @@ export class HttpAgent implements Agent {
private readonly _retryTimes; // Retry requests N times before erroring by default
public readonly _isAgent = true;

#subnetKeys: Map<string, SubnetStatus> = new Map();

constructor(options: HttpAgentOptions = {}) {
if (options.source) {
if (!(options.source instanceof HttpAgent)) {
Expand Down Expand Up @@ -588,6 +592,21 @@ export class HttpAgent implements Agent {
this._identity = Promise.resolve(identity);
}

public async fetchSubnetKeys(canisterId: Principal | string): Promise<any> {
const effectiveCanisterId: Principal = Principal.from(canisterId);
const response = await request({
canisterId: effectiveCanisterId,
paths: ['subnet'],
agent: this,
});

const subnetResponse = response.get('subnet');
if (subnetResponse && typeof subnetResponse === 'object' && 'nodeKeys' in subnetResponse) {
this.#subnetKeys.set(effectiveCanisterId.toText(), subnetResponse as SubnetStatus);
}
return subnetResponse;
}

protected _transform(request: HttpAgentRequest): Promise<HttpAgentRequest> {
let p = Promise.resolve(request);

Expand Down
2 changes: 1 addition & 1 deletion packages/agent/src/canisterStatus/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,6 @@ describe('Canister Status utility', () => {
expect(status.get('asdf' as unknown as Path)).toBe(null);
// Expect undefined for unset value
expect(status.get('test123')).toBe(undefined);
expect(consoleSpy).toBeCalledTimes(2);
expect(consoleSpy).toBeCalledTimes(3);
});
});
41 changes: 37 additions & 4 deletions packages/agent/src/canisterStatus/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/** @module CanisterStatus */

import { Principal } from '@dfinity/principal';
import { AgentError } from '../errors';
import { HttpAgent } from '../agent/http';
import { Certificate, CreateCertificateOptions } from '../certificate';
import {
Certificate,
CreateCertificateOptions,
SubnetStatus,
lookupResultToBuffer,
} from '../certificate';
import { toHex } from '../utils/buffer';
import * as Cbor from '../cbor';
import { decodeLeb128, decodeTime } from '../utils/leb';
Expand All @@ -12,7 +16,15 @@ import { decodeLeb128, decodeTime } from '../utils/leb';
* Types of an entry on the canisterStatus map.
* An entry of null indicates that the request failed, due to lack of permissions or the result being missing.
*/
export type Status = string | ArrayBuffer | Date | ArrayBuffer[] | Principal[] | bigint | null;
export type Status =
| string
| ArrayBuffer
| Date
| ArrayBuffer[]
| Principal[]
| SubnetStatus
| bigint
| null;

/**
* Interface to define a custom path. Nested paths will be represented as individual buffers, and can be created from text using {@link TextEncoder}
Expand Down Expand Up @@ -98,7 +110,24 @@ export const request = async (options: {
canisterId: canisterId,
});

const data = cert.lookup(encodePath(uniquePaths[index], canisterId));
response.certificate;
const lookup = (cert: Certificate, path: Path) => {
if (path === 'subnet') {
const data = cert.cache_node_keys();
return {
path: path,
data,
};
} else {
return {
path: path,
data: lookupResultToBuffer(cert.lookup(encodePath(path, canisterId))),
};
}
};

// must pass in the rootKey if we have no delegation
const { path, data } = lookup(cert, uniquePaths[index]);
if (!data) {
// Typically, the cert lookup will throw
console.warn(`Expected to find result for path ${path}, but instead found nothing.`);
Expand All @@ -121,6 +150,10 @@ export const request = async (options: {
status.set(path, decodeHex(data));
break;
}
case 'subnet': {
status.set(path, data);
break;
}
case 'candid': {
status.set(path, new TextDecoder().decode(data));
break;
Expand Down
Loading

0 comments on commit 67e6255

Please sign in to comment.