Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: effective target canister ID for mgmt call #954

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading