Skip to content

Commit d89af62

Browse files
feat(execution_environment): Allow msg_reject_code in call_on_cleanup (#6915)
This PR allows `msg_reject_code` system API to be accessible in the cleanup callback. This allows users to know the status of the **inter canister call (success / failure) in the cleanup (in case of traps)** and can make data decisions based on that.
1 parent fe32744 commit d89af62

File tree

4 files changed

+172
-2
lines changed

4 files changed

+172
-2
lines changed

rs/embedders/src/wasmtime_embedder/system_api.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ pub enum ApiType {
410410
Cleanup {
411411
caller: PrincipalId,
412412
time: Time,
413+
reject_code: i32,
413414
/// The total number of instructions executed in the call context
414415
call_context_instructions_executed: NumInstructions,
415416
},
@@ -1445,7 +1446,6 @@ impl SystemApiImpl {
14451446
ApiType::Start { .. }
14461447
| ApiType::Init { .. }
14471448
| ApiType::SystemTask { .. }
1448-
| ApiType::Cleanup { .. }
14491449
| ApiType::CompositeCleanup { .. }
14501450
| ApiType::ReplicatedQuery { .. }
14511451
| ApiType::NonReplicatedQuery { .. }
@@ -1458,6 +1458,7 @@ impl SystemApiImpl {
14581458
| ApiType::CompositeRejectCallback { reject_context, .. } => {
14591459
Some(reject_context.code() as i32)
14601460
}
1461+
ApiType::Cleanup { reject_code, .. } => Some(*reject_code),
14611462
}
14621463
}
14631464

rs/embedders/tests/system_api.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ fn cleanup_api() -> ApiType {
157157
ApiType::Cleanup {
158158
caller: PrincipalId::new_anonymous(),
159159
time: UNIX_EPOCH,
160+
reject_code: 0,
160161
call_context_instructions_executed: 0.into(),
161162
}
162163
}
@@ -186,7 +187,7 @@ fn is_supported(api_type: SystemApiCallId, context: &str) -> bool {
186187
SystemApiCallId::MsgArgDataCopy => vec!["I", "U", "RQ", "NRQ", "CQ", "Ry", "CRy", "F"],
187188
SystemApiCallId::MsgCallerSize => vec!["*"],
188189
SystemApiCallId::MsgCallerCopy => vec!["*"],
189-
SystemApiCallId::MsgRejectCode => vec!["Ry", "Rt", "CRy", "CRt"],
190+
SystemApiCallId::MsgRejectCode => vec!["Ry", "Rt", "CRy", "CRt", "C"],
190191
SystemApiCallId::MsgRejectMsgSize => vec!["Rt", "CRt"],
191192
SystemApiCallId::MsgRejectMsgCopy => vec!["Rt", "CRt"],
192193
SystemApiCallId::MsgReplyDataAppend => vec!["U", "RQ", "NRQ", "CQ", "Ry", "Rt", "CRy", "CRt"],

rs/execution_environment/src/execution/response.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,10 +1075,17 @@ fn execute_response_cleanup(
10751075
FuncRef::QueryClosure(cleanup_closure)
10761076
}
10771077
};
1078+
1079+
let reject_code = match &original.message.response_payload {
1080+
Payload::Data(_) => 0,
1081+
Payload::Reject(context) => context.code() as i32,
1082+
};
1083+
10781084
let result = round.hypervisor.execute_dts(
10791085
ApiType::Cleanup {
10801086
caller: original.call_origin.get_principal(),
10811087
time: original.time,
1088+
reject_code,
10821089
call_context_instructions_executed: original.instructions_executed,
10831090
},
10841091
helper.canister().execution_state.as_ref().unwrap(),

rs/execution_environment/tests/hypervisor.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10518,3 +10518,164 @@ fn parallel_callbacks() {
1051810518
assert_eq!(err.code(), ErrorCode::CanisterCalledTrap);
1051910519
assert!(err.description().contains("trap in second callback"));
1052010520
}
10521+
10522+
const REPLY_REJECT_CLEANUP_CALLBACK_WAT: &str = r#"
10523+
(module
10524+
(import "ic0" "call_new"
10525+
(func $ic0_call_new
10526+
(param i32 i32)
10527+
(param $method_name_src i32) (param $method_name_len i32)
10528+
(param $reply_fun i32) (param $reply_env i32)
10529+
(param $reject_fun i32) (param $reject_env i32)
10530+
))
10531+
(import "ic0" "call_perform" (func $ic0_call_perform (result i32)))
10532+
10533+
(import "ic0" "trap" (func $ic0_trap (param i32) (param i32)))
10534+
(import "ic0" "call_on_cleanup" (func $ic0_call_on_cleanup (param i32) (param i32)))
10535+
(import "ic0" "msg_reject_code" (func $ic0_msg_reject_code (result i32)))
10536+
10537+
(import "ic0" "msg_reply" (func $msg_reply))
10538+
(import "ic0" "msg_reply_data_append"
10539+
(func $msg_reply_data_append (param i32) (param i32)))
10540+
10541+
(func $dummy
10542+
(call $msg_reply_data_append
10543+
(i32.const 300) (i32.const 1)) ;; refers to 9 on the heap
10544+
(call $msg_reply)
10545+
)
10546+
10547+
10548+
(func $test
10549+
(call $ic0_call_new
10550+
(i32.const 100) (i32.const 10) ;; callee canister id = 0
10551+
(i32.const 0) (i32.const 4) ;; refers to "test" on the heap
10552+
(i32.const 0) (i32.const 200) ;; on_reply closure at table index 0
10553+
(i32.const 1) (i32.const 200)) ;; on_reject closure at table index 1
10554+
(call $ic0_call_on_cleanup
10555+
(i32.const 2) (i32.const 200)) ;; cleanup closure at table index 2
10556+
(drop (call $ic0_call_perform))
10557+
)
10558+
10559+
(func $on_reply (param i32)
10560+
(call $ic0_trap
10561+
(i32.const 200) (i32.const 12)) ;; reply callback traps
10562+
)
10563+
10564+
(func $on_reject (param i32)
10565+
(call $ic0_trap
10566+
(i32.const 200) (i32.const 12)) ;; reject callback traps
10567+
)
10568+
10569+
(func $on_cleanup (param i32)
10570+
(i32.store8 ;; cleanup can't reply, so
10571+
(i32.const 300) (call $ic0_msg_reject_code)) ;; we write cleanup code to memory and retrieve it via $dummy
10572+
)
10573+
10574+
(export "canister_update test" (func $test))
10575+
(export "canister_update dummy" (func $dummy))
10576+
(memory $memory 1)
10577+
(export "memory" (memory $memory))
10578+
(data (i32.const 0) "test")
10579+
(data (i32.const 100) "\00\00\00\00\00\00\00\00\01\01") ;; cansister_id of the installed canister is 0
10580+
(data (i32.const 200) "trap message")
10581+
(data (i32.const 300) "\09")
10582+
(table 3 3 funcref)
10583+
(elem (i32.const 0) $on_reply $on_reject $on_cleanup)
10584+
)
10585+
"#;
10586+
10587+
#[test]
10588+
fn can_access_reject_code_in_cleanup_call_rejected() {
10589+
const CANISTER_SIMPLE_WAT: &str = r#"
10590+
(module
10591+
(import "ic0" "msg_reject"
10592+
(func $msg_reject (param $msg_reject_src i32) (param $msg_reject_size i32)))
10593+
(func $test
10594+
(call $msg_reject
10595+
(i32.const 0) (i32.const 26)) ;; refers to "explictly_rejected_message" on the heap
10596+
)
10597+
(export "canister_update test" (func $test))
10598+
(memory $memory 1)
10599+
(export "memory" (memory $memory))
10600+
(data (i32.const 0) "explictly_rejected_message")
10601+
)"#;
10602+
10603+
let mut test = ExecutionTestBuilder::new().build();
10604+
let canister_id = test.create_canister(Cycles::new(1_000_000_000_000));
10605+
10606+
// assert that the hardcoded canister_id in REPLY_REJECT_CLEANUP_CALLBACK_WAT
10607+
// matches the one from instantiation
10608+
assert_eq!(canister_id, CanisterId::from_u64(0));
10609+
10610+
test.install_canister(canister_id, wat::parse_str(CANISTER_SIMPLE_WAT).unwrap())
10611+
.unwrap();
10612+
10613+
let main_canister_id = test.create_canister(Cycles::new(1_000_000_000_000));
10614+
test.install_canister(
10615+
main_canister_id,
10616+
wat::parse_str(REPLY_REJECT_CLEANUP_CALLBACK_WAT).unwrap(),
10617+
)
10618+
.unwrap();
10619+
10620+
// Initial value at the cleanup reference is 9
10621+
let result = test.ingress(main_canister_id, "dummy", vec![]).unwrap();
10622+
assert_eq!(WasmResult::Reply(vec![9]), result);
10623+
10624+
// Reject callback traps
10625+
let result = test.ingress(main_canister_id, "test", vec![]);
10626+
assert!(result.is_err());
10627+
10628+
// msg_reject_code should be written to memory now via cleanup callback
10629+
// reject code is 4 when a canister explicitly rejects
10630+
let result = test.ingress(main_canister_id, "dummy", vec![]).unwrap();
10631+
assert_eq!(WasmResult::Reply(vec![4]), result);
10632+
}
10633+
10634+
#[test]
10635+
fn can_access_reject_code_in_cleanup_call_replied() {
10636+
const CANISTER_SIMPLE_WAT: &str = r#"
10637+
(module
10638+
(import "ic0" "msg_reply" (func $msg_reply))
10639+
(import "ic0" "msg_reply_data_append"
10640+
(func $msg_reply_data_append (param i32) (param i32)))
10641+
(func $test
10642+
(call $msg_reply_data_append
10643+
(i32.const 0) (i32.const 26)) ;; refers to "explictly_approved_message" on the heap
10644+
(call $msg_reply)
10645+
)
10646+
(export "canister_update test" (func $test))
10647+
(memory $memory 1)
10648+
(export "memory" (memory $memory))
10649+
(data (i32.const 0) "explictly_approved_message")
10650+
)"#;
10651+
10652+
let mut test = ExecutionTestBuilder::new().build();
10653+
let canister_id = test.create_canister(Cycles::new(1_000_000_000_000));
10654+
10655+
// assert that the hardcoded canister id in REPLY_REJECT_CLEANUP_CALLBACK_WAT
10656+
// matches the one from instantiation
10657+
assert_eq!(canister_id, CanisterId::from_u64(0));
10658+
10659+
test.install_canister(canister_id, wat::parse_str(CANISTER_SIMPLE_WAT).unwrap())
10660+
.unwrap();
10661+
10662+
let main_canister_id = test.create_canister(Cycles::new(1_000_000_000_000));
10663+
test.install_canister(
10664+
main_canister_id,
10665+
wat::parse_str(REPLY_REJECT_CLEANUP_CALLBACK_WAT).unwrap(),
10666+
)
10667+
.unwrap();
10668+
10669+
// Initial value at the cleanup reference is 9
10670+
let result = test.ingress(main_canister_id, "dummy", vec![]).unwrap();
10671+
assert_eq!(WasmResult::Reply(vec![9]), result);
10672+
10673+
// Reply callback traps
10674+
let result = test.ingress(main_canister_id, "test", vec![]);
10675+
assert!(result.is_err());
10676+
10677+
// msg_reject_code should be written to memory now via cleanup callback
10678+
// canister replied would be reject code 0
10679+
let result = test.ingress(main_canister_id, "dummy", vec![]).unwrap();
10680+
assert_eq!(WasmResult::Reply(vec![0]), result);
10681+
}

0 commit comments

Comments
 (0)