Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

feat: [IC-272] canister logging #263

Merged
merged 45 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a0e70ea
add fetch_logs to ic.did
maksymar Dec 14, 2023
8766c2e
add log_visibility and methods description
maksymar Dec 14, 2023
4cb532e
add canister logs section
maksymar Dec 14, 2023
787c983
timestamp_nanos
maksymar Dec 15, 2023
409b3b1
typo
maksymar Dec 15, 2023
61387a5
idx nat64
maksymar Dec 15, 2023
45c48a1
canister_id : canister_id
maksymar Dec 15, 2023
a3f3445
describe log record
maksymar Dec 15, 2023
952172e
fetch_logs description update
maksymar Jan 8, 2024
2152140
drop update method, keep query only, rename
maksymar Jan 8, 2024
07543ad
drop update method, keep query only, rename : ic.did
maksymar Jan 8, 2024
c0d806a
replica-signed comment
maksymar Jan 8, 2024
6dcea7f
remove no user calls restriction
maksymar Jan 8, 2024
ebd854f
add abstract state definition
maksymar Jan 8, 2024
718375f
formatting
maksymar Jan 8, 2024
3e1442b
update fetch_logs description
mraszyk Jan 8, 2024
b505b4c
add canister_logs to abstract IC state
mraszyk Jan 8, 2024
3ebece2
refine
mraszyk Jan 8, 2024
492c4e8
refine
mraszyk Jan 8, 2024
b7a2b7e
fix
mraszyk Jan 8, 2024
98128f3
record logs in install_code
mraszyk Jan 9, 2024
6c0a7a9
content-s
maksymar Jan 9, 2024
e7e4cfb
Merge branch 'maksym/ic-272-logging' of github.com:dfinity/interface-…
maksymar Jan 9, 2024
406aea8
content-s
maksymar Jan 9, 2024
3bec1d8
only users can call, no canisters, no composite queries
maksymar Jan 9, 2024
02f1215
default log_visibility = controllers
maksymar Jan 9, 2024
95d44d2
fix abstract state canister_log_visibility
maksymar Jan 9, 2024
f98608b
log visibility controlers by default
maksymar Jan 9, 2024
3f8b503
log visibility order: controllers, public
maksymar Jan 9, 2024
26a3ebd
log_visibility enum, not a list
maksymar Jan 9, 2024
07ec1dd
Merge branch 'master' into maksym/ic-272-logging
mraszyk Jan 10, 2024
40da319
rename to fetch_canister_logs
maksymar Jan 12, 2024
85eb05b
create canister with New_canister_log_visibility
maksymar Jan 15, 2024
8317cba
wording
maksymar Jan 15, 2024
6d5cd24
fix canister_log_visibility condition
maksymar Jan 15, 2024
8f1623d
define New_canister_log_visibility
maksymar Jan 15, 2024
de1a5f1
fix using New_canister_log_visibility
maksymar Jan 15, 2024
c1b3d8f
Merge branch 'master' into maksym/ic-272-logging
mraszyk Mar 7, 2024
6673903
post-merge fixes
mraszyk Mar 7, 2024
ec562ba
wording
maksymar Mar 12, 2024
554c438
add experimental note
mraszyk Mar 26, 2024
3a503ef
Merge branch 'master' into maksym/ic-272-logging
mraszyk May 17, 2024
08233b0
abstract canister log size in its limits
mraszyk May 23, 2024
d1bee8f
update changelog
mraszyk May 23, 2024
4c6f942
Merge branch 'master' into maksym/ic-272-logging
mraszyk May 23, 2024
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
24 changes: 24 additions & 0 deletions spec/_attachments/ic.did
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
type canister_id = principal;
type wasm_module = blob;

