Can't do cross-canister call in init function

I have an init function in my Rust canister, and I am trying to get access to randomness from the random beacon within that init function. I get the following error "ic0_call_new" cannot be executed in init mode.

I am trying to do this from the init function:

let call_result: Result<(Vec<u8>,), _> = ic_cdk::api::call::call(ic_cdk::export::Principal::management_canister(), "raw_rand", ()).await;
2 Likes

I’m pretty sure that that is a limitation. I ran across it in the IC code somewhere Init can’t do cross canister.

1 Like

Yes, I can confirm that is a limitiation, documented in the Interface Spec.

Motoko will actually statically reject actors that try to send messages from their initialisation code, but I guess the Rust library cannot or is not designed to enforce this.

2 Likes

If you look at the spec, the system functions have comments that use codes to indicate in which contexts they can be called (trapping on violation).

In particular, ic0_call_new cannot be called in mode I.

4 Likes

My experience is that you can’t use await in the init function, which means you can’t do cross-canister call and get the result in init function.

Could you elaborate on this limitation, please? Why does it work that way?

1 Like

My understanding is that the canister_init method is used to set up the initial invariants of the canister.
If you allowed messaging during canister_init, then another canister, or the canister itself, could call into the canister under construction, before its invariants have been established.

This is a well-known flaw with constructors in some OO languages, IIRC.

(NB: I didn’t design the System API and am just speculating on the rationale.)

3 Likes

Thanks a lot!
Then, I believe, it should be possible to introduce some kind of post_init lifecycle hook? That would be handy.

2 Likes

I can definitely use await from the init function

1 Like

I don’t try it in Rust, but in motoko:

Stdout:

Stderr:
/Users/flyq/workspace/test/test_await/src/main1.mo:8.13-8.30: type error [M0038], misplaced await

source:

import Test2 "./main2";
import Principal "mo:base/Principal";
actor class Test1() {

    var a = 0;

    let b = await Test2.Test2; // line 8
...

It looks like there is a workaround for this limitation. Just discovered it.

You can use ic-cron library to schedule a task inside init() function and then immediately process that task in the first ever heartbeat of your canister.

implement_cron!();

#[derive(CandidType, Deserialize)]
pub enum CronTaskType {
    Init(Principal);
};

#[init]
fn init(external_canister: Principal) {
    cron_enqueue(
        CronTaskType::Init(external_canister), 
       
        // these options represent an immediate one-time task
        SchedulingOptions {
            delay_nano: 0,
            interval_nano: 0,
            iterations: Iterations::Exact(1),
        }
    );
}

#[heartbeat]
fn tick() {
    for task in cron_ready_tasks() {
        let task_type: CronTaskType = task.get_payload().expect("get_payload failed");

        match task_type {
            CronTaskType::Init(external_canister) => {
                spawn(async move {
                    call(external_canister, "test", ()).await
                });
            }
        };
    }
}
4 Likes

Would this be an issue in a system based on capabilities?

Post init would be a great system hook to have.

3 Likes