Skip to content

Commit

Permalink
feat!: config canister endpoint decoding quota (#465)
Browse files Browse the repository at this point in the history
* feat: config canister endpoint decoding quota

* add call_with_config

* fix

* fix

* changelog

* fix

* fix

* update changelog
  • Loading branch information
chenyan-dfinity authored Mar 1, 2024
1 parent f6c1820 commit bbd6339
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 26 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = ["src/*", "library/*", "e2e-tests"]
resolver = "2"

[workspace.package]
authors = ["DFINITY Stiftung <sdk@dfinity.org>"]
Expand All @@ -23,8 +24,8 @@ ic0 = { path = "src/ic0", version = "0.21.1" }
ic-cdk = { path = "src/ic-cdk", version = "0.12.1" }
ic-cdk-timers = { path = "src/ic-cdk-timers", version = "0.6.0" }

candid = "0.10"
candid_parser = "0.1.0"
candid = "0.10.4"
candid_parser = "0.1.4"
futures = "0.3"
hex = "0.4"
quote = "1"
Expand All @@ -33,3 +34,4 @@ serde_bytes = "0.11"
sha2 = "0.10"
slotmap = "1"
syn = "1"

2 changes: 1 addition & 1 deletion examples/counter/src/counter_rs/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn init() {
OWNER.with(|owner| owner.set(ic_cdk::api::caller()));
}

#[update]
#[update(debug = true, decoding_quota = 50, skipping_quota = 0)]
fn inc() {
ic_cdk::println!("{:?}", OWNER.with(|owner| owner.get()));
COUNTER.with(|counter| *counter.borrow_mut() += 1u64);
Expand Down
91 changes: 82 additions & 9 deletions src/ic-cdk-macros/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ struct ExportAttributes {
pub composite: bool,
#[serde(default)]
pub hidden: bool,
#[serde(default)]
pub decoding_quota: Option<usize>,
#[serde(default = "default_skipping_quota")]
pub skipping_quota: Option<usize>,
#[serde(default)]
pub debug: bool,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -56,6 +62,10 @@ impl std::fmt::Display for MethodType {
}
}

fn default_skipping_quota() -> Option<usize> {
Some(10_000)
}

fn get_args(method: MethodType, signature: &Signature) -> Result<Vec<(Ident, Box<Type>)>, Error> {
// We only need the tuple of arguments, not their types. Magic of type inference.
let mut args = vec![];
Expand Down Expand Up @@ -176,7 +186,29 @@ fn dfn_macro(
let arg_decode = if method.is_lifecycle() && arg_count == 0 {
quote! {}
} else {
quote! { let ( #( #arg_tuple, )* ) = ic_cdk::api::call::arg_data(); }
let decoding_quota = if let Some(n) = attrs.decoding_quota {
quote! { Some(#n) }
} else {
quote! { None }
};
let skipping_quota = if let Some(n) = attrs.skipping_quota {
quote! { Some(#n) }
} else {
quote! { None }
};
let debug = if attrs.debug {
quote! { true }
} else {
quote! { false }
};
let config = quote! {
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: #decoding_quota,
skipping_quota: #skipping_quota,
debug: #debug,
}
};
quote! { let ( #( #arg_tuple, )* ) = ic_cdk::api::call::arg_data(#config); }
};

let guard = if let Some(guard_name) = attrs.guard {
Expand Down Expand Up @@ -298,7 +330,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let () = ic_cdk::api::call::arg_data();
let () = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query();
ic_cdk::api::call::reply(())
});
Expand All @@ -314,7 +352,6 @@ mod test {
_ => panic!("not a function"),
};
}

#[test]
fn ic_query_return_one_value() {
let generated = ic_query(
Expand All @@ -335,7 +372,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let () = ic_cdk::api::call::arg_data();
let () = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query();
ic_cdk::api::call::reply((result,))
});
Expand Down Expand Up @@ -372,7 +415,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let () = ic_cdk::api::call::arg_data();
let () = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query();
ic_cdk::api::call::reply(result)
});
Expand Down Expand Up @@ -409,7 +458,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let (a, ) = ic_cdk::api::call::arg_data();
let (a, ) = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query(a);
ic_cdk::api::call::reply(())
});
Expand Down Expand Up @@ -446,7 +501,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let (a, b, ) = ic_cdk::api::call::arg_data();
let (a, b, ) = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query(a, b);
ic_cdk::api::call::reply(())
});
Expand Down Expand Up @@ -483,7 +544,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let (a, b, ) = ic_cdk::api::call::arg_data();
let (a, b, ) = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query(a, b);
ic_cdk::api::call::reply((result,))
});
Expand Down Expand Up @@ -520,7 +587,13 @@ mod test {
fn #fn_name() {
ic_cdk::setup();
ic_cdk::spawn(async {
let () = ic_cdk::api::call::arg_data();
let () = ic_cdk::api::call::arg_data(
ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: None,
skipping_quota: Some(10000usize),
debug: false,
}
);
let result = query();
ic_cdk::api::call::reply(())
});
Expand Down
7 changes: 6 additions & 1 deletion src/ic-cdk-timers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,12 @@ extern "C" fn timer_executor() {
if ic_cdk::api::caller() != ic_cdk::api::id() {
ic_cdk::trap("This function is internal to ic-cdk and should not be called externally.");
}
let (task_id,) = ic_cdk::api::call::arg_data();
let config = ic_cdk::api::call::ArgDecoderConfig {
decoding_quota: Some(10_000),
skipping_quota: Some(100),
debug: false,
};
let (task_id,) = ic_cdk::api::call::arg_data(config);
let task_id = TimerId(KeyData::from_ffi(task_id));
// We can't be holding `TASKS` when we call the function, because it may want to schedule more tasks.
// Instead, we swap the task out in order to call it, and then either swap it back in, or remove it.
Expand Down
10 changes: 10 additions & 0 deletions src/ic-cdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add `is_recovering_from_trap` function for implementing trap cleanup logic
- Allow setting decoding quota for canister endpoints and inter-canister calls.
* When defining canister endpoints, we add the following attributes: `#[update(decoding_quota = 10000, skipping_quota = 100, debug = true)]`
- `skipping_quota` limits the amount of work allowed for skipping unneeded data on the wire. If this attributes is not present, we set a default quota of `10_000`. This affects ALL existing canisters, and is mainly used to improve canister throughput. See [docs on the Candid library](https://docs.rs/candid/latest/candid/de/struct.DecoderConfig.html#method.set_skipping_quota) to understand the skipping cost.
- `decoding_quota` limits the total amount of work the deserializer can perform. See [docs on the Candid library](https://docs.rs/candid/latest/candid/de/struct.DecoderConfig.html#method.set_decoding_quota) to understand the cost model.
- `debug = true` prints the instruction count and the decoding/skipping cost to the replica log, after a successful deserialization. The decoding/skipping cost is logged only when you have already set a quota in the attributes. The debug mode is useful to determine the right quotas above. Developers can send a few large payloads to the debugging endpoint and know the actual decoding cost.
* When making inter-canister calls, we have a new function `call_with_config` to config the same decoding quotas described above. It's strongly recommended to use `call_with_config` when calling third-party untrusted canisters.

### Changed

- `ic_cdk::api::call::arg_data` takes `ArgDecoderConfig` as argument.

## [0.12.1] - 2024-01-12

Expand Down
Loading

0 comments on commit bbd6339

Please sign in to comment.