Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
dprats committed Jul 19, 2023
2 parents 69b26d7 + cffd7d2 commit 3804eea
Show file tree
Hide file tree
Showing 106 changed files with 5,300 additions and 2,467 deletions.
2 changes: 1 addition & 1 deletion docs/developer-docs/backend/motoko/candid-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Based on the type signature of the actor, Candid also provides a web interface t

After you have deployed your project in the local canister execution environment using the `dfx deploy` or `dfx canister install` command, you can access the Candid web interface endpoint in a browser.

This web interface, the Candid UI, exposes the service description in a form, enabling you to quickly view and test functions and experiment with entering different data types without writing any front-end code.
This web interface, the Candid UI, exposes the service description in a form, enabling you to quickly view and test functions and experiment with entering different data types without writing any front-end code.

To use the Candid web interface to test canister functions, first find the Candid UI canister identifier associated with the current project by running the command:

Expand Down
2 changes: 1 addition & 1 deletion docs/developer-docs/backend/periodic-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ If there are no await points in the timer handler, the periodic timer will be re

### Tutorials and examples using Rust

- **Backend tutorial:** [Using timers](rust/timers.md).
- **Backend tutorial:** [Using timers](rust/10-timers.md).
- **Example:** [Periodic tasks and timers](https://github.com/dfinity/examples/tree/master/rust/periodic_tasks) (compares the costs of timers and heartbeats).
115 changes: 115 additions & 0 deletions docs/developer-docs/backend/rust/1-infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# 1: Rust backend canister infrastructure

## Overview

When developing on the IC, there are currently two primary languages to build backend canisters with; Motoko and Rust. This guide provides an introduction to using Rust to developer backend canisters and covers the basic infrastructure of Rust canisters, as well as design considerations and observability.

## Rust CDK

To support Rust development, the IC SDK includes the [Rust canister development kit (Rust CDK)](https://github.com/dfinity/cdk-rs).

While using the IC SDK is the typical path for most developers, experienced Rust developers may choose to circumvent IC SDK entirely and use the Rust CDK directly. This documentation assumes one is using the IC SDK to build Rust canisters.

The Rust CDK consists of the following crates:
- The core of Rust CDK is the `ic-cdk` crate. It provides the core methods that enable Rust programs to interact with the Internet Computer blockchain system API.
- Also, the `ic-cdk-timers` crate provides an API to schedule multiple and periodic tasks.

## Canister builds
When building a backend canister, it's important to keep two things in mind:

1. Making your build reproducible: if other developers or users are utilizing your canister, they may want to verify that the canister is functioning as they expect it to (especially if your canister deals with transferring their tokens). The IC provides the ability for anyone to inspect the SHA256 hash sum of a canister's WebAssembly module to confirm that the hash of the canister matches the hash of a validated, known good canister, allowing for users to determine if a canister's contents have been edited or changed.

2. Planning for canister upgrades: typically, developers can manage without needing upgrades during the canister's initial development cycle. However, losing the canister's state on each deployment of the canister can be inconvenient. Once a canister has been deployed to the mainnet, the only way for new versions of the canister's code to be shipped is through planned upgrades.

### Making canister builds reproducible

To create a reproducible canister build, there are two popular workflows: Linux containers like Docker and Nix. Container technologies such as Docker are more popular, provide more resources and may be easier to set up. In comparison, Nix builds tend to be more widely reproducible. Either workflow can be used. Typically, building your canister using a public continuous integration system (CI) can help provide easy to follow instructions for reproducing your final project.

It is the canister developer’s responsibility to provide a reproducible way of building a WebAssembly module from the published sources. If your code is still within development, it can help to provide users or other developers with module hashes that correlate to each released version of the project's source code.

For more information on reproducible canister builds, check out [here](../reproducible-builds.md)

## Observability
Metrics can be used to gain insight into a wide range of information regarding your canister's production services. This data is important to learn about your canister's statistics and productivity.

### Exposing canister metrics

#### Approach 1: Expose a query call that returns a data structure containing your canister's metrics.
If this data is not intended to be public, this query can be configured to be rejected based on the caller's principal. This approach provides an response that is structured and easy to parse.

```rust
pub struct MyMetrics {
pub stable_memory_size: u32,
pub allocated_bytes: u32,
pub my_user_map_size: u64,
pub last_upgraded_ts: u64,
}
#[query]
fn metrics() -> MyMetrics {
check_acl();
MyMetrics {
// ...
}
}
```

#### Approach 2: Expose the canister's metrics in a format that your monitoring system can ingest through the canister's HTTP gateway.

For text-based exposition formats, the following example can be used:

```rust
fn http_request(req: HttpRequest) -> HttpResponse {
match path(&req) {
"/metrics" => HttpResponse {
status_code: 200,
body: format!("\
stable_memory_bytes {}
allocated_bytes {}
registered_users_total {}",
stable_memory_bytes, allocated_bytes, num_users),
// ...
}
}
}
```

#### Important metric data to watch
- The size of the canister's stable memory.
- The size of the canister's internal data structures
- The sizes of objects allocated within the heap.
- The date and time the canister was last upgraded.

## Globally mutable states

By design, canisters on the IC are structured in a way that forces developers to use a global mutable state. However, Rust's design makes it difficult to global mutable variables. This results in Rust developers needing to choose a method of code organization that takes the IC's design into consideration. This guide will cover a few of those code organization options.

### Using `thread_local!` with `Cell/RefCell` for state variables

Using `thread_local!` with `Cell/RefCell` is the safest option to avoid issues with asynchrony and memory corruption.

The following is an example of how `thread_local!` can be used:

```
thread_local! {
static NEXT_USER_ID: Cell<u64> = Cell::new(0);
static ACTIVE_USERS: RefCell<UserMap> = RefCell::new(UserMap::new());
}
```

### Canister code should be target-independent

It pays off to factor most of the canister code into loosely coupled modules and packages and to test them independently. Most of the code that depends on the System API should go into the main file.

It is also possible to create a thin abstraction for the System API and test your code with a fake but faithful implementation. For example, we could use the following trait to abstract the stable memory API:

```
pub trait Memory {
fn size(&self) -> WasmPages;
fn grow(&self, pages: WasmPages) -> WasmPages;
fn read(&self, offset: u32, dst: &mut [u8]);
fn write(&self, offset: u32, src: &[u8]);
}
```

## Next steps
Now that you've learned about the infrastructure of Rust backend canisters on the Internet Computer, the next step is to learn about [project organization](./2-project-organization.md).
239 changes: 239 additions & 0 deletions docs/developer-docs/backend/rust/10-timers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# 10: Using periodic timers

## Overview
This guide demonstrates how to create a small dapp with a periodic task. The task is triggered automatically by the Internet Computer with a specified interval.

## Prerequisites

Before getting started, assure you have set up your developer environment according to the instructions in the [developer environment guide](./3-dev-env.md).

## Create the timer dapp project

Open a terminal window on your local computer, if you don’t already have one open.

First, create a new Internet Computer project called `my_timers`:

```sh
dfx new --type=rust my_timers --no-frontend
```

Then, navigate into your project directory by running the command:

```sh
cd my_timers
```

:::info
Note: the following steps assume the terminal is still open and the current directory is `my_timers`.
:::

## Writing the Cargo.toml file

First, open the `src/my_timers_backend/Cargo.toml` file in a code editor. Replace the existing contents with the following:


```toml
[package]
name = "my_timers_backend"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
candid = "0.8.2"
ic-cdk = "0.7.0"
ic-cdk-timers = "0.1"
serde = { version = "1.0", features = ["derive"] }
```

Save the file.

## Update the Cargo.toml dependencies

Since we made changes to the Cargo.toml file, run the following command to update the project's dependencies:

```
cargo update
```

## Declaring the canister interface

Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer. Candid files provide a language-independent description of canister interfaces.

To see details about the Candid interface description language syntax, see the [Candid guide](../candid/) or the [Candid crate documentation](https://docs.rs/candid/).

Start by opening the `src/my_timers_backend/my_timers_backend.did` file in a code editor and replace its content with the following:

```candid
service : (nat64) -> {
"counter": () -> (nat64) query;
}
```

This code establishes the following:

- `service : (nat64) -> {...}` declares a new service which accepts a single integer argument.
- `"counter": () -> (nat64) query` declares a canister query entry point named `counter`. The `counter` query takes no arguments `()` and returns an integer `(nat64)`.

Save the file.

## Implementing the `counter` query

In the previous step the `counter` query is declared as `"counter": () -> (nat64) query`. This step implements it.

In the code editor, open the `src/my_timers_backend/src/lib.rs` file and replace its content with the following:

```rust
use std::sync::atomic::{AtomicU64, Ordering};

static COUNTER: AtomicU64 = AtomicU64::new(0);

#[ic_cdk::query]
fn counter() -> u64 {
COUNTER.load(Ordering::Relaxed)
}
```

This code establishes the following:

- `static COUNTER: AtomicU64 = ...` defines a new global variable called `COUNTER`.
- `#[ic_cdk::query]` marks the following `counter` function as a `query` entry point, so the function will be exported as `canister_query counter`.
- `fn counter() -> u64 {...}` defines the query. Just like in the `.did` definition, it takes no arguments and returns `u64`.
- `COUNTER.load(...)` loads and returns the global `COUNTER` value.
-

:::info
It is important to note that `Ordering` in this code is a shorter, but equivalent alternative to `thread_local!` wrapping a `Cell<u64>`.
:::

## Implementing canister initialization

In the previous step, the service is declared as `service : (nat64) -> {...}`. This step implements the canister initialization with an argument.

In the code editor, open the `src/my_timers_backend/src/lib.rs` file and append the following:

```rust
// ...

#[ic_cdk::init]
fn init(timer_interval_secs: u64) {
let interval = std::time::Duration::from_secs(timer_interval_secs);
ic_cdk::println!("Starting a periodic task with interval {interval:?}");
ic_cdk_timers::set_timer_interval(interval, || {
COUNTER.fetch_add(1, Ordering::Relaxed);
});
}
```

This code establishes the following:

- `#[ic_cdk::init]` marks the following `init` function as a canister initialization method, so the function will be run when the canister is installed.
- `fn init(interval: u64) {...}` defines the initialization method. Just like in the `.did` definition, the function takes one argument: timer interval in seconds.
- `ic_cdk::println!(...)` prints the debug log message on the local `dfx` console.
- `ic_cdk_timers::set_timer_interval(...)` creates a new periodic timer with the specified interval and a closure to call.
- `COUNTER.fetch_add(1, ...)` increases the global `COUNTER` every time the periodic task is triggered.

## Implementing canister upgrade

:::info
Note: As described in the [periodic tasks and timers](../periodic-tasks) page, the timers library does not handle canister upgrades. It is up to the canister developer to serialize the timers in the `canister_pre_upgrade` and reactivate the timers in the `canister_post_upgrade` method if needed.
:::

For the sake of simplicity, in this guide the `canister_post_upgrade` method just calls `canister_init` to reinitialize the timer.

In the code editor, open the `src/my_timers_backend/src/lib.rs` file and append the following:

```rust
// ...

#[ic_cdk::post_upgrade]
fn post_upgrade(timer_interval_secs: u64) {
init(timer_interval_secs)
}
```

This code establishes the following:

- `#[ic_cdk::post_upgrade]` marks the following `post_upgrade` function as a canister post-upgrade handler, so the function will be exported as `canister_post_upgrade`.
- `fn post_upgrade(interval: u64) {...}` defines the post-upgrade method. Just like in the `.did` definition, the function takes one argument: timer interval in seconds.
- `init(timer_interval_secs)` for the sake of simplicity, the post-upgrade just calls the `init` function, i.e. does exactly the same as the canister initialization.

The canister's code is now complete. The finished file should look like this:

`src/my_timers_backend/src/lib.rs`:

```rust
use std::sync::atomic::{AtomicU64, Ordering};

static COUNTER: AtomicU64 = AtomicU64::new(0);

#[ic_cdk::query]
fn counter() -> u64 {
COUNTER.load(Ordering::Relaxed)
}

#[ic_cdk::init]
fn init(timer_interval_secs: u64) {
let interval = std::time::Duration::from_secs(timer_interval_secs);
ic_cdk::println!("Starting a periodic task with interval {interval:?}");
ic_cdk_timers::set_timer_interval(interval, || {
COUNTER.fetch_add(1, Ordering::Relaxed);
});
}

#[ic_cdk::post_upgrade]
fn post_upgrade(timer_interval_secs: u64) {
init(timer_interval_secs)
}
```

Save the file.

## Running the dapp locally

The libraries are added, the canister interface is described and the code is complete. Time to try it all out!

Start by assuring that you are still in your project's directory. Then, start the local execution environment with the command:

```sh
dfx start --clean --background
```

Then, compile and deploy `my_timers_backend` canister, setting the interval for the periodic task to 1s:

```sh
dfx deploy my_timers_backend --argument 1
```

The counter inside the canister starts increasing every second.

Example output:

```sh
dfx deploy my_timers_backend --argument 1
[...]
Deployed canisters.
URLs:
Backend canister via Candid interface:
my_timers_backend: http://127.0.0.1/...
```

Then, observe that the counter is actually non-zero:

```sh
dfx canister call my_timers_backend counter
```

Example output:

```sh
dfx canister call my_timers_backend counter
(8 : nat64)
```

## Next steps
For the next step, let's dive into Rust [stable structures](./stable-structures).
13 changes: 13 additions & 0 deletions docs/developer-docs/backend/rust/11-stable-structures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 11: Stable structures

## Overview

Stable structures are designed to use stable memory as the backing store, allowing them to grow to gigabytes in size without the need for `pre_upgrade`/`post_upgrade` hooks.

A [stable structure library](https://github.com/dfinity/stable-structures#readme) exists which aims to simplify managing data structures directly in stable memory and provides example code templates.

For more information about stable structures, please see the [stable structures library](https://github.com/dfinity/stable-structures) and [Roman's tutorial on stable structures](https://mmapped.blog/posts/14-stable-structures.html).

## Next steps

For the next step, let's dive into [storing and searching records](12-searching-records.md).
Loading

0 comments on commit 3804eea

Please sign in to comment.