From 40d7b31a116d406f9c06ed8345839a45d859134b Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 11:04:04 -0700 Subject: [PATCH 1/7] feat: updates for canister logging feature from management canister --- e2e/node/basic/mainnet.test.ts | 34 +- packages/agent/src/actor.ts | 2 + packages/agent/src/agent/http/index.ts | 48 +- packages/agent/src/canisters/management.did | 387 +++++++++++++++ .../agent/src/canisters/management_idl.ts | 448 +++++++++--------- .../agent/src/canisters/management_service.ts | 394 ++++++++------- 6 files changed, 903 insertions(+), 410 deletions(-) create mode 100644 packages/agent/src/canisters/management.did diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index 2a42b9a29..5a770c794 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -1,4 +1,12 @@ -import { Actor, AnonymousIdentity, HttpAgent, Identity, CanisterStatus } from '@dfinity/agent'; +import { + Actor, + AnonymousIdentity, + HttpAgent, + Identity, + CanisterStatus, + MANAGEMENT_CANISTER_ID, + getManagementCanister, +} from '@dfinity/agent'; import { IDL } from '@dfinity/candid'; import { Ed25519KeyIdentity } from '@dfinity/identity'; import { Principal } from '@dfinity/principal'; @@ -161,3 +169,27 @@ describe('controllers', () => { `); }); }); + +it.only('should make requests to the management canister', async () => { + const canisterLogs = async ({ + canisterId, + identity, + }: { + canisterId: Principal; + identity: Identity; + }) => { + const actor = getManagementCanister({ + agent: new HttpAgent({ host: 'https://icp-api.io', identity }), + }); + + return await actor.fetch_canister_logs({ + canister_id: canisterId, + }); + }; + + const logs = await canisterLogs({ + canisterId: Principal.from('rrkah-fqaaa-aaaaa-aaaaq-cai'), + identity: new AnonymousIdentity(), + }); + console.log(logs); +}); diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index a7fd26823..bd76ec655 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -278,6 +278,8 @@ export class Actor { compute_allocation: settings.compute_allocation ? [settings.compute_allocation] : [], freezing_threshold: settings.freezing_threshold ? [settings.freezing_threshold] : [], memory_allocation: settings.memory_allocation ? [settings.memory_allocation] : [], + reserved_cycles_limit: [], + log_visibility: [], }, ]; } diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index e8ca5a1d0..5ee8fbf60 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -52,12 +52,14 @@ export enum RequestStatusResponseStatus { const DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS = 5 * 60 * 1000; // Root public key for the IC, encoded as hex -const IC_ROOT_KEY = +export const IC_ROOT_KEY = '308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100814' + 'c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d968' + '5f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484' + 'b01291091c5f87b98883463f98091a0baaae'; +export const MANAGEMENT_CANISTER_ID = 'aaaaa-aa'; + // IC0 domain info const IC0_DOMAIN = 'ic0.app'; const IC0_SUB_DOMAIN = '.ic0.app'; @@ -446,17 +448,31 @@ export class HttpAgent implements Agent { body, }, ); - const queryResponse: QueryResponse = cbor.decode(await fetchResponse.arrayBuffer()); - response = { - ...queryResponse, - httpDetails: { - ok: fetchResponse.ok, - status: fetchResponse.status, - statusText: fetchResponse.statusText, - headers: httpHeadersTransform(fetchResponse.headers), - }, - requestId, - }; + if (fetchResponse.status === 200) { + const queryResponse: QueryResponse = cbor.decode(await fetchResponse.arrayBuffer()); + response = { + ...queryResponse, + httpDetails: { + ok: fetchResponse.ok, + status: fetchResponse.status, + statusText: fetchResponse.statusText, + headers: httpHeadersTransform(fetchResponse.headers), + }, + requestId, + }; + } else { + throw new AgentHTTPResponseError( + `Server returned an error:\n` + + ` Code: ${fetchResponse.status} (${fetchResponse.statusText})\n` + + ` Body: ${await fetchResponse.text()}\n`, + { + ok: fetchResponse.ok, + status: fetchResponse.status, + statusText: fetchResponse.statusText, + headers: httpHeadersTransform(fetchResponse.headers), + }, + ); + } } catch (error) { if (tries < this._retryTimes) { this.log.warn( @@ -471,8 +487,8 @@ export class HttpAgent implements Agent { const timestamp = response.signatures?.[0]?.timestamp; - // Skip watermark verification if the user has set verifyQuerySignatures to false - if (!this.#verifyQuerySignatures) { + // Skip watermark verification if the user has set verifyQuerySignatures to false or if the canister is the management canister + if (!this.#verifyQuerySignatures ?? canister === MANAGEMENT_CANISTER_ID) { return response; } @@ -606,7 +622,7 @@ export class HttpAgent implements Agent { }; const getSubnetStatus = async (): Promise => { - if (!this.#verifyQuerySignatures) { + if (!this.#verifyQuerySignatures || canisterId === MANAGEMENT_CANISTER_ID) { return undefined; } const subnetStatus = this.#subnetKeys.get(canisterId.toString()); @@ -623,7 +639,7 @@ export class HttpAgent implements Agent { this.log('Query response:', query); // Skip verification if the user has disabled it - if (!this.#verifyQuerySignatures) { + if (!this.#verifyQuerySignatures || canisterId === MANAGEMENT_CANISTER_ID) { return query; } diff --git a/packages/agent/src/canisters/management.did b/packages/agent/src/canisters/management.did new file mode 100644 index 000000000..14f4e5906 --- /dev/null +++ b/packages/agent/src/canisters/management.did @@ -0,0 +1,387 @@ +type canister_id = principal; +type wasm_module = blob; + +type log_visibility = variant { + controllers; + public; +}; + +type canister_settings = record { + controllers : opt vec principal; + compute_allocation : opt nat; + memory_allocation : opt nat; + freezing_threshold : opt nat; + reserved_cycles_limit : opt nat; + log_visibility : opt log_visibility; +}; + +type definite_canister_settings = record { + controllers : vec principal; + compute_allocation : nat; + memory_allocation : nat; + freezing_threshold : nat; + reserved_cycles_limit : nat; + log_visibility : log_visibility; +}; + +type change_origin = variant { + from_user : record { + user_id : principal; + }; + from_canister : record { + canister_id : principal; + canister_version : opt nat64; + }; +}; + +type change_details = variant { + creation : record { + controllers : vec principal; + }; + code_uninstall; + code_deployment : record { + mode : variant { install; reinstall; upgrade }; + module_hash : blob; + }; + controllers_change : record { + controllers : vec principal; + }; +}; + +type change = record { + timestamp_nanos : nat64; + canister_version : nat64; + origin : change_origin; + details : change_details; +}; + +type chunk_hash = blob; + +type http_header = record { + name : text; + value : text; +}; + +type http_request_result = record { + status : nat; + headers : vec http_header; + body : blob; +}; + +type ecdsa_curve = variant { + secp256k1; +}; + +type satoshi = nat64; + +type bitcoin_network = variant { + mainnet; + testnet; +}; + +type bitcoin_address = text; + +type block_hash = blob; + +type outpoint = record { + txid : blob; + vout : nat32; +}; + +type utxo = record { + outpoint : outpoint; + value : satoshi; + height : nat32; +}; + +type bitcoin_get_utxos_args = record { + address : bitcoin_address; + network : bitcoin_network; + filter : opt variant { + min_confirmations : nat32; + page : blob; + }; +}; + +type bitcoin_get_utxos_query_args = record { + address : bitcoin_address; + network : bitcoin_network; + filter : opt variant { + min_confirmations : nat32; + page : blob; + }; +}; + +type bitcoin_get_current_fee_percentiles_args = record { + network : bitcoin_network; +}; + +type bitcoin_get_utxos_result = record { + utxos : vec utxo; + tip_block_hash : block_hash; + tip_height : nat32; + next_page : opt blob; +}; + +type bitcoin_get_utxos_query_result = record { + utxos : vec utxo; + tip_block_hash : block_hash; + tip_height : nat32; + next_page : opt blob; +}; + +type bitcoin_get_balance_args = record { + address : bitcoin_address; + network : bitcoin_network; + min_confirmations : opt nat32; +}; + +type bitcoin_get_balance_query_args = record { + address : bitcoin_address; + network : bitcoin_network; + min_confirmations : opt nat32; +}; + +type bitcoin_send_transaction_args = record { + transaction : blob; + network : bitcoin_network; +}; + +type millisatoshi_per_byte = nat64; + +type node_metrics = record { + node_id : principal; + num_blocks_total : nat64; + num_block_failures_total : nat64; +}; + +type create_canister_args = record { + settings : opt canister_settings; + sender_canister_version : opt nat64; +}; + +type create_canister_result = record { + canister_id : canister_id; +}; + +type update_settings_args = record { + canister_id : principal; + settings : canister_settings; + sender_canister_version : opt nat64; +}; + +type upload_chunk_args = record { + canister_id : principal; + chunk : blob; +}; + +type clear_chunk_store_args = record { + canister_id : canister_id; +}; + +type stored_chunks_args = record { + canister_id : canister_id; +}; + +type install_code_args = record { + mode : variant { + install; + reinstall; + upgrade : opt record { + skip_pre_upgrade : opt bool; + }; + }; + canister_id : canister_id; + wasm_module : wasm_module; + arg : blob; + sender_canister_version : opt nat64; +}; + +type install_chunked_code_args = record { + mode : variant { + install; + reinstall; + upgrade : opt record { + skip_pre_upgrade : opt bool; + }; + }; + target_canister : canister_id; + storage_canister : opt canister_id; + chunk_hashes_list : vec chunk_hash; + wasm_module_hash : blob; + arg : blob; + sender_canister_version : opt nat64; +}; + +type uninstall_code_args = record { + canister_id : canister_id; + sender_canister_version : opt nat64; +}; + +type start_canister_args = record { + canister_id : canister_id; +}; + +type stop_canister_args = record { + canister_id : canister_id; +}; + +type canister_status_args = record { + canister_id : canister_id; +}; + +type canister_status_result = record { + status : variant { running; stopping; stopped }; + settings : definite_canister_settings; + module_hash : opt blob; + memory_size : nat; + cycles : nat; + reserved_cycles : nat; + idle_cycles_burned_per_day : nat; +}; + +type canister_info_args = record { + canister_id : canister_id; + num_requested_changes : opt nat64; +}; + +type canister_info_result = record { + total_num_changes : nat64; + recent_changes : vec change; + module_hash : opt blob; + controllers : vec principal; +}; + +type delete_canister_args = record { + canister_id : canister_id; +}; + +type deposit_cycles_args = record { + canister_id : canister_id; +}; + +type http_request_args = record { + url : text; + max_response_bytes : opt nat64; + method : variant { get; head; post }; + headers : vec http_header; + body : opt blob; + transform : opt record { + function : func(record { response : http_request_result; context : blob }) -> (http_request_result) query; + context : blob; + }; +}; + +type ecdsa_public_key_args = record { + canister_id : opt canister_id; + derivation_path : vec blob; + key_id : record { curve : ecdsa_curve; name : text }; +}; + +type ecdsa_public_key_result = record { + public_key : blob; + chain_code : blob; +}; + +type sign_with_ecdsa_args = record { + message_hash : blob; + derivation_path : vec blob; + key_id : record { curve : ecdsa_curve; name : text }; +}; + +type sign_with_ecdsa_result = record { + signature : blob; +}; + +type node_metrics_history_args = record { + subnet_id : principal; + start_at_timestamp_nanos : nat64; +}; + +type node_metrics_history_result = vec record { + timestamp_nanos : nat64; + node_metrics : vec node_metrics; +}; + +type provisional_create_canister_with_cycles_args = record { + amount : opt nat; + settings : opt canister_settings; + specified_id : opt canister_id; + sender_canister_version : opt nat64; +}; + +type provisional_create_canister_with_cycles_result = record { + canister_id : canister_id; +}; + +type provisional_top_up_canister_args = record { + canister_id : canister_id; + amount : nat; +}; + +type raw_rand_result = blob; + +type stored_chunks_result = vec chunk_hash; + +type upload_chunk_result = chunk_hash; + +type bitcoin_get_balance_result = satoshi; + +type bitcoin_get_balance_query_result = satoshi; + +type bitcoin_get_current_fee_percentiles_result = vec millisatoshi_per_byte; + +type fetch_canister_logs_args = record { + canister_id : canister_id; +}; + +type canister_log_record = record { + idx : nat64; + timestamp_nanos : nat64; + content : blob; +}; + +type fetch_canister_logs_result = record { + canister_log_records : vec canister_log_record; +}; + +service ic : { + create_canister : (create_canister_args) -> (create_canister_result); + update_settings : (update_settings_args) -> (); + upload_chunk : (upload_chunk_args) -> (upload_chunk_result); + clear_chunk_store : (clear_chunk_store_args) -> (); + stored_chunks : (stored_chunks_args) -> (stored_chunks_result); + install_code : (install_code_args) -> (); + install_chunked_code : (install_chunked_code_args) -> (); + uninstall_code : (uninstall_code_args) -> (); + start_canister : (start_canister_args) -> (); + stop_canister : (stop_canister_args) -> (); + canister_status : (canister_status_args) -> (canister_status_result); + canister_info : (canister_info_args) -> (canister_info_result); + delete_canister : (delete_canister_args) -> (); + deposit_cycles : (deposit_cycles_args) -> (); + raw_rand : () -> (raw_rand_result); + http_request : (http_request_args) -> (http_request_result); + + // Threshold ECDSA signature + ecdsa_public_key : (ecdsa_public_key_args) -> (ecdsa_public_key_result); + sign_with_ecdsa : (sign_with_ecdsa_args) -> (sign_with_ecdsa_result); + + // bitcoin interface + bitcoin_get_balance : (bitcoin_get_balance_args) -> (bitcoin_get_balance_result); + bitcoin_get_balance_query : (bitcoin_get_balance_query_args) -> (bitcoin_get_balance_query_result) query; + bitcoin_get_utxos : (bitcoin_get_utxos_args) -> (bitcoin_get_utxos_result); + bitcoin_get_utxos_query : (bitcoin_get_utxos_query_args) -> (bitcoin_get_utxos_query_result) query; + bitcoin_send_transaction : (bitcoin_send_transaction_args) -> (); + bitcoin_get_current_fee_percentiles : (bitcoin_get_current_fee_percentiles_args) -> (bitcoin_get_current_fee_percentiles_result); + + // metrics interface + node_metrics_history : (node_metrics_history_args) -> (node_metrics_history_result); + + // provisional interfaces for the pre-ledger world + provisional_create_canister_with_cycles : (provisional_create_canister_with_cycles_args) -> (provisional_create_canister_with_cycles_result); + provisional_top_up_canister : (provisional_top_up_canister_args) -> (); + + // canister logging + fetch_canister_logs : (fetch_canister_logs_args) -> (fetch_canister_logs_result) query; +}; diff --git a/packages/agent/src/canisters/management_idl.ts b/packages/agent/src/canisters/management_idl.ts index 8a2875e38..bad5b8ea3 100644 --- a/packages/agent/src/canisters/management_idl.ts +++ b/packages/agent/src/canisters/management_idl.ts @@ -11,17 +11,25 @@ export default ({ IDL }) => { testnet: IDL.Null, }); const bitcoin_address = IDL.Text; - const get_balance_request = IDL.Record({ + const bitcoin_get_balance_args = IDL.Record({ network: bitcoin_network, address: bitcoin_address, min_confirmations: IDL.Opt(IDL.Nat32), }); const satoshi = IDL.Nat64; - const get_current_fee_percentiles_request = IDL.Record({ + const bitcoin_get_balance_result = satoshi; + const bitcoin_get_balance_query_args = IDL.Record({ + network: bitcoin_network, + address: bitcoin_address, + min_confirmations: IDL.Opt(IDL.Nat32), + }); + const bitcoin_get_balance_query_result = satoshi; + const bitcoin_get_current_fee_percentiles_args = IDL.Record({ network: bitcoin_network, }); const millisatoshi_per_byte = IDL.Nat64; - const get_utxos_request = IDL.Record({ + const bitcoin_get_current_fee_percentiles_result = IDL.Vec(millisatoshi_per_byte); + const bitcoin_get_utxos_args = IDL.Record({ network: bitcoin_network, filter: IDL.Opt( IDL.Variant({ @@ -41,17 +49,37 @@ export default ({ IDL }) => { value: satoshi, outpoint: outpoint, }); - const get_utxos_response = IDL.Record({ + const bitcoin_get_utxos_result = IDL.Record({ + next_page: IDL.Opt(IDL.Vec(IDL.Nat8)), + tip_height: IDL.Nat32, + tip_block_hash: block_hash, + utxos: IDL.Vec(utxo), + }); + const bitcoin_get_utxos_query_args = IDL.Record({ + network: bitcoin_network, + filter: IDL.Opt( + IDL.Variant({ + page: IDL.Vec(IDL.Nat8), + min_confirmations: IDL.Nat32, + }), + ), + address: bitcoin_address, + }); + const bitcoin_get_utxos_query_result = IDL.Record({ next_page: IDL.Opt(IDL.Vec(IDL.Nat8)), tip_height: IDL.Nat32, tip_block_hash: block_hash, utxos: IDL.Vec(utxo), }); - const send_transaction_request = IDL.Record({ + const bitcoin_send_transaction_args = IDL.Record({ transaction: IDL.Vec(IDL.Nat8), network: bitcoin_network, }); const canister_id = IDL.Principal; + const canister_info_args = IDL.Record({ + canister_id: canister_id, + num_requested_changes: IDL.Opt(IDL.Nat64), + }); const change_origin = IDL.Variant({ from_user: IDL.Record({ user_id: IDL.Principal }), from_canister: IDL.Record({ @@ -80,254 +108,234 @@ export default ({ IDL }) => { origin: change_origin, details: change_details, }); + const canister_info_result = IDL.Record({ + controllers: IDL.Vec(IDL.Principal), + module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + recent_changes: IDL.Vec(change), + total_num_changes: IDL.Nat64, + }); + const canister_status_args = IDL.Record({ canister_id: canister_id }); + const log_visibility = IDL.Variant({ + controllers: IDL.Null, + public: IDL.Null, + }); const definite_canister_settings = IDL.Record({ freezing_threshold: IDL.Nat, controllers: IDL.Vec(IDL.Principal), + reserved_cycles_limit: IDL.Nat, + log_visibility: log_visibility, memory_allocation: IDL.Nat, compute_allocation: IDL.Nat, }); + const canister_status_result = IDL.Record({ + status: IDL.Variant({ + stopped: IDL.Null, + stopping: IDL.Null, + running: IDL.Null, + }), + memory_size: IDL.Nat, + cycles: IDL.Nat, + settings: definite_canister_settings, + idle_cycles_burned_per_day: IDL.Nat, + module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + reserved_cycles: IDL.Nat, + }); + const clear_chunk_store_args = IDL.Record({ canister_id: canister_id }); const canister_settings = IDL.Record({ freezing_threshold: IDL.Opt(IDL.Nat), controllers: IDL.Opt(IDL.Vec(IDL.Principal)), + reserved_cycles_limit: IDL.Opt(IDL.Nat), + log_visibility: IDL.Opt(log_visibility), memory_allocation: IDL.Opt(IDL.Nat), compute_allocation: IDL.Opt(IDL.Nat), }); + const create_canister_args = IDL.Record({ + settings: IDL.Opt(canister_settings), + sender_canister_version: IDL.Opt(IDL.Nat64), + }); + const create_canister_result = IDL.Record({ canister_id: canister_id }); + const delete_canister_args = IDL.Record({ canister_id: canister_id }); + const deposit_cycles_args = IDL.Record({ canister_id: canister_id }); const ecdsa_curve = IDL.Variant({ secp256k1: IDL.Null }); + const ecdsa_public_key_args = IDL.Record({ + key_id: IDL.Record({ name: IDL.Text, curve: ecdsa_curve }), + canister_id: IDL.Opt(canister_id), + derivation_path: IDL.Vec(IDL.Vec(IDL.Nat8)), + }); + const ecdsa_public_key_result = IDL.Record({ + public_key: IDL.Vec(IDL.Nat8), + chain_code: IDL.Vec(IDL.Nat8), + }); + const fetch_canister_logs_args = IDL.Record({ canister_id: canister_id }); + const canister_log_record = IDL.Record({ + idx: IDL.Nat64, + timestamp_nanos: IDL.Nat64, + content: IDL.Vec(IDL.Nat8), + }); + const fetch_canister_logs_result = IDL.Record({ + canister_log_records: IDL.Vec(canister_log_record), + }); const http_header = IDL.Record({ value: IDL.Text, name: IDL.Text }); - const http_response = IDL.Record({ + const http_request_result = IDL.Record({ status: IDL.Nat, body: IDL.Vec(IDL.Nat8), headers: IDL.Vec(http_header), }); + const http_request_args = IDL.Record({ + url: IDL.Text, + method: IDL.Variant({ + get: IDL.Null, + head: IDL.Null, + post: IDL.Null, + }), + max_response_bytes: IDL.Opt(IDL.Nat64), + body: IDL.Opt(IDL.Vec(IDL.Nat8)), + transform: IDL.Opt( + IDL.Record({ + function: IDL.Func( + [ + IDL.Record({ + context: IDL.Vec(IDL.Nat8), + response: http_request_result, + }), + ], + [http_request_result], + ['query'], + ), + context: IDL.Vec(IDL.Nat8), + }), + ), + headers: IDL.Vec(http_header), + }); const chunk_hash = IDL.Vec(IDL.Nat8); + const install_chunked_code_args = IDL.Record({ + arg: IDL.Vec(IDL.Nat8), + wasm_module_hash: IDL.Vec(IDL.Nat8), + mode: IDL.Variant({ + reinstall: IDL.Null, + upgrade: IDL.Opt(IDL.Record({ skip_pre_upgrade: IDL.Opt(IDL.Bool) })), + install: IDL.Null, + }), + chunk_hashes_list: IDL.Vec(chunk_hash), + target_canister: canister_id, + sender_canister_version: IDL.Opt(IDL.Nat64), + storage_canister: IDL.Opt(canister_id), + }); const wasm_module = IDL.Vec(IDL.Nat8); + const install_code_args = IDL.Record({ + arg: IDL.Vec(IDL.Nat8), + wasm_module: wasm_module, + mode: IDL.Variant({ + reinstall: IDL.Null, + upgrade: IDL.Opt(IDL.Record({ skip_pre_upgrade: IDL.Opt(IDL.Bool) })), + install: IDL.Null, + }), + canister_id: canister_id, + sender_canister_version: IDL.Opt(IDL.Nat64), + }); + const node_metrics_history_args = IDL.Record({ + start_at_timestamp_nanos: IDL.Nat64, + subnet_id: IDL.Principal, + }); const node_metrics = IDL.Record({ num_block_failures_total: IDL.Nat64, node_id: IDL.Principal, num_blocks_total: IDL.Nat64, }); + const node_metrics_history_result = IDL.Vec( + IDL.Record({ + timestamp_nanos: IDL.Nat64, + node_metrics: IDL.Vec(node_metrics), + }), + ); + const provisional_create_canister_with_cycles_args = IDL.Record({ + settings: IDL.Opt(canister_settings), + specified_id: IDL.Opt(canister_id), + amount: IDL.Opt(IDL.Nat), + sender_canister_version: IDL.Opt(IDL.Nat64), + }); + const provisional_create_canister_with_cycles_result = IDL.Record({ + canister_id: canister_id, + }); + const provisional_top_up_canister_args = IDL.Record({ + canister_id: canister_id, + amount: IDL.Nat, + }); + const raw_rand_result = IDL.Vec(IDL.Nat8); + const sign_with_ecdsa_args = IDL.Record({ + key_id: IDL.Record({ name: IDL.Text, curve: ecdsa_curve }), + derivation_path: IDL.Vec(IDL.Vec(IDL.Nat8)), + message_hash: IDL.Vec(IDL.Nat8), + }); + const sign_with_ecdsa_result = IDL.Record({ + signature: IDL.Vec(IDL.Nat8), + }); + const start_canister_args = IDL.Record({ canister_id: canister_id }); + const stop_canister_args = IDL.Record({ canister_id: canister_id }); + const stored_chunks_args = IDL.Record({ canister_id: canister_id }); + const stored_chunks_result = IDL.Vec(chunk_hash); + const uninstall_code_args = IDL.Record({ + canister_id: canister_id, + sender_canister_version: IDL.Opt(IDL.Nat64), + }); + const update_settings_args = IDL.Record({ + canister_id: IDL.Principal, + settings: canister_settings, + sender_canister_version: IDL.Opt(IDL.Nat64), + }); + const upload_chunk_args = IDL.Record({ + chunk: IDL.Vec(IDL.Nat8), + canister_id: IDL.Principal, + }); + const upload_chunk_result = chunk_hash; return IDL.Service({ - bitcoin_get_balance: IDL.Func([get_balance_request], [satoshi], []), - bitcoin_get_balance_query: IDL.Func([get_balance_request], [satoshi], ['query']), - bitcoin_get_current_fee_percentiles: IDL.Func( - [get_current_fee_percentiles_request], - [IDL.Vec(millisatoshi_per_byte)], - [], + bitcoin_get_balance: IDL.Func([bitcoin_get_balance_args], [bitcoin_get_balance_result], []), + bitcoin_get_balance_query: IDL.Func( + [bitcoin_get_balance_query_args], + [bitcoin_get_balance_query_result], + ['query'], ), - bitcoin_get_utxos: IDL.Func([get_utxos_request], [get_utxos_response], []), - bitcoin_get_utxos_query: IDL.Func([get_utxos_request], [get_utxos_response], ['query']), - bitcoin_send_transaction: IDL.Func([send_transaction_request], [], []), - canister_info: IDL.Func( - [ - IDL.Record({ - canister_id: canister_id, - num_requested_changes: IDL.Opt(IDL.Nat64), - }), - ], - [ - IDL.Record({ - controllers: IDL.Vec(IDL.Principal), - module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), - recent_changes: IDL.Vec(change), - total_num_changes: IDL.Nat64, - }), - ], - [], - ), - canister_status: IDL.Func( - [IDL.Record({ canister_id: canister_id })], - [ - IDL.Record({ - status: IDL.Variant({ - stopped: IDL.Null, - stopping: IDL.Null, - running: IDL.Null, - }), - memory_size: IDL.Nat, - cycles: IDL.Nat, - settings: definite_canister_settings, - idle_cycles_burned_per_day: IDL.Nat, - module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), - }), - ], - [], - ), - clear_chunk_store: IDL.Func([IDL.Record({ canister_id: canister_id })], [], []), - create_canister: IDL.Func( - [ - IDL.Record({ - settings: IDL.Opt(canister_settings), - sender_canister_version: IDL.Opt(IDL.Nat64), - }), - ], - [IDL.Record({ canister_id: canister_id })], - [], - ), - delete_canister: IDL.Func([IDL.Record({ canister_id: canister_id })], [], []), - deposit_cycles: IDL.Func([IDL.Record({ canister_id: canister_id })], [], []), - ecdsa_public_key: IDL.Func( - [ - IDL.Record({ - key_id: IDL.Record({ name: IDL.Text, curve: ecdsa_curve }), - canister_id: IDL.Opt(canister_id), - derivation_path: IDL.Vec(IDL.Vec(IDL.Nat8)), - }), - ], - [ - IDL.Record({ - public_key: IDL.Vec(IDL.Nat8), - chain_code: IDL.Vec(IDL.Nat8), - }), - ], - [], - ), - http_request: IDL.Func( - [ - IDL.Record({ - url: IDL.Text, - method: IDL.Variant({ - get: IDL.Null, - head: IDL.Null, - post: IDL.Null, - }), - max_response_bytes: IDL.Opt(IDL.Nat64), - body: IDL.Opt(IDL.Vec(IDL.Nat8)), - transform: IDL.Opt( - IDL.Record({ - function: IDL.Func( - [ - IDL.Record({ - context: IDL.Vec(IDL.Nat8), - response: http_response, - }), - ], - [http_response], - ['query'], - ), - context: IDL.Vec(IDL.Nat8), - }), - ), - headers: IDL.Vec(http_header), - }), - ], - [http_response], + bitcoin_get_current_fee_percentiles: IDL.Func( + [bitcoin_get_current_fee_percentiles_args], + [bitcoin_get_current_fee_percentiles_result], [], ), - install_chunked_code: IDL.Func( - [ - IDL.Record({ - arg: IDL.Vec(IDL.Nat8), - wasm_module_hash: IDL.Vec(IDL.Nat8), - mode: IDL.Variant({ - reinstall: IDL.Null, - upgrade: IDL.Opt(IDL.Record({ skip_pre_upgrade: IDL.Opt(IDL.Bool) })), - install: IDL.Null, - }), - chunk_hashes_list: IDL.Vec(chunk_hash), - target_canister: canister_id, - sender_canister_version: IDL.Opt(IDL.Nat64), - storage_canister: IDL.Opt(canister_id), - }), - ], - [], - [], + bitcoin_get_utxos: IDL.Func([bitcoin_get_utxos_args], [bitcoin_get_utxos_result], []), + bitcoin_get_utxos_query: IDL.Func( + [bitcoin_get_utxos_query_args], + [bitcoin_get_utxos_query_result], + ['query'], ), - install_code: IDL.Func( - [ - IDL.Record({ - arg: IDL.Vec(IDL.Nat8), - wasm_module: wasm_module, - mode: IDL.Variant({ - reinstall: IDL.Null, - upgrade: IDL.Opt(IDL.Record({ skip_pre_upgrade: IDL.Opt(IDL.Bool) })), - install: IDL.Null, - }), - canister_id: canister_id, - sender_canister_version: IDL.Opt(IDL.Nat64), - }), - ], - [], - [], - ), - node_metrics_history: IDL.Func( - [ - IDL.Record({ - start_at_timestamp_nanos: IDL.Nat64, - subnet_id: IDL.Principal, - }), - ], - [ - IDL.Vec( - IDL.Record({ - timestamp_nanos: IDL.Nat64, - node_metrics: IDL.Vec(node_metrics), - }), - ), - ], - [], + bitcoin_send_transaction: IDL.Func([bitcoin_send_transaction_args], [], []), + canister_info: IDL.Func([canister_info_args], [canister_info_result], []), + canister_status: IDL.Func([canister_status_args], [canister_status_result], []), + clear_chunk_store: IDL.Func([clear_chunk_store_args], [], []), + create_canister: IDL.Func([create_canister_args], [create_canister_result], []), + delete_canister: IDL.Func([delete_canister_args], [], []), + deposit_cycles: IDL.Func([deposit_cycles_args], [], []), + ecdsa_public_key: IDL.Func([ecdsa_public_key_args], [ecdsa_public_key_result], []), + fetch_canister_logs: IDL.Func( + [fetch_canister_logs_args], + [fetch_canister_logs_result], + ['query'], ), + http_request: IDL.Func([http_request_args], [http_request_result], []), + install_chunked_code: IDL.Func([install_chunked_code_args], [], []), + install_code: IDL.Func([install_code_args], [], []), + node_metrics_history: IDL.Func([node_metrics_history_args], [node_metrics_history_result], []), provisional_create_canister_with_cycles: IDL.Func( - [ - IDL.Record({ - settings: IDL.Opt(canister_settings), - specified_id: IDL.Opt(canister_id), - amount: IDL.Opt(IDL.Nat), - sender_canister_version: IDL.Opt(IDL.Nat64), - }), - ], - [IDL.Record({ canister_id: canister_id })], - [], - ), - provisional_top_up_canister: IDL.Func( - [IDL.Record({ canister_id: canister_id, amount: IDL.Nat })], - [], - [], - ), - raw_rand: IDL.Func([], [IDL.Vec(IDL.Nat8)], []), - sign_with_ecdsa: IDL.Func( - [ - IDL.Record({ - key_id: IDL.Record({ name: IDL.Text, curve: ecdsa_curve }), - derivation_path: IDL.Vec(IDL.Vec(IDL.Nat8)), - message_hash: IDL.Vec(IDL.Nat8), - }), - ], - [IDL.Record({ signature: IDL.Vec(IDL.Nat8) })], - [], - ), - start_canister: IDL.Func([IDL.Record({ canister_id: canister_id })], [], []), - stop_canister: IDL.Func([IDL.Record({ canister_id: canister_id })], [], []), - stored_chunks: IDL.Func([IDL.Record({ canister_id: canister_id })], [IDL.Vec(chunk_hash)], []), - uninstall_code: IDL.Func( - [ - IDL.Record({ - canister_id: canister_id, - sender_canister_version: IDL.Opt(IDL.Nat64), - }), - ], - [], - [], - ), - update_settings: IDL.Func( - [ - IDL.Record({ - canister_id: IDL.Principal, - settings: canister_settings, - sender_canister_version: IDL.Opt(IDL.Nat64), - }), - ], - [], - [], - ), - upload_chunk: IDL.Func( - [ - IDL.Record({ - chunk: IDL.Vec(IDL.Nat8), - canister_id: IDL.Principal, - }), - ], - [chunk_hash], + [provisional_create_canister_with_cycles_args], + [provisional_create_canister_with_cycles_result], [], ), + provisional_top_up_canister: IDL.Func([provisional_top_up_canister_args], [], []), + raw_rand: IDL.Func([], [raw_rand_result], []), + sign_with_ecdsa: IDL.Func([sign_with_ecdsa_args], [sign_with_ecdsa_result], []), + start_canister: IDL.Func([start_canister_args], [], []), + stop_canister: IDL.Func([stop_canister_args], [], []), + stored_chunks: IDL.Func([stored_chunks_args], [stored_chunks_result], []), + uninstall_code: IDL.Func([uninstall_code_args], [], []), + update_settings: IDL.Func([update_settings_args], [], []), + upload_chunk: IDL.Func([upload_chunk_args], [upload_chunk_result], []), }); }; diff --git a/packages/agent/src/canisters/management_service.ts b/packages/agent/src/canisters/management_service.ts index 8efe3b63e..8cedd28d3 100644 --- a/packages/agent/src/canisters/management_service.ts +++ b/packages/agent/src/canisters/management_service.ts @@ -10,15 +10,86 @@ import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; export type bitcoin_address = string; +export interface bitcoin_get_balance_args { + network: bitcoin_network; + address: bitcoin_address; + min_confirmations: [] | [number]; +} +export interface bitcoin_get_balance_query_args { + network: bitcoin_network; + address: bitcoin_address; + min_confirmations: [] | [number]; +} +export type bitcoin_get_balance_query_result = satoshi; +export type bitcoin_get_balance_result = satoshi; +export interface bitcoin_get_current_fee_percentiles_args { + network: bitcoin_network; +} +export type bitcoin_get_current_fee_percentiles_result = BigUint64Array | bigint[]; +export interface bitcoin_get_utxos_args { + network: bitcoin_network; + filter: [] | [{ page: Uint8Array | number[] } | { min_confirmations: number }]; + address: bitcoin_address; +} +export interface bitcoin_get_utxos_query_args { + network: bitcoin_network; + filter: [] | [{ page: Uint8Array | number[] } | { min_confirmations: number }]; + address: bitcoin_address; +} +export interface bitcoin_get_utxos_query_result { + next_page: [] | [Uint8Array | number[]]; + tip_height: number; + tip_block_hash: block_hash; + utxos: Array; +} +export interface bitcoin_get_utxos_result { + next_page: [] | [Uint8Array | number[]]; + tip_height: number; + tip_block_hash: block_hash; + utxos: Array; +} export type bitcoin_network = { mainnet: null } | { testnet: null }; +export interface bitcoin_send_transaction_args { + transaction: Uint8Array | number[]; + network: bitcoin_network; +} export type block_hash = Uint8Array | number[]; export type canister_id = Principal; +export interface canister_info_args { + canister_id: canister_id; + num_requested_changes: [] | [bigint]; +} +export interface canister_info_result { + controllers: Array; + module_hash: [] | [Uint8Array | number[]]; + recent_changes: Array; + total_num_changes: bigint; +} +export interface canister_log_record { + idx: bigint; + timestamp_nanos: bigint; + content: Uint8Array | number[]; +} export interface canister_settings { freezing_threshold: [] | [bigint]; controllers: [] | [Array]; + reserved_cycles_limit: [] | [bigint]; + log_visibility: [] | [log_visibility]; memory_allocation: [] | [bigint]; compute_allocation: [] | [bigint]; } +export interface canister_status_args { + canister_id: canister_id; +} +export interface canister_status_result { + status: { stopped: null } | { stopping: null } | { running: null }; + memory_size: bigint; + cycles: bigint; + settings: definite_canister_settings; + idle_cycles_burned_per_day: bigint; + module_hash: [] | [Uint8Array | number[]]; + reserved_cycles: bigint; +} export interface change { timestamp_nanos: bigint; canister_version: bigint; @@ -46,56 +117,151 @@ export type change_origin = }; }; export type chunk_hash = Uint8Array | number[]; +export interface clear_chunk_store_args { + canister_id: canister_id; +} +export interface create_canister_args { + settings: [] | [canister_settings]; + sender_canister_version: [] | [bigint]; +} +export interface create_canister_result { + canister_id: canister_id; +} export interface definite_canister_settings { freezing_threshold: bigint; controllers: Array; + reserved_cycles_limit: bigint; + log_visibility: log_visibility; memory_allocation: bigint; compute_allocation: bigint; } +export interface delete_canister_args { + canister_id: canister_id; +} +export interface deposit_cycles_args { + canister_id: canister_id; +} export type ecdsa_curve = { secp256k1: null }; -export interface get_balance_request { - network: bitcoin_network; - address: bitcoin_address; - min_confirmations: [] | [number]; +export interface ecdsa_public_key_args { + key_id: { name: string; curve: ecdsa_curve }; + canister_id: [] | [canister_id]; + derivation_path: Array; } -export interface get_current_fee_percentiles_request { - network: bitcoin_network; +export interface ecdsa_public_key_result { + public_key: Uint8Array | number[]; + chain_code: Uint8Array | number[]; } -export interface get_utxos_request { - network: bitcoin_network; - filter: [] | [{ page: Uint8Array | number[] } | { min_confirmations: number }]; - address: bitcoin_address; +export interface fetch_canister_logs_args { + canister_id: canister_id; } -export interface get_utxos_response { - next_page: [] | [Uint8Array | number[]]; - tip_height: number; - tip_block_hash: block_hash; - utxos: Array; +export interface fetch_canister_logs_result { + canister_log_records: Array; } export interface http_header { value: string; name: string; } -export interface http_response { +export interface http_request_args { + url: string; + method: { get: null } | { head: null } | { post: null }; + max_response_bytes: [] | [bigint]; + body: [] | [Uint8Array | number[]]; + transform: [] | [{ function: [Principal, string]; context: Uint8Array | number[] }]; + headers: Array; +} +export interface http_request_result { status: bigint; body: Uint8Array | number[]; headers: Array; } +export interface install_chunked_code_args { + arg: Uint8Array | number[]; + wasm_module_hash: Uint8Array | number[]; + mode: + | { reinstall: null } + | { upgrade: [] | [{ skip_pre_upgrade: [] | [boolean] }] } + | { install: null }; + chunk_hashes_list: Array; + target_canister: canister_id; + sender_canister_version: [] | [bigint]; + storage_canister: [] | [canister_id]; +} +export interface install_code_args { + arg: Uint8Array | number[]; + wasm_module: wasm_module; + mode: + | { reinstall: null } + | { upgrade: [] | [{ skip_pre_upgrade: [] | [boolean] }] } + | { install: null }; + canister_id: canister_id; + sender_canister_version: [] | [bigint]; +} +export type log_visibility = { controllers: null } | { public: null }; export type millisatoshi_per_byte = bigint; export interface node_metrics { num_block_failures_total: bigint; node_id: Principal; num_blocks_total: bigint; } +export interface node_metrics_history_args { + start_at_timestamp_nanos: bigint; + subnet_id: Principal; +} +export type node_metrics_history_result = Array<{ + timestamp_nanos: bigint; + node_metrics: Array; +}>; export interface outpoint { txid: Uint8Array | number[]; vout: number; } +export interface provisional_create_canister_with_cycles_args { + settings: [] | [canister_settings]; + specified_id: [] | [canister_id]; + amount: [] | [bigint]; + sender_canister_version: [] | [bigint]; +} +export interface provisional_create_canister_with_cycles_result { + canister_id: canister_id; +} +export interface provisional_top_up_canister_args { + canister_id: canister_id; + amount: bigint; +} +export type raw_rand_result = Uint8Array | number[]; export type satoshi = bigint; -export interface send_transaction_request { - transaction: Uint8Array | number[]; - network: bitcoin_network; +export interface sign_with_ecdsa_args { + key_id: { name: string; curve: ecdsa_curve }; + derivation_path: Array; + message_hash: Uint8Array | number[]; +} +export interface sign_with_ecdsa_result { + signature: Uint8Array | number[]; } +export interface start_canister_args { + canister_id: canister_id; +} +export interface stop_canister_args { + canister_id: canister_id; +} +export interface stored_chunks_args { + canister_id: canister_id; +} +export type stored_chunks_result = Array; +export interface uninstall_code_args { + canister_id: canister_id; + sender_canister_version: [] | [bigint]; +} +export interface update_settings_args { + canister_id: Principal; + settings: canister_settings; + sender_canister_version: [] | [bigint]; +} +export interface upload_chunk_args { + chunk: Uint8Array | number[]; + canister_id: Principal; +} +export type upload_chunk_result = chunk_hash; export interface utxo { height: number; value: satoshi; @@ -103,164 +269,46 @@ export interface utxo { } export type wasm_module = Uint8Array | number[]; export default interface _SERVICE { - bitcoin_get_balance: ActorMethod<[get_balance_request], satoshi>; - bitcoin_get_balance_query: ActorMethod<[get_balance_request], satoshi>; - bitcoin_get_current_fee_percentiles: ActorMethod< - [get_current_fee_percentiles_request], - BigUint64Array | bigint[] - >; - bitcoin_get_utxos: ActorMethod<[get_utxos_request], get_utxos_response>; - bitcoin_get_utxos_query: ActorMethod<[get_utxos_request], get_utxos_response>; - bitcoin_send_transaction: ActorMethod<[send_transaction_request], undefined>; - canister_info: ActorMethod< - [{ canister_id: canister_id; num_requested_changes: [] | [bigint] }], - { - controllers: Array; - module_hash: [] | [Uint8Array | number[]]; - recent_changes: Array; - total_num_changes: bigint; - } - >; - canister_status: ActorMethod< - [{ canister_id: canister_id }], - { - status: { stopped: null } | { stopping: null } | { running: null }; - memory_size: bigint; - cycles: bigint; - settings: definite_canister_settings; - idle_cycles_burned_per_day: bigint; - module_hash: [] | [Uint8Array | number[]]; - } + bitcoin_get_balance: ActorMethod<[bitcoin_get_balance_args], bitcoin_get_balance_result>; + bitcoin_get_balance_query: ActorMethod< + [bitcoin_get_balance_query_args], + bitcoin_get_balance_query_result >; - clear_chunk_store: ActorMethod<[{ canister_id: canister_id }], undefined>; - create_canister: ActorMethod< - [ - { - settings: [] | [canister_settings]; - sender_canister_version: [] | [bigint]; - }, - ], - { canister_id: canister_id } - >; - delete_canister: ActorMethod<[{ canister_id: canister_id }], undefined>; - deposit_cycles: ActorMethod<[{ canister_id: canister_id }], undefined>; - ecdsa_public_key: ActorMethod< - [ - { - key_id: { name: string; curve: ecdsa_curve }; - canister_id: [] | [canister_id]; - derivation_path: Array; - }, - ], - { - public_key: Uint8Array | number[]; - chain_code: Uint8Array | number[]; - } - >; - http_request: ActorMethod< - [ - { - url: string; - method: { get: null } | { head: null } | { post: null }; - max_response_bytes: [] | [bigint]; - body: [] | [Uint8Array | number[]]; - transform: - | [] - | [ - { - function: [Principal, string]; - context: Uint8Array | number[]; - }, - ]; - headers: Array; - }, - ], - http_response - >; - install_chunked_code: ActorMethod< - [ - { - arg: Uint8Array | number[]; - wasm_module_hash: Uint8Array | number[]; - mode: - | { reinstall: null } - | { upgrade: [] | [{ skip_pre_upgrade: [] | [boolean] }] } - | { install: null }; - chunk_hashes_list: Array; - target_canister: canister_id; - sender_canister_version: [] | [bigint]; - storage_canister: [] | [canister_id]; - }, - ], - undefined - >; - install_code: ActorMethod< - [ - { - arg: Uint8Array | number[]; - wasm_module: wasm_module; - mode: - | { reinstall: null } - | { upgrade: [] | [{ skip_pre_upgrade: [] | [boolean] }] } - | { install: null }; - canister_id: canister_id; - sender_canister_version: [] | [bigint]; - }, - ], - undefined + bitcoin_get_current_fee_percentiles: ActorMethod< + [bitcoin_get_current_fee_percentiles_args], + bitcoin_get_current_fee_percentiles_result >; - node_metrics_history: ActorMethod< - [{ start_at_timestamp_nanos: bigint; subnet_id: Principal }], - Array<{ timestamp_nanos: bigint; node_metrics: Array }> + bitcoin_get_utxos: ActorMethod<[bitcoin_get_utxos_args], bitcoin_get_utxos_result>; + bitcoin_get_utxos_query: ActorMethod< + [bitcoin_get_utxos_query_args], + bitcoin_get_utxos_query_result >; + bitcoin_send_transaction: ActorMethod<[bitcoin_send_transaction_args], undefined>; + canister_info: ActorMethod<[canister_info_args], canister_info_result>; + canister_status: ActorMethod<[canister_status_args], canister_status_result>; + clear_chunk_store: ActorMethod<[clear_chunk_store_args], undefined>; + create_canister: ActorMethod<[create_canister_args], create_canister_result>; + delete_canister: ActorMethod<[delete_canister_args], undefined>; + deposit_cycles: ActorMethod<[deposit_cycles_args], undefined>; + ecdsa_public_key: ActorMethod<[ecdsa_public_key_args], ecdsa_public_key_result>; + fetch_canister_logs: ActorMethod<[fetch_canister_logs_args], fetch_canister_logs_result>; + http_request: ActorMethod<[http_request_args], http_request_result>; + install_chunked_code: ActorMethod<[install_chunked_code_args], undefined>; + install_code: ActorMethod<[install_code_args], undefined>; + node_metrics_history: ActorMethod<[node_metrics_history_args], node_metrics_history_result>; provisional_create_canister_with_cycles: ActorMethod< - [ - { - settings: [] | [canister_settings]; - specified_id: [] | [canister_id]; - amount: [] | [bigint]; - sender_canister_version: [] | [bigint]; - }, - ], - { canister_id: canister_id } - >; - provisional_top_up_canister: ActorMethod< - [{ canister_id: canister_id; amount: bigint }], - undefined - >; - raw_rand: ActorMethod<[], Uint8Array | number[]>; - sign_with_ecdsa: ActorMethod< - [ - { - key_id: { name: string; curve: ecdsa_curve }; - derivation_path: Array; - message_hash: Uint8Array | number[]; - }, - ], - { signature: Uint8Array | number[] } - >; - start_canister: ActorMethod<[{ canister_id: canister_id }], undefined>; - stop_canister: ActorMethod<[{ canister_id: canister_id }], undefined>; - stored_chunks: ActorMethod<[{ canister_id: canister_id }], Array>; - uninstall_code: ActorMethod< - [ - { - canister_id: canister_id; - sender_canister_version: [] | [bigint]; - }, - ], - undefined - >; - update_settings: ActorMethod< - [ - { - canister_id: Principal; - settings: canister_settings; - sender_canister_version: [] | [bigint]; - }, - ], - undefined + [provisional_create_canister_with_cycles_args], + provisional_create_canister_with_cycles_result >; - upload_chunk: ActorMethod<[{ chunk: Uint8Array | number[]; canister_id: Principal }], chunk_hash>; + provisional_top_up_canister: ActorMethod<[provisional_top_up_canister_args], undefined>; + raw_rand: ActorMethod<[], raw_rand_result>; + sign_with_ecdsa: ActorMethod<[sign_with_ecdsa_args], sign_with_ecdsa_result>; + start_canister: ActorMethod<[start_canister_args], undefined>; + stop_canister: ActorMethod<[stop_canister_args], undefined>; + stored_chunks: ActorMethod<[stored_chunks_args], stored_chunks_result>; + uninstall_code: ActorMethod<[uninstall_code_args], undefined>; + update_settings: ActorMethod<[update_settings_args], undefined>; + upload_chunk: ActorMethod<[upload_chunk_args], upload_chunk_result>; } export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; From 3432dcd2108dc7d5098352f3ba87bc6fbd9e39d9 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 14:49:59 -0700 Subject: [PATCH 2/7] feat: support and tests for fetch_canister_logs --- e2e/node/basic/logging.test.ts | 31 +++++++++++++++ e2e/node/basic/mainnet.test.ts | 34 +--------------- e2e/node/canisters/logs.ts | 52 +++++++++++++++++++++++++ e2e/node/canisters/logs.wasm | Bin 0 -> 130136 bytes packages/agent/src/actor.ts | 6 ++- packages/agent/src/agent/api.ts | 5 +++ packages/agent/src/agent/http/index.ts | 37 ++++++++++++------ packages/agent/src/utils/buffer.ts | 1 + packages/candid/src/utils/buffer.ts | 17 ++++++-- 9 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 e2e/node/basic/logging.test.ts create mode 100644 e2e/node/canisters/logs.ts create mode 100644 e2e/node/canisters/logs.wasm diff --git a/e2e/node/basic/logging.test.ts b/e2e/node/basic/logging.test.ts new file mode 100644 index 000000000..2be54acbc --- /dev/null +++ b/e2e/node/basic/logging.test.ts @@ -0,0 +1,31 @@ +import { bufFromBufLike, getManagementCanister } from '@dfinity/agent'; +import { describe, it, expect } from 'vitest'; +import logsActor from '../canisters/logs'; +import { makeAgent } from '../utils/agent'; + +describe('canister logs', () => { + it('should make requests to the management canister', async () => { + const { canisterId } = await logsActor(); + + const management = await getManagementCanister({ agent: await makeAgent() }); + const logs = await management.fetch_canister_logs({ canister_id: canisterId }); + + expect(logs.canister_log_records.length).toBe(1); + const content = bufFromBufLike(logs.canister_log_records[0].content); + + expect(new TextDecoder().decode(content).trim()).toBe('Hello, first call!'); + }); + it('should show additional logs', async () => { + const { canisterId, actor } = await logsActor(); + + await actor.hello('second call'); + + const management = await getManagementCanister({ agent: await makeAgent() }); + const logs = await management.fetch_canister_logs({ canister_id: canisterId }); + + expect(logs.canister_log_records.length).toBe(2); + const content = bufFromBufLike(logs.canister_log_records[1].content); + + expect(new TextDecoder().decode(content).trim()).toBe('Hello, second call!'); + }); +}, 10_000); diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index 5a770c794..2a42b9a29 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -1,12 +1,4 @@ -import { - Actor, - AnonymousIdentity, - HttpAgent, - Identity, - CanisterStatus, - MANAGEMENT_CANISTER_ID, - getManagementCanister, -} from '@dfinity/agent'; +import { Actor, AnonymousIdentity, HttpAgent, Identity, CanisterStatus } from '@dfinity/agent'; import { IDL } from '@dfinity/candid'; import { Ed25519KeyIdentity } from '@dfinity/identity'; import { Principal } from '@dfinity/principal'; @@ -169,27 +161,3 @@ describe('controllers', () => { `); }); }); - -it.only('should make requests to the management canister', async () => { - const canisterLogs = async ({ - canisterId, - identity, - }: { - canisterId: Principal; - identity: Identity; - }) => { - const actor = getManagementCanister({ - agent: new HttpAgent({ host: 'https://icp-api.io', identity }), - }); - - return await actor.fetch_canister_logs({ - canister_id: canisterId, - }); - }; - - const logs = await canisterLogs({ - canisterId: Principal.from('rrkah-fqaaa-aaaaa-aaaaq-cai'), - identity: new AnonymousIdentity(), - }); - console.log(logs); -}); diff --git a/e2e/node/canisters/logs.ts b/e2e/node/canisters/logs.ts new file mode 100644 index 000000000..556a8ea23 --- /dev/null +++ b/e2e/node/canisters/logs.ts @@ -0,0 +1,52 @@ +import { Principal } from '@dfinity/principal'; +import agent from '../utils/agent'; +import { readFileSync } from 'fs'; +import path from 'path'; +import { Actor, ActorMethod, ActorSubclass } from '@dfinity/agent'; +import { IDL } from '@dfinity/candid'; + +export interface _SERVICE { + hello: ActorMethod<[string], undefined>; +} +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; + +export const idlFactory = ({ IDL }) => { + return IDL.Service({ hello: IDL.Func([IDL.Text], [], []) }); +}; + +let cache: { + canisterId: Principal; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actor: any; +} | null = null; + +/** + * Create a counter Actor + canisterId + */ +export default async function (): Promise<{ + canisterId: Principal; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actor: any; +}> { + if (!cache) { + const module = readFileSync(path.join(__dirname, 'logs.wasm')); + + const canisterId = await Actor.createCanister({ agent: await agent }); + await Actor.install({ module }, { canisterId, agent: await agent }); + + const actor = Actor.createActor(idlFactory, { + canisterId, + agent: await agent, + }) as ActorSubclass<_SERVICE>; + + await actor.hello('first call'); + + cache = { + canisterId, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actor, + }; + } + + return cache; +} diff --git a/e2e/node/canisters/logs.wasm b/e2e/node/canisters/logs.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f382d09a30bb933b873c9c2b08248de2b131182b GIT binary patch literal 130136 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 literal 0 HcmV?d00001 diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index bd76ec655..0926f3939 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -431,7 +431,11 @@ function _createActorMethod( const cid = Principal.from(options.canisterId || actor[metadataSymbol].config.canisterId); const arg = IDL.encode(func.argTypes, args); - const result = await agent.query(cid, { methodName, arg }); + const result = await agent.query(cid, { + methodName, + arg, + effectiveCanisterId: options.effectiveCanisterId, + }); switch (result.status) { case QueryResponseStatus.Rejected: diff --git a/packages/agent/src/agent/api.ts b/packages/agent/src/agent/api.ts index 7fdbba41e..ab46c29ac 100644 --- a/packages/agent/src/agent/api.ts +++ b/packages/agent/src/agent/api.ts @@ -88,6 +88,11 @@ export interface QueryFields { * A binary encoded argument. This is already encoded and will be sent as is. */ arg: ArrayBuffer; + + /** + * Overrides canister id for path to fetch. This is used for management canister calls. + */ + effectiveCanisterId?: Principal; } /** diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index 5ee8fbf60..f4bb30acd 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -398,6 +398,8 @@ export class HttpAgent implements Agent { const body = cbor.encode(transformedRequest.body); + this.log(`fetching "/api/v2/canister/${ecid.toText()}/call" with request:`, transformedRequest); + // Run both in parallel. The fetch is quite expensive, so we have plenty of time to // calculate the requestId locally. const request = this._requestAndRetry(() => @@ -429,19 +431,19 @@ export class HttpAgent implements Agent { async #requestAndRetryQuery( args: { - canister: string; + ecid: Principal; transformedRequest: HttpAgentRequest; body: ArrayBuffer; requestId: RequestId; }, tries = 0, ): Promise { - const { canister, transformedRequest, body, requestId } = args; + const { ecid, transformedRequest, body, requestId } = args; let response: ApiQueryResponse; // Make the request and retry if it throws an error try { const fetchResponse = await this._fetch( - '' + new URL(`/api/v2/canister/${canister}/query`, this._host), + '' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this._host), { ...this._fetchOptions, ...transformedRequest.request, @@ -487,8 +489,8 @@ export class HttpAgent implements Agent { const timestamp = response.signatures?.[0]?.timestamp; - // Skip watermark verification if the user has set verifyQuerySignatures to false or if the canister is the management canister - if (!this.#verifyQuerySignatures ?? canister === MANAGEMENT_CANISTER_ID) { + // Skip watermark verification if the user has set verifyQuerySignatures to false + if (!this.#verifyQuerySignatures) { return response; } @@ -569,7 +571,12 @@ export class HttpAgent implements Agent { fields: QueryFields, identity?: Identity | Promise, ): Promise { - this.log(`making query to canister ${canisterId} with fields:`, fields); + const ecid = fields.effectiveCanisterId + ? Principal.from(fields.effectiveCanisterId) + : Principal.from(canisterId); + + this.log(`ecid ${ecid.toString()}`); + this.log(`canisterId ${canisterId.toString()}`); const makeQuery = async () => { const id = await (identity !== undefined ? await identity : await this._identity); if (!id) { @@ -613,6 +620,7 @@ export class HttpAgent implements Agent { const args = { canister: canister.toText(), + ecid, transformedRequest, body, requestId, @@ -622,15 +630,15 @@ export class HttpAgent implements Agent { }; const getSubnetStatus = async (): Promise => { - if (!this.#verifyQuerySignatures || canisterId === MANAGEMENT_CANISTER_ID) { + if (!this.#verifyQuerySignatures) { return undefined; } - const subnetStatus = this.#subnetKeys.get(canisterId.toString()); + const subnetStatus = this.#subnetKeys.get(ecid.toString()); if (subnetStatus) { return subnetStatus; } - await this.fetchSubnetKeys(canisterId.toString()); - return this.#subnetKeys.get(canisterId.toString()); + await this.fetchSubnetKeys(ecid.toString()); + return this.#subnetKeys.get(ecid.toString()); }; // Attempt to make the query i=retryTimes times // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -639,7 +647,7 @@ export class HttpAgent implements Agent { this.log('Query response:', query); // Skip verification if the user has disabled it - if (!this.#verifyQuerySignatures || canisterId === MANAGEMENT_CANISTER_ID) { + if (!this.#verifyQuerySignatures) { return query; } @@ -649,7 +657,7 @@ export class HttpAgent implements Agent { // In case the node signatures have changed, refresh the subnet keys and try again this.log.warn('Query response verification failed. Retrying with fresh subnet keys.'); this.#subnetKeys.delete(canisterId.toString()); - await this.fetchSubnetKeys(canisterId.toString()); + await this.fetchSubnetKeys(ecid.toString()); const updatedSubnetStatus = this.#subnetKeys.get(canisterId.toString()); if (!updatedSubnetStatus) { @@ -783,6 +791,10 @@ export class HttpAgent implements Agent { const transformedRequest = request ?? (await this.createReadStateRequest(fields, identity)); const body = cbor.encode(transformedRequest.body); + this.log( + `fetching "/api/v2/canister/${canister}/read_state" with request:`, + transformedRequest, + ); // TODO - https://dfinity.atlassian.net/browse/SDK-1092 const response = await this._requestAndRetry(() => this._fetch('' + new URL(`/api/v2/canister/${canister}/read_state`, this._host), { @@ -874,6 +886,7 @@ export class HttpAgent implements Agent { } : {}; + this.log(`fetching "/api/v2/status"`); const response = await this._requestAndRetry(() => this._fetch('' + new URL(`/api/v2/status`, this._host), { headers, ...this._fetchOptions }), ); diff --git a/packages/agent/src/utils/buffer.ts b/packages/agent/src/utils/buffer.ts index e858b1c31..7dc08bdf6 100644 --- a/packages/agent/src/utils/buffer.ts +++ b/packages/agent/src/utils/buffer.ts @@ -93,6 +93,7 @@ export function bufFromBufLike( | ArrayBufferView | ArrayBufferLike | [number] + | number[] | { buffer: ArrayBuffer }, ): ArrayBuffer { if (bufLike instanceof Uint8Array) { diff --git a/packages/candid/src/utils/buffer.ts b/packages/candid/src/utils/buffer.ts index 5335ea947..34688b1f3 100644 --- a/packages/candid/src/utils/buffer.ts +++ b/packages/candid/src/utils/buffer.ts @@ -137,7 +137,15 @@ export function uint8ToBuf(arr: Uint8Array): ArrayBuffer { * @returns ArrayBuffer */ export function bufFromBufLike( - bufLike: ArrayBuffer | Uint8Array | DataView | ArrayBufferView | ArrayBufferLike, + bufLike: + | ArrayBuffer + | Uint8Array + | DataView + | ArrayBufferView + | ArrayBufferLike + | [number] + | number[] + | { buffer: ArrayBuffer }, ): ArrayBuffer { if (bufLike instanceof Uint8Array) { return uint8ToBuf(bufLike); @@ -145,8 +153,11 @@ export function bufFromBufLike( if (bufLike instanceof ArrayBuffer) { return bufLike; } + if (Array.isArray(bufLike)) { + return uint8ToBuf(new Uint8Array(bufLike)); + } if ('buffer' in bufLike) { - return bufLike.buffer; + return bufFromBufLike(bufLike.buffer); } - return new Uint8Array(bufLike); + return uint8ToBuf(new Uint8Array(bufLike)); } From ade34c67953461748eaba108926e0a51121637da Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 15:00:24 -0700 Subject: [PATCH 3/7] changelog --- docs/CHANGELOG.md | 6 ++++++ packages/agent/src/canisters/management.did | 1 + 2 files changed, 7 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d46cae2af..37218afd3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,10 +2,16 @@ ## [Unreleased] +### Added + +* feat: adds support for verified queries against management canister + * includes support for `fetch_canister_logs` in the actor provided by `getManagementCanister` + ### Changed * fix: pads date numbers in changelog automation. E.G. 2024-3-1 -> 2024-03-01 * feat: allow passing `DBCreateOptions` to `IdbStorage` constructor +* updated management canister interface ## [1.1.1] - 2024-03-19 diff --git a/packages/agent/src/canisters/management.did b/packages/agent/src/canisters/management.did index 14f4e5906..33db98ba6 100644 --- a/packages/agent/src/canisters/management.did +++ b/packages/agent/src/canisters/management.did @@ -1,3 +1,4 @@ +// https://github.com/dfinity/interface-spec/blob/master/spec/_attachments/ic.did type canister_id = principal; type wasm_module = blob; From 34d84254f8ed790bbf14b8940a544cd342666c30 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 15:17:39 -0700 Subject: [PATCH 4/7] test for bitcoin query --- e2e/node/basic/mainnet.test.ts | 27 ++++++++++++++++++++++++++- packages/agent/src/actor.ts | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index 2a42b9a29..b815e55ff 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -1,4 +1,11 @@ -import { Actor, AnonymousIdentity, HttpAgent, Identity, CanisterStatus } from '@dfinity/agent'; +import { + Actor, + AnonymousIdentity, + HttpAgent, + Identity, + CanisterStatus, + getManagementCanister, +} from '@dfinity/agent'; import { IDL } from '@dfinity/candid'; import { Ed25519KeyIdentity } from '@dfinity/identity'; import { Principal } from '@dfinity/principal'; @@ -161,3 +168,21 @@ describe('controllers', () => { `); }); }); + +describe('bitcoin query', async () => { + it('should return the balance of a bitcoin address', async () => { + const agent = await makeAgent({ host: 'https://icp-api.io' }); + const management = getManagementCanister({ + agent, + effectiveCanisterId: Principal.from('rrkah-fqaaa-aaaaa-aaaaq-cai'), + }); + + const result = await management.bitcoin_get_balance_query({ + address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + network: { mainnet: null }, + min_confirmations: [6], + }); + + expect(result).toBeGreaterThan(0n); + }); +}); diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 0926f3939..428343391 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -523,6 +523,9 @@ export function getManagementCanister(config: CallConfig): ActorSubclass & { canister_id: string }[], ) { + if (config.effectiveCanisterId) { + return { effectiveCanisterId: Principal.from(config.effectiveCanisterId) }; + } const first = args[0]; let effectiveCanisterId = Principal.fromHex(''); if (first && typeof first === 'object' && first.canister_id) { From 9db796bd7b8b6958f8daaa81a1c3bbec8c44a43a Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 15:29:15 -0700 Subject: [PATCH 5/7] bitcoin query tests --- docs/CHANGELOG.md | 16 ++++++++++++++++ e2e/node/basic/mainnet.test.ts | 7 ++++--- e2e/node/utils/agent.ts | 2 -- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 37218afd3..6f90f2555 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,22 @@ * feat: adds support for verified queries against management canister * includes support for `fetch_canister_logs` in the actor provided by `getManagementCanister` + * allows you to pass an `effectiveCanisterId` to the `getManagementCanister` api, which makes it possible to query bitcoin methods as well. For example: + +```ts +// For now, the verifyQuerySignatures option must be set to false +const agent = await makeAgent({ host: 'https://icp-api.io', verifyQuerySignatures: false }); +const management = getManagementCanister({ + agent, + effectiveCanisterId: Principal.from('mm444-5iaaa-aaaar-qaabq-cai'), +}); + +const result = await management.bitcoin_get_balance_query({ + address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + network: { mainnet: null }, + min_confirmations: [6], +}); +``` ### Changed diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index b815e55ff..b4cf6e22b 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -171,10 +171,11 @@ describe('controllers', () => { describe('bitcoin query', async () => { it('should return the balance of a bitcoin address', async () => { - const agent = await makeAgent({ host: 'https://icp-api.io' }); + // TODO - verify node signature for bitcoin once supported + const agent = await makeAgent({ host: 'https://icp-api.io', verifyQuerySignatures: false }); const management = getManagementCanister({ agent, - effectiveCanisterId: Principal.from('rrkah-fqaaa-aaaaa-aaaaq-cai'), + effectiveCanisterId: Principal.from('mm444-5iaaa-aaaar-qaabq-cai'), }); const result = await management.bitcoin_get_balance_query({ @@ -182,7 +183,7 @@ describe('bitcoin query', async () => { network: { mainnet: null }, min_confirmations: [6], }); - + console.log(`balance for address: ${result}`); expect(result).toBeGreaterThan(0n); }); }); diff --git a/e2e/node/utils/agent.ts b/e2e/node/utils/agent.ts index 718bab5d9..4eef883ae 100644 --- a/e2e/node/utils/agent.ts +++ b/e2e/node/utils/agent.ts @@ -12,8 +12,6 @@ if (Number.isNaN(port)) { export const makeAgent = async (options?: HttpAgentOptions) => { const agent = new HttpAgent({ host: `http://127.0.0.1:${process.env.REPLICA_PORT ?? 4943}`, - // TODO - remove this when the dfx replica supports it - verifyQuerySignatures: false, ...options, }); try { From bc561fd1f1b055d141bb632e462ac4139b3c1de0 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 15:35:38 -0700 Subject: [PATCH 6/7] removing misleading claim about effectiveCanisterId --- docs/CHANGELOG.md | 15 ++++++++++++--- e2e/node/basic/mainnet.test.ts | 1 - 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6f90f2555..5f2b012df 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,14 +6,23 @@ * feat: adds support for verified queries against management canister * includes support for `fetch_canister_logs` in the actor provided by `getManagementCanister` - * allows you to pass an `effectiveCanisterId` to the `getManagementCanister` api, which makes it possible to query bitcoin methods as well. For example: + * also includes support for bitcoin queries + +Logging + +```ts +// Agent should not use an anonymous identity for this call, and should ideally be a canister controller +const management = await getManagementCanister({ agent }); +const logs = await management.fetch_canister_logs({ canister_id: canisterId }); +``` + +Bitcoin ```ts // For now, the verifyQuerySignatures option must be set to false const agent = await makeAgent({ host: 'https://icp-api.io', verifyQuerySignatures: false }); const management = getManagementCanister({ - agent, - effectiveCanisterId: Principal.from('mm444-5iaaa-aaaar-qaabq-cai'), + agent }); const result = await management.bitcoin_get_balance_query({ diff --git a/e2e/node/basic/mainnet.test.ts b/e2e/node/basic/mainnet.test.ts index b4cf6e22b..0c4d7bc4f 100644 --- a/e2e/node/basic/mainnet.test.ts +++ b/e2e/node/basic/mainnet.test.ts @@ -175,7 +175,6 @@ describe('bitcoin query', async () => { const agent = await makeAgent({ host: 'https://icp-api.io', verifyQuerySignatures: false }); const management = getManagementCanister({ agent, - effectiveCanisterId: Principal.from('mm444-5iaaa-aaaar-qaabq-cai'), }); const result = await management.bitcoin_get_balance_query({ From 717b5c5d9c28bdae1138ac6b7efc1f8cb8b01362 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Mar 2024 15:42:03 -0700 Subject: [PATCH 7/7] disabling signature verification for mitm base case --- e2e/node/basic/mitm.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/node/basic/mitm.test.ts b/e2e/node/basic/mitm.test.ts index a1171e00f..16de92f3a 100644 --- a/e2e/node/basic/mitm.test.ts +++ b/e2e/node/basic/mitm.test.ts @@ -12,6 +12,7 @@ mitmTest( const counter = await createActor('tnnnb-2yaaa-aaaab-qaiiq-cai', { agent: await makeAgent({ host: 'http://127.0.0.1:8888', + verifyQuerySignatures: false, }), }); await expect(counter.greet('counter')).rejects.toThrow(/Invalid certificate/);