Skip to content

Latest commit

 

History

History
84 lines (48 loc) · 4.99 KB

README.md

File metadata and controls

84 lines (48 loc) · 4.99 KB

What is this?

This starter is a collection of helpful patterns, tooling and best practices for developing canisters in Rust. You can read more below.

Preqreuisites

  • dfx 0.24.3
  • node 22.12.0
  • npm 10.9.0
  • pocket-ic binary present in /usr/local/bin/pocket-ic (make sure it's executable)

Lifecycle

The canister's lifecycle methods are managed in src/backend/src/lifecycle.rs. The init method in main.rs is called when the canister is created, and the post_upgrade method in main.rs is called after the canister is upgraded. Currently they both share the same implementation, that is when upgrading the canister you have to provide the same arguments as when creating the canister.

State

The application has a state that is stored in the canister. The state is a struct that is defined in src/backend/src/state.rs. The state state is lost when the canister is being upgraded. For data that should be persisted, use refer to the Storage section.

Storage

Data that is supposed to survive canister upgrades is defined in src/backend/src/storage.rs. The data is stored in the canister's stable memory and is not lost when the canister is being upgraded. This is faciliated by using the ic-stable-structures crate released by DFINITY.

Logging

There are two levels of logging in the application:

  • INFO: General information about the application
  • DEBUG: Detailed information about the application

To emit a log message, use the following syntax:

log!(INFO, "Received a request to fetch logs");
log!(DEBUG, "Received a request to fetch logs");

Logs in query calls are not persisted.

You can access canister logs via http requests to the canister's /logs endpoint. It accepts the following query parameters:

  • priority: The log level to filter by. Possible values are info and debug.
  • time: Accepts a timestamp in nanoseconds. Only logs after this timestamp will be returned.
  • sort: The order in which logs are returned. Possible values are asc and desc. Default is asc when time is not provided, and desc otherwise.

Note that logs are not persisted and are lost when the canister is being upgraded. You also can't log when the execution traps, as by design the state changes are rolled back. For this you can rely on the canister logging feature provided by the protocol by simply using println! exposed by the ic_cdk, note that here you only have one level of logging and 4KB of log storage. Read more here and in the example here.

Metrics

There are different metrics exposed via http requests to the canister's /metrics endpoint. You can modify them in src/backend/src/metrics.rs

Dashboard

The application has a dashboard that can be accessed via http requests to the canisters the /dashboard endpoint. It currently only exposes the way the user is greeted when calling greet. You can modify the askama dashboard template in src/backend/dashboard.rs and the corresponding HTML in src/backend/templates/dashboard.html.

Persisted Event Log

We use a StableLog to persist all greeting in stable memory. Usually this is used to store state changing events that should survive canister upgrades. They can be used to restore the canisters state that lives on the heap after an upgrade. You can learn more about the reasoning for this approach here. It can also be used as an audit trail for the canister. In our case we just replay the event logs in the post_upgrade to restore a hashmap that keeps the count of greetings per name greeted.

Canbench

canbench is a tool for benchmarking canisters on the Internet Computer. The config can be found in canbench.yml.

To encode the deploy arguments in candid use the didc tool:

didc encode '(variant { InitArg = record { greeting = "moin" } })' -d src/backend/backend.did -t '(Arg)'

Edit rust-analyzer.cargo.features in your .vscode/settings.json to enable the canbench feature for the rust-analyzer so canbench blocks are analyzed.

Run canbench --persist to persist the current benchmark results, then canbench after code changes to compare the current benchmark results with the persisted ones.

Tests

There are some example integration tests leveraging pocket-ic in src/backend/tests. You can run them with cargo test.

Cuzz

Simple automatic fuzz testing for Internet Computer Protocol (ICP) canisters, configuration is defined in cuzz.json. Read more here.

Candid Interface

The canisters candid file is generated by leveraging the ic_cdk::export_candid!() macro and candid-extractor. You can read more about this approach here.