type log_visibility = variant {
maksymar marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand All @@ -15,6 +21,7 @@ type definite_canister_settings = record {
memory_allocation : nat;
freezing_threshold : nat;
reserved_cycles_limit : nat;
log_visibility : log_visibility;
};

type change_origin = variant {
Expand Down Expand Up @@ -324,6 +331,20 @@ 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) -> ();
Expand Down Expand Up @@ -360,4 +381,7 @@ service ic : {
// 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;
};
145 changes: 144 additions & 1 deletion spec/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,15 @@ It must be contained in the canister ranges of a subnet, otherwise the correspon

- Otherwise, the call is rejected by the system independently of the effective canister id.

- If the request is an update call to a canister that is not the Management Canister (`aaaaa-aa`) or if the request is a query call, then the effective canister id must be the `canister_id` in the request.
- If the request is a query call to the Management Canister (`aaaaa-aa`), then:

- If the call is to the `bitcoin_get_balance_query` or `bitcoin_get_utxos_query` method, then the effective canister id for this call must be the Management Canister (`aaaaa-aa`).
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

- Otherwise, if the `arg` is a Candid-encoded record with a `canister_id` field of type `principal`, then the effective canister id must be that principal.

- Otherwise, the call is rejected by the system independently of the effective canister id.

- If the request is an update or query call to a canister that is not the Management Canister (`aaaaa-aa`), then the effective canister id must be the `canister_id` in the request.

:::note

Expand Down Expand Up @@ -2077,6 +2085,8 @@ Indicates various information about the canister. It contains:

- The reserved cycles limit of the canister, i.e., the maximum number of cycles that can be in the canister's reserved balance after increasing the canister's memory allocation and/or actual memory usage.

- The canister log visibility of the canister.

- A SHA256 hash of the module installed on the canister. This is `null` if the canister is empty.

- The actual memory usage of the canister.
Expand Down Expand Up @@ -2374,6 +2384,35 @@ This function returns fee percentiles, measured in millisatoshi/vbyte (1000 mill

The [standard nearest-rank estimation method](https://en.wikipedia.org/wiki/Percentile#The_nearest-rank_method), inclusive, with the addition of a 0th percentile is used. Concretely, for any i from 1 to 100, the ith percentile is the fee with rank `⌈i * 100⌉`. The 0th percentile is defined as the smallest fee (excluding coinbase transactions).

### IC method `fetch_canister_logs` {#ic-fetch_canister_logs}

Given a canister ID as input, this method returns a vector of logs of that canister including its trap messages.
The total length of all log contents does not exceed 4KiB.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
If new logs are added resulting in exceeding the maximum total log length of 4KiB, the oldest logs will be removed.
Logs persist across canister upgrades and they are deleted if the canister is reinstalled or uninstalled.
The log visibility is defined in the `log_visibility` field of `canister_settings`: logs can be either public (visible to everyone) or only visible to the canister's controllers (by default).

A single log is a record with the following fields:

- `idx` (`nat64`): the unique sequence number of the log for this particular canister;
- `timestamp_nanos` (`nat64`): the timestamp as nanoseconds since 1970-01-01 at which the log was recorded;
- `content` (`blob`): the actual content of the log;

:::note

This method is exposed as a query and is only accessible in non-replicated mode.
maksymar marked this conversation as resolved.
Show resolved Hide resolved
Calls in replicated mode are rejected.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
Only users can call this method, not canisters (also no nested composite query calls).

:::

:::warning

The response of a query comes from a single replica, and is therefore not appropriate for security-sensitive applications.
maksymar marked this conversation as resolved.
Show resolved Hide resolved
Replica-signed queries may improve security because the recipient can verify the response comes from the correct subnet.

:::

## Certification {#certification}

Some parts of the IC state are exposed to users in a tamperproof way via certification: the IC can reveal a *partial state tree* which includes just the data of interest, together with a signature on the root hash of the state tree. This means that a user can be sure that the response is correct, even if the user happens to be communicating with a malicious node, or has received the certificate via some other untrusted way.
Expand Down Expand Up @@ -2986,6 +3025,14 @@ Finally, we can describe the state of the IC as a record having the following fi
total_num_changes : Nat;
recent_changes : [Change];
}
CanisterLogVisibility
= Controllers
| Public
CanisterLog = {
idx : Nat;
timestamp_nanos : Nat;
content : Blob;
}
Subnet = {
subnet_id : Principal;
subnet_size : Nat;
Expand All @@ -3007,6 +3054,8 @@ Finally, we can describe the state of the IC as a record having the following fi
reserved_balance_limits: CanisterId ↦ Nat;
certified_data: CanisterId ↦ Blob;
canister_history: CanisterId ↦ CanisterHistory;
canister_log_visibility: CanisterId ↦ CanisterLogVisibility;
canister_logs: CanisterId ↦ [CanisterLog];
system_time : Timestamp
call_contexts : CallId ↦ CallCtxt;
messages : List Message; // ordered!
Expand Down Expand Up @@ -3075,6 +3124,8 @@ The initial state of the IC is
reserved_balance_limits = ();
certified_data = ();
canister_history = ();
canister_log_visibility = ();
canister_logs = ();
system_time = T;
call_contexts = ();
messages = [];
Expand Down Expand Up @@ -3686,6 +3737,8 @@ then

balances[M.receiver] = New_balance
reserved_balances[M.receiver] = New_reserved_balance

canister_logs[M.receiver] = S.canister_logs[M.receiver] · canister_logs
else
S with
messages = Older_messages · Younger_messages
Expand All @@ -3699,6 +3752,8 @@ Depending on whether this is a call message and a response messages, we have eit

The cycle consumption of executing this message is modeled via the unspecified `cycles_used` variable; the variable takes some value between 0 and `MAX_CYCLES_PER_MESSAGE`/`MAX_CYCLES_PER_RESPONSE` (for call execution and response execution, respectively).

The logs produced by the canister during message execution are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[M.receiver]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

This transition detects certain behavior that will appear as a trap (and which an implementation may implement by trapping directly in a system call):

- Responding if the present call context does not need to be responded to
Expand Down Expand Up @@ -3881,6 +3936,11 @@ New_canister_history = {
}
}

if A.settings.log_visibility is not null:
New_canister_log_visibility = A.settings.log_visibility
else:
New_canister_log_visibility = Controllers

```

State after
Expand All @@ -3901,6 +3961,8 @@ S with
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
certified_data[Canister_id] = ""
canister_history[Canister_id] = New_canister_history
canister_log_visibility[Canister_id] = New_canister_log_visibility
canister_logs[Canister_id] = []
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -4024,6 +4086,8 @@ S with
reserved_balances[A.canister_id] = New_reserved_balance
reserved_balance_limits[A.canister_id] = New_reserved_balance_limit
canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1
if A.settings.log_visibility is not null:
canister_log_visibility[A.canister_id] = A.settings.log_visibility
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -4342,6 +4406,7 @@ S with
balances[A.canister_id] = New_balance
reserved_balances[A.canister_id] = New_reserved_balance
canister_history[A.canister_id] = New_canister_history
canister_logs[A.canister_id] = canister_logs
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin;
Expand All @@ -4351,6 +4416,8 @@ S with

```

The logs produced by the canister during the execution of the WebAssembly `start` and `canister_init` functions are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps.

#### IC Management Canister: Code upgrade

Only the controllers of the given canister can install new code. This changes the code of an *existing* canister, preserving the state in the stable memory. This involves invoking the `canister_pre_upgrade` method, if the `skip_pre_upgrade` flag is not set to `opt true`, on the old and `canister_post_upgrade` method on the new canister, which must succeed and must not invoke other methods.
Expand Down Expand Up @@ -4494,6 +4561,7 @@ S with
balances[A.canister_id] = New_balance;
reserved_balances[A.canister_id] = New_reserved_balance;
canister_history[A.canister_id] = New_canister_history
canister_logs[A.canister_id] = S.canister_logs[A.canister_id] · canister_logs
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin;
Expand All @@ -4503,6 +4571,8 @@ S with

```

The logs produced by the canister during the execution of the WebAssembly `canister_pre_upgrade`, `start`, and `canister_post_upgrade` functions are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps.

#### IC Management Canister: Install chunked code

Conditions
Expand Down Expand Up @@ -4577,6 +4647,7 @@ S with
details = CodeUninstall;
};
}
canister_logs[A.canister_id] = []
canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1
global_timer[A.canister_id] = 0

Expand Down Expand Up @@ -4860,6 +4931,8 @@ S with
reserved_balance_limits[A.canister_id] = (deleted)
certified_data[A.canister_id] = (deleted)
canister_history[A.canister_id] = (deleted)
canister_log_visibility[A.canister_id] = (deleted)
canister_logs[A.canister_id] = (deleted)
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -5046,6 +5119,11 @@ New_canister_history {
}
}

if A.settings.log_visibility is not null:
New_canister_log_visibility = A.settings.log_visibility
else:
New_canister_log_visibility = Controllers

```

State after
Expand All @@ -5065,6 +5143,8 @@ S with
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
certified_data[Canister_id] = ""
canister_history[Canister_id] = New_canister_history
canister_log_visibility[Canister_id] = New_canister_log_visibility
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
canister_logs[Canister_id] = []
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -5267,6 +5347,7 @@ S with
total_num_changes = N;
recent_changes = [];
}
canister_logs[CanisterId] = []
canister_version[CanisterId] = S.canister_version[CanisterId] + 1
global_timer[CanisterId] = 0

Expand Down Expand Up @@ -5419,8 +5500,70 @@ S with

```

#### Trimming canister logs

Canister logs can be trimmed if the total length of their contents exceeds 4KiB.

Conditions

```html

S.canister_logs[CanisterId] = Older_logs · Newer_logs
SUM { |l.content| | l <- Older_logs } > 4KiB
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

```

State after

```html

S with
canister_logs[CanisterId] = Newer_logs

```

#### IC Management Canister: Canister logs (query call) {#ic-mgmt-canister-fetch-canister-logs}

This section specifies management canister query calls.
They are calls to `/api/v2/canister/<effective_canister_id>/query`
with CBOR body `Q` such that `Q.canister_id = ic_principal`.

The management canister offers the method `fetch_canister_logs`
that can be called as a query call and
returns logs of a requested canister.

Conditions

```html

Q.canister_id = ic_principal
Q.method_name = 'fetch_canister_logs'
Q.arg = candid(A)
A.canister_id = effective_canister_id
S[A.canister_id].canister_log_visibility = Public or Q.sender in S[A.canister_id].controllers

```

Query response `R`:

```html

{status: "replied"; reply: {arg: candid(S.canister_logs[A.canister_id])}, signatures: Sigs}
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

```

where the query `Q`, the response `R`, and a certificate `Cert'` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister/<effective_canister_id>/read_state` satisfy the following:

```html

verify_response(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough"

```

#### Query call {#query-call}

This section specifies query calls `Q` whose `Q.canister_id` is a non-empty canister `S.canisters[Q.canister_id]`. Query calls to the management canister, i.e., `Q.canister_id = ic_principal`, are specified in Section [Canister logs](#ic-mgmt-canister-fetch-canister-logs).

Canister query calls to `/api/v2/canister/<ECID>/query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen.

In query and composite query methods evaluated on the target canister of the query call, a certificate is provided to the canister that is valid, contains a current state tree (or "recent enough"; the specification is currently vague about how old the certificate may be), and reveals the canister's [Certified Data](#system-api-certified-data).
Expand Down
Loading