Skip to content

Commit

Permalink
feat: effective target canister ID for mgmt call (#954)
Browse files Browse the repository at this point in the history
* feat: effective target canister ID for mgmt call
* docs: add entry to changelog
  • Loading branch information
peterpeterparker authored Nov 26, 2024
1 parent d0b6b4c commit 7b119b3
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 1 deletion.
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: Add support for effective target canister ID in management canister calls.

### Changed

- chore: Removes warning that users found unhelpful, when a message originates from other sources than the identity provider in `AuthClient` during authentication.
Expand Down
5 changes: 4 additions & 1 deletion packages/agent/src/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ export type ManagementCanisterRecord = _SERVICE;
export function getManagementCanister(config: CallConfig): ActorSubclass<ManagementCanisterRecord> {
function transform(
_methodName: string,
args: Record<string, unknown> & { canister_id: string }[],
args: Record<string, unknown> & { canister_id: string; target_canister?: unknown }[],
) {
if (config.effectiveCanisterId) {
return { effectiveCanisterId: Principal.from(config.effectiveCanisterId) };
Expand All @@ -668,6 +668,9 @@ export function getManagementCanister(config: CallConfig): ActorSubclass<Managem
if (first && typeof first === 'object' && first.canister_id) {
effectiveCanisterId = Principal.from(first.canister_id as unknown);
}
if (first && typeof first === 'object' && first.target_canister) {
effectiveCanisterId = Principal.from(first.target_canister);
}
return { effectiveCanisterId };
}

Expand Down
72 changes: 72 additions & 0 deletions packages/agent/src/agent/http/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,78 @@ test('it should handle calls against the ic-management canister that succeed', a
expect(status).toMatchSnapshot();
});

test('it should use target_canister as effective canister id for calls against the ic-management canister', async () => {
const identity = new AnonymousIdentity();

// Response generated by calling a locally deployed replica of the management canister, cloned using fetchCloner
const mockResponse = {
headers: [
['access-control-allow-origin', '*'],
['content-length', '761'],
['content-type', 'application/cbor'],
['date', 'Tue, 22 Oct 2024 22:19:07 GMT'],
],
ok: true,
status: 200,
statusText: 'OK',
body: 'd9d9f7a266737461747573677265706c6965646b63657274696669636174655902d7d9d9f7a26474726565830183018204582012dbb02955bd3e2987bbba491230b2bb4a593feb02b5bb2d08f5f861afa9cec28301820458202b60693266aeec370be9f54508af493f4dd740086476054c862fe5af17ab15c183024e726571756573745f73746174757383018301820458204bebdfa0327978bfb109f0e14b35e8d368bb62114628ae547386162e9ee3dad883025820cf1cd57f39dfbb40ca1c816c71407c8b1b2edfb5a632676c7917dc4aa8641c5283018302457265706c7982035901684449444c0a6c0b9cb1fa2568b2ceef2f01c0cff2717d9cbab69c0202ffdb81f7037d8daacd94087de3f9f5d90805e8fc8cec0908b0e4d2970a7d81cfaef40a0984aaa89e0f7d6b038da4879b047ff496e4910b7fffdba5db0e7f6d036c020004017d6d7b6c089cb1fa2568c0cff2717dd7e09b90020680ad988a047dedd9c8c90707f8e287cc0c7ddeebb5a90e7da882acc60f7d6d686b02d7e09b90027fa981ceb7067f6c04c1f8dc83037d83cac6e9057da1d0b8af0a7d8fd0cfd00f7d6e040100011100001945cd0f5904e6ce2e5ac91900fb0102809a9e01010100b9f384b5ff59d4b88c01b9f384b5ff59011100001945cd0f5904e6ce2e5ac91900fb01809a9e0103010104010a80000000001000000101011d9a1e6bf09022ffccbffa69fc8e083bb02d5079d48b3640c086b9bfb10280a0e5b9c291010000000000000000aab36e0120fe9be8c73192343439c983d14da2f37a593269285d310fd2c11d4970e5f16eb6008302467374617475738203477265706c69656482045820daeffcc5dadc3aca94e0dc470e429ad4e3bc08517b5776f6a71e7e6982883bef8301820458208e6c6a7c4ba444475de4f4cd2d6df9501873d3290693060faf92e6dc528ee08083024474696d65820349a88dd3f9dacbb98018697369676e6174757265583088040a8228ef3f428c61918c5fb356e74b2ab07aa19f960edbb1fdfcbbc115e35f2e6c3f33b5cf4752799619e67e2b22',
now: 1729635546372,
};

// Mock the fetch implementation, resolving a pre-calculated response
const mockFetch: jest.Mock = jest.fn(() => {
return Promise.resolve({
...mockResponse,
body: fromHex(mockResponse.body),
arrayBuffer: async () => fromHex(mockResponse.body),
});
});

// Mock time so certificates can be accurately decoded
jest.useFakeTimers();
jest.setSystemTime(mockResponse.now);

// Pass in rootKey from replica (used because test was written using local replica)
const agent = await HttpAgent.createSync({
identity,
fetch: mockFetch,
host: 'http://localhost:4943',
rootKey: fromHex(
'308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c050302010361008be882f1985cccb53fd551571a42818014835ed8f8a27767669b67dd4a836eb0d62b327e3368a80615b0e4f472c73f7917c036dc9317dcb64b319a1efa43dd7c656225c061de359db6fdf7033ac1bff24c944c145e46ebdce2093680b6209a13',
),
});

// Use management canister call
const management = getManagementCanister({ agent });

// Important - override nonce when making request to ensure reproducible result
(Actor.agentOf(management) as HttpAgent).addTransform('update', async args => {
args.body.nonce = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]) as Nonce;
return args;
});

const spy = jest.spyOn(Actor.agentOf(management) as HttpAgent, 'call');

const target_canister = Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai');

await management.install_chunked_code({
arg: new Uint8Array([1, 2, 3]),
wasm_module_hash: new Uint8Array([4, 5, 6]),
mode: { install: null },
chunk_hashes_list: [],
target_canister,
store_canister: [],
sender_canister_version: [],
});

expect(spy).toHaveBeenCalledWith(
Principal.fromHex(''),
expect.objectContaining({
effectiveCanisterId: target_canister,
}),
);
});

/**
* Test utility to clone a fetch response for mocking purposes with the agent
* @param request - RequestInfo
Expand Down

0 comments on commit 7b119b3

Please sign in to comment.