diff --git a/Cargo.Bazel.Fuzzing.json.lock b/Cargo.Bazel.Fuzzing.json.lock index 62b92f3b732..d6d698bcf82 100644 --- a/Cargo.Bazel.Fuzzing.json.lock +++ b/Cargo.Bazel.Fuzzing.json.lock @@ -1,5 +1,5 @@ { - "checksum": "e2f0c9fb8047d4200e8e14e235a3d28792aef48b70952cf21a1f849ff9ea495d", + "checksum": "31d108b4a4982572672c954576cb1de774d95b6efbedede950aed738c4911526", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -10110,7 +10110,7 @@ "target": "canbench_rs" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -10205,7 +10205,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -10292,14 +10292,14 @@ ], "license_file": null }, - "candid 0.10.10": { + "candid 0.10.12": { "name": "candid", - "version": "0.10.10", + "version": "0.10.12", "package_url": "https://github.com/dfinity/candid", "repository": { "Http": { - "url": "https://static.crates.io/crates/candid/0.10.10/download", - "sha256": "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" + "url": "https://static.crates.io/crates/candid/0.10.12/download", + "sha256": "51e129c4051c57daf943586e01ef72faae48b04a8f692d5f646febf17a264c38" } }, "targets": [ @@ -10427,7 +10427,7 @@ ], "selects": {} }, - "version": "0.10.10" + "version": "0.10.12" }, "license": "Apache-2.0", "license_ids": [ @@ -10542,7 +10542,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -18518,7 +18518,7 @@ "target": "canbench_rs" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -18781,6 +18781,11 @@ "id": "ic-cdk 0.16.0", "target": "ic_cdk" }, + { + "id": "ic-cdk 0.18.0-alpha.1", + "target": "ic_cdk", + "alias": "ic_cdk_next" + }, { "id": "ic-cdk-timers 0.11.0", "target": "ic_cdk_timers" @@ -23225,7 +23230,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -29972,7 +29977,7 @@ "target": "cached" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30443,7 +30448,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30547,7 +30552,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30634,7 +30639,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30697,7 +30702,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30765,7 +30770,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30833,7 +30838,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30869,6 +30874,81 @@ ], "license_file": "LICENSE" }, + "ic-cdk 0.18.0-alpha.1": { + "name": "ic-cdk", + "version": "0.18.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic-cdk" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ic_cdk", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic_cdk", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "candid 0.10.12", + "target": "candid" + }, + { + "id": "ic0 0.24.0-alpha.1", + "target": "ic0" + }, + { + "id": "serde 1.0.217", + "target": "serde" + }, + { + "id": "serde_bytes 0.11.15", + "target": "serde_bytes" + }, + { + "id": "thiserror 2.0.3", + "target": "thiserror" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "ic-cdk-macros 0.18.0-alpha.1", + "target": "ic_cdk_macros" + } + ], + "selects": {} + }, + "version": "0.18.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic-cdk-macros 0.8.4": { "name": "ic-cdk-macros", "version": "0.8.4", @@ -30901,7 +30981,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30968,7 +31048,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31035,7 +31115,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31102,7 +31182,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31137,6 +31217,76 @@ ], "license_file": "LICENSE" }, + "ic-cdk-macros 0.18.0-alpha.1": { + "name": "ic-cdk-macros", + "version": "0.18.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic-cdk-macros" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "ic_cdk_macros", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic_cdk_macros", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "candid 0.10.12", + "target": "candid" + }, + { + "id": "proc-macro2 1.0.89", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.37", + "target": "quote" + }, + { + "id": "serde 1.0.217", + "target": "serde" + }, + { + "id": "serde_tokenstream 0.2.1", + "target": "serde_tokenstream" + }, + { + "id": "syn 2.0.87", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.18.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic-cdk-timers 0.11.0": { "name": "ic-cdk-timers", "version": "0.11.0", @@ -31240,7 +31390,7 @@ "target": "cached" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31515,7 +31665,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31594,7 +31744,7 @@ "target": "bytes" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31770,7 +31920,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31988,7 +32138,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32047,7 +32197,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32131,7 +32281,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32225,7 +32375,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32503,7 +32653,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32578,7 +32728,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32711,6 +32861,47 @@ ], "license_file": "LICENSE" }, + "ic0 0.24.0-alpha.1": { + "name": "ic0", + "version": "0.24.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic0" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ic0", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic0", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.24.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic_bls12_381 0.10.0": { "name": "ic_bls12_381", "version": "0.10.0", @@ -32919,7 +33110,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32990,7 +33181,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -49415,7 +49606,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -87417,7 +87608,7 @@ "cached 0.49.2", "canbench 0.1.8", "canbench-rs 0.1.8", - "candid 0.10.10", + "candid 0.10.12", "candid_parser 0.1.2", "cargo_metadata 0.14.2", "cc 1.1.37", @@ -87484,6 +87675,7 @@ "ic-canister-sig-creation 1.1.0", "ic-cbor 3.0.2", "ic-cdk 0.16.0", + "ic-cdk 0.18.0-alpha.1", "ic-cdk-macros 0.9.0", "ic-cdk-timers 0.11.0", "ic-certificate-verification 3.0.2", diff --git a/Cargo.Bazel.Fuzzing.toml.lock b/Cargo.Bazel.Fuzzing.toml.lock index 36b66b3cf23..28caa184cf8 100644 --- a/Cargo.Bazel.Fuzzing.toml.lock +++ b/Cargo.Bazel.Fuzzing.toml.lock @@ -1745,9 +1745,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" +checksum = "51e129c4051c57daf943586e01ef72faae48b04a8f692d5f646febf17a264c38" dependencies = [ "anyhow", "binread", @@ -3080,6 +3080,7 @@ dependencies = [ "ic-canister-sig-creation", "ic-cbor", "ic-cdk 0.16.0", + "ic-cdk 0.18.0-alpha.1", "ic-cdk-macros 0.9.0", "ic-cdk-timers", "ic-certificate-verification", @@ -5123,6 +5124,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-cdk" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "ic-cdk-macros 0.18.0-alpha.1", + "ic0 0.24.0-alpha.1", + "serde", + "serde_bytes", + "thiserror 2.0.3", +] + [[package]] name = "ic-cdk-macros" version = "0.8.4" @@ -5179,6 +5193,19 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "ic-cdk-macros" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.1", + "syn 2.0.87", +] + [[package]] name = "ic-cdk-timers" version = "0.11.0" @@ -5482,6 +5509,11 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +[[package]] +name = "ic0" +version = "0.24.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" + [[package]] name = "ic_bls12_381" version = "0.10.0" diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index ae3a6e269f4..41080f21f89 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "c940ed0c71e779f38de47e366662d71cd883022d89016936e1952a55719fe089", + "checksum": "b68f88e7aabce820556a6b7f8b0373dfb1e55e46fa3f4ebece0092b0c634e5e3", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -10027,7 +10027,7 @@ "target": "canbench_rs" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -10122,7 +10122,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -10209,14 +10209,14 @@ ], "license_file": null }, - "candid 0.10.10": { + "candid 0.10.12": { "name": "candid", - "version": "0.10.10", + "version": "0.10.12", "package_url": "https://github.com/dfinity/candid", "repository": { "Http": { - "url": "https://static.crates.io/crates/candid/0.10.10/download", - "sha256": "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" + "url": "https://static.crates.io/crates/candid/0.10.12/download", + "sha256": "51e129c4051c57daf943586e01ef72faae48b04a8f692d5f646febf17a264c38" } }, "targets": [ @@ -10323,7 +10323,7 @@ ], "selects": {} }, - "version": "0.10.10" + "version": "0.10.12" }, "license": "Apache-2.0", "license_ids": [ @@ -10438,7 +10438,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -18346,7 +18346,7 @@ "target": "canbench_rs" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -18609,6 +18609,11 @@ "id": "ic-cdk 0.16.0", "target": "ic_cdk" }, + { + "id": "ic-cdk 0.18.0-alpha.1", + "target": "ic_cdk", + "alias": "ic_cdk_next" + }, { "id": "ic-cdk-timers 0.11.0", "target": "ic_cdk_timers" @@ -19505,6 +19510,11 @@ "id": "ic-cdk-macros 0.9.0", "target": "ic_cdk_macros" }, + { + "id": "ic-cdk-macros 0.18.0-alpha.1", + "target": "ic_cdk_macros", + "alias": "ic_cdk_macros_next" + }, { "id": "indoc 1.0.9", "target": "indoc" @@ -23076,7 +23086,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -29827,7 +29837,7 @@ "target": "cached" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30298,7 +30308,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30402,7 +30412,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30489,7 +30499,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30552,7 +30562,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30620,7 +30630,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30688,7 +30698,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30724,6 +30734,81 @@ ], "license_file": "LICENSE" }, + "ic-cdk 0.18.0-alpha.1": { + "name": "ic-cdk", + "version": "0.18.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic-cdk" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ic_cdk", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic_cdk", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "candid 0.10.12", + "target": "candid" + }, + { + "id": "ic0 0.24.0-alpha.1", + "target": "ic0" + }, + { + "id": "serde 1.0.217", + "target": "serde" + }, + { + "id": "serde_bytes 0.11.15", + "target": "serde_bytes" + }, + { + "id": "thiserror 2.0.3", + "target": "thiserror" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "ic-cdk-macros 0.18.0-alpha.1", + "target": "ic_cdk_macros" + } + ], + "selects": {} + }, + "version": "0.18.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic-cdk-macros 0.8.4": { "name": "ic-cdk-macros", "version": "0.8.4", @@ -30756,7 +30841,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30823,7 +30908,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30890,7 +30975,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30957,7 +31042,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -30992,6 +31077,76 @@ ], "license_file": "LICENSE" }, + "ic-cdk-macros 0.18.0-alpha.1": { + "name": "ic-cdk-macros", + "version": "0.18.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic-cdk-macros" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "ic_cdk_macros", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic_cdk_macros", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "candid 0.10.12", + "target": "candid" + }, + { + "id": "proc-macro2 1.0.89", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.37", + "target": "quote" + }, + { + "id": "serde 1.0.217", + "target": "serde" + }, + { + "id": "serde_tokenstream 0.2.1", + "target": "serde_tokenstream" + }, + { + "id": "syn 2.0.87", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.18.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic-cdk-timers 0.11.0": { "name": "ic-cdk-timers", "version": "0.11.0", @@ -31095,7 +31250,7 @@ "target": "cached" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31370,7 +31525,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31449,7 +31604,7 @@ "target": "bytes" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31625,7 +31780,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31822,7 +31977,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31881,7 +32036,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -31965,7 +32120,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32059,7 +32214,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32337,7 +32492,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32412,7 +32567,7 @@ "deps": { "common": [ { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32545,6 +32700,47 @@ ], "license_file": "LICENSE" }, + "ic0 0.24.0-alpha.1": { + "name": "ic0", + "version": "0.24.0-alpha.1", + "package_url": "https://github.com/dfinity/cdk-rs", + "repository": { + "Git": { + "remote": "https://github.com/dfinity/cdk-rs.git", + "commitish": { + "Rev": "4e287ce51636b0e70768c193da38d2fc5324ea15" + }, + "strip_prefix": "ic0" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ic0", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ic0", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.24.0-alpha.1" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "ic_bls12_381 0.10.0": { "name": "ic_bls12_381", "version": "0.10.0", @@ -32753,7 +32949,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -32824,7 +33020,7 @@ "target": "anyhow" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -49217,7 +49413,7 @@ "target": "base64" }, { - "id": "candid 0.10.10", + "id": "candid 0.10.12", "target": "candid" }, { @@ -87297,7 +87493,7 @@ "cached 0.49.2", "canbench 0.1.8", "canbench-rs 0.1.8", - "candid 0.10.10", + "candid 0.10.12", "candid_parser 0.1.2", "cargo_metadata 0.14.2", "cc 1.1.37", @@ -87364,7 +87560,9 @@ "ic-canister-sig-creation 1.1.0", "ic-cbor 3.0.2", "ic-cdk 0.16.0", + "ic-cdk 0.18.0-alpha.1", "ic-cdk-macros 0.9.0", + "ic-cdk-macros 0.18.0-alpha.1", "ic-cdk-timers 0.11.0", "ic-certificate-verification 3.0.2", "ic-certification 3.0.2", diff --git a/Cargo.Bazel.toml.lock b/Cargo.Bazel.toml.lock index a81cc6d2d52..2d78c613af6 100644 --- a/Cargo.Bazel.toml.lock +++ b/Cargo.Bazel.toml.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "abnf" @@ -1746,9 +1746,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" +checksum = "51e129c4051c57daf943586e01ef72faae48b04a8f692d5f646febf17a264c38" dependencies = [ "anyhow", "binread", @@ -3069,6 +3069,8 @@ dependencies = [ "ic-canister-sig-creation", "ic-cbor", "ic-cdk 0.16.0", + "ic-cdk 0.18.0-alpha.1", + "ic-cdk-macros 0.18.0-alpha.1", "ic-cdk-macros 0.9.0", "ic-cdk-timers", "ic-certificate-verification", @@ -5113,6 +5115,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-cdk" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "ic-cdk-macros 0.18.0-alpha.1", + "ic0 0.24.0-alpha.1", + "serde", + "serde_bytes", + "thiserror 2.0.3", +] + [[package]] name = "ic-cdk-macros" version = "0.8.4" @@ -5169,6 +5184,19 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "ic-cdk-macros" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.1", + "syn 2.0.87", +] + [[package]] name = "ic-cdk-timers" version = "0.11.0" @@ -5472,6 +5500,11 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +[[package]] +name = "ic0" +version = "0.24.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs.git?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" + [[package]] name = "ic_bls12_381" version = "0.10.0" diff --git a/Cargo.lock b/Cargo.lock index f2e4877645c..fe69c30577a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6362,6 +6362,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-cdk" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "ic-cdk-macros 0.18.0-alpha.1", + "ic0 0.24.0-alpha.1", + "serde", + "serde_bytes", + "thiserror 2.0.11", +] + [[package]] name = "ic-cdk-macros" version = "0.8.4" @@ -6432,6 +6445,19 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "ic-cdk-macros" +version = "0.18.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.2", + "syn 2.0.96", +] + [[package]] name = "ic-cdk-timers" version = "0.7.0" @@ -9675,6 +9701,7 @@ dependencies = [ "serde", "slog", "tempfile", + "test-strategy 0.3.1", "tracing", "xnet-test", ] @@ -13733,6 +13760,11 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +[[package]] +name = "ic0" +version = "0.24.0-alpha.1" +source = "git+https://github.com/dfinity/cdk-rs?rev=4e287ce51636b0e70768c193da38d2fc5324ea15#4e287ce51636b0e70768c193da38d2fc5324ea15" + [[package]] name = "ic_bls12_381" version = "0.10.0" @@ -18137,12 +18169,13 @@ dependencies = [ "candid", "futures", "ic-base-types", - "ic-cdk 0.16.0", - "ic-cdk-macros 0.9.0", + "ic-cdk 0.18.0-alpha.1", + "ic-cdk-macros 0.18.0-alpha.1", "ic-error-types", "ic-types", "rand 0.8.5", "serde", + "serde_bytes", ] [[package]] diff --git a/bazel/external_crates.bzl b/bazel/external_crates.bzl index f25afadc154..9d04b8104c0 100644 --- a/bazel/external_crates.bzl +++ b/bazel/external_crates.bzl @@ -334,7 +334,7 @@ def external_crates_repository(name, cargo_lockfile, lockfile, sanitizers_enable version = "^0.1.8", ), "candid": crate.spec( - version = "^0.10.6", + version = "^0.10.12", ), "cargo_metadata": crate.spec( version = "^0.14.2", @@ -592,6 +592,16 @@ def external_crates_repository(name, cargo_lockfile, lockfile, sanitizers_enable "ic-cdk-macros": crate.spec( version = "^0.9.0", ), + "ic-cdk-macros-next": crate.spec( + package = "ic-cdk-macros", + git = "https://github.com/dfinity/cdk-rs.git", + rev = "4e287ce51636b0e70768c193da38d2fc5324ea15", + ), + "ic-cdk-next": crate.spec( + package = "ic-cdk", + git = "https://github.com/dfinity/cdk-rs.git", + rev = "4e287ce51636b0e70768c193da38d2fc5324ea15", + ), "ic-certified-map": crate.spec( version = "^0.3.1", ), diff --git a/rs/messaging/BUILD.bazel b/rs/messaging/BUILD.bazel index ea5c1ef8500..5c3aaf362aa 100644 --- a/rs/messaging/BUILD.bazel +++ b/rs/messaging/BUILD.bazel @@ -93,6 +93,10 @@ rust_ic_test_suite( "RANDOM_TRAFFIC_TEST_CANISTER_WASM_PATH": "$(rootpath //rs/rust_canisters/random_traffic_test:random-traffic-test-canister)", "XNET_TEST_CANISTER_WASM_PATH": "$(rootpath //rs/rust_canisters/xnet_test:xnet-test-canister)", }, + proc_macro_deps = [ + # Keep sorted. + "@crate_index//:test-strategy", + ], deps = [ # Keep sorted. ":messaging", diff --git a/rs/messaging/Cargo.toml b/rs/messaging/Cargo.toml index 8d3b7bbc82b..5b141022eef 100644 --- a/rs/messaging/Cargo.toml +++ b/rs/messaging/Cargo.toml @@ -70,6 +70,7 @@ rand_chacha = { workspace = true } random-traffic-test = { path = "../rust_canisters/random_traffic_test" } serde = { workspace = true } tempfile = { workspace = true } +test-strategy = "0.3.1" xnet-test = { path = "../rust_canisters/xnet_test" } [features] diff --git a/rs/messaging/tests/memory_tests.rs b/rs/messaging/tests/memory_tests.rs index cdb888ef398..7f6899cf1f6 100644 --- a/rs/messaging/tests/memory_tests.rs +++ b/rs/messaging/tests/memory_tests.rs @@ -2,7 +2,9 @@ use candid::{Decode, Encode}; use canister_test::Project; use ic_base_types::{CanisterId, NumBytes, SubnetId}; use ic_config::{ + embedders::{Config as EmbeddersConfig, FeatureFlags}, execution_environment::Config as HypervisorConfig, + flag_status::FlagStatus, subnet_config::{CyclesAccountManagerConfig, SchedulerConfig, SubnetConfig}, }; use ic_registry_routing_table::{routing_table_insert_subnet, RoutingTable}; @@ -11,6 +13,7 @@ use ic_replicated_state::ReplicatedState; use ic_state_machine_tests::{StateMachine, StateMachineBuilder, StateMachineConfig, UserError}; use ic_test_utilities_types::ids::{SUBNET_0, SUBNET_1}; use ic_types::{ + ingress::{IngressState, IngressStatus}, messages::{MessageId, MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64}, Cycles, }; @@ -27,54 +30,71 @@ const MB: u64 = KB * KB; const MAX_PAYLOAD_BYTES: u32 = MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 as u32; -proptest! { - #![proptest_config(ProptestConfig::with_cases(1))] - #[test] - fn check_guaranteed_response_message_memory_limits_are_respected( - seeds in proptest::collection::vec(any::().no_shrink(), 3), - max_payload_bytes in (MAX_PAYLOAD_BYTES / 4)..=MAX_PAYLOAD_BYTES, - calls_per_round in 1..=10, - reply_weight in 1..=2, - call_weight in 0..=2, - // Note: both weights zero defaults to only replies. +prop_compose! { + /// Generates a random `CanisterConfig` using reasonable ranges of values; receivers is empty + /// and assumed to be populated manually. + fn arb_canister_config(max_payload_bytes: u32, max_calls_per_heartbeat: u32)( + max_call_bytes in 0..=max_payload_bytes, + max_reply_bytes in 0..=max_payload_bytes, + calls_per_heartbeat in 0..=max_calls_per_heartbeat, + max_timeout_secs in 10..=100_u32, + downstream_call_percentage in 0..=100_u32, + best_effort_call_percentage in 0..=100_u32, + ) -> CanisterConfig { + CanisterConfig::try_new( + vec![], + 0..=max_call_bytes, + 0..=max_reply_bytes, + 0..=0, // instructions_count + 0..=max_timeout_secs, + calls_per_heartbeat, + downstream_call_percentage, + best_effort_call_percentage, + ) + .expect("bad config inputs") + } +} + +#[test_strategy::proptest(ProptestConfig::with_cases(3))] +fn check_guaranteed_response_message_memory_limits_are_respected( + #[strategy(proptest::collection::vec(any::().no_shrink(), 3))] seeds: Vec, + #[strategy(arb_canister_config(MAX_PAYLOAD_BYTES, 5))] config: CanisterConfig, +) { + if let Err((err_msg, nfo)) = check_guaranteed_response_message_memory_limits_are_respected_impl( + 30, // chatter_phase_round_count + 300, // shutdown_phase_max_rounds + seeds.as_slice(), + config, ) { - prop_assert!(check_guaranteed_response_message_memory_limits_are_respected_impl( - seeds.as_slice(), - max_payload_bytes, - calls_per_round as u32, - reply_weight as u32, - call_weight as u32, - ).is_ok()); + unreachable!("\nerr_msg: {err_msg}\n{:#?}", nfo.records); } } /// Runs a state machine test with two subnets, a local subnet with 2 canisters installed and a /// remote subnet with 1 canister installed. /// -/// In the first phase a number of rounds are executed on both subnets, including XNet traffic with -/// 'chatter' enabled, i.e. the installed canisters are making random calls (including downstream calls). +/// In the first phase `chatter_phase_round_count` rounds are executed on both subnets, including XNet +/// traffic with 'chatter' enabled, i.e. the installed canisters are making random calls (including +/// downstream calls depending on `config`). /// /// For the second phase, the 'chatter' is disabled by putting a canister into `Stopping` state /// every 10 rounds. In addition to shutting down traffic altogether from that canister (including -/// downstream calls) this will also induce a lot asychnronous rejections for requests. If any +/// downstream calls) this will also induce a lot asynchronous rejections for requests. If any /// canister fails to reach `Stopped` state (i.e. no pending calls), something went wrong in /// message routing, most likely a bug connected to reject signals for requests. /// -/// Checks that the guaranteed response message memory never exceeds the limit; that all calls eventually -/// receive a reply (or were rejected synchronously when issued); and that the message memory goes -/// back to 0 after all in-flight messages have been dealt with. +/// In the final phase, up to `shutdown_phase_max_rounds` additional rounds are executed after +/// 'chatter' has been turned off to conclude all calls (or else return `Err(_)` if any call fails +/// to do so). +/// +/// During all these phases, a check ensures that guaranteed response message memory never exceeds +/// the limit specified in the `FixtureConfig` used to generate the fixture used in this test. fn check_guaranteed_response_message_memory_limits_are_respected_impl( + chatter_phase_round_count: usize, + shutdown_phase_max_rounds: usize, seeds: &[u64], - max_payload_bytes: u32, - calls_per_round: u32, - reply_weight: u32, - call_weight: u32, + mut config: CanisterConfig, ) -> Result<(), (String, DebugInfo)> { - // The number of rounds to execute while chatter is on. - const CHATTER_PHASE_ROUND_COUNT: u64 = 30; - // The maximum number of rounds to execute after chatter is turned off. It it takes more than - // this number of rounds until there are no more pending calls, the test fails. - const SHUTDOWN_PHASE_MAX_ROUNDS: u64 = 300; // The amount of memory available for guaranteed response message memory on `local_env`. const LOCAL_MESSAGE_MEMORY_CAPACITY: u64 = 100 * MB; // The amount of memory available for guaranteed response message memory on `remote_env`. @@ -89,27 +109,16 @@ fn check_guaranteed_response_message_memory_limits_are_respected_impl( remote_message_memory_capacity: REMOTE_MESSAGE_MEMORY_CAPACITY, }); - let config = CanisterConfig::try_new( - fixture.canisters(), // receivers - 0..=max_payload_bytes, // call_bytes - 0..=max_payload_bytes, // reply_bytes - 0..=0, // instructions_count - ) - .unwrap(); + config.receivers = fixture.canisters(); // Send configs to canisters, seed the rng. for (index, canister) in fixture.canisters().into_iter().enumerate() { fixture.set_config(canister, config.clone()); fixture.seed_rng(canister, seeds[index]); - fixture.set_reply_weight(canister, reply_weight); - fixture.set_call_weight(canister, call_weight); } - // Start chatter on all canisters. - fixture.start_chatter(calls_per_round); - // Build up backlog and keep up chatter for while. - for _ in 0..CHATTER_PHASE_ROUND_COUNT { + for _ in 0..chatter_phase_round_count { fixture.tick(); // Check message memory limits are respected. @@ -123,10 +132,7 @@ fn check_guaranteed_response_message_memory_limits_are_respected_impl( // Shut down chatter by putting a canister into `Stopping` state every 10 ticks until they are // all `Stopping` or `Stopped`. for canister in fixture.canisters().into_iter() { - // The max calls per heartbeat are set to 0 here, because the canister has to be started - // to query it's records. This is to make sure the canister doesn't start making calls - // immediately before we can get its records. - fixture.set_max_calls_per_heartbeat(canister, 0); + fixture.stop_chatter(canister); fixture.stop_canister_non_blocking(canister); for _ in 0..10 { fixture.tick(); @@ -140,41 +146,28 @@ fn check_guaranteed_response_message_memory_limits_are_respected_impl( } } - // Keep ticking until all calls are answered. - for counter in 0.. { - fixture.tick(); - - // Check message memory limits are respected. + // Tick until all calls have concluded; or else fail the test. + fixture.tick_to_conclusion(shutdown_phase_max_rounds, |fixture| { fixture.expect_guaranteed_response_message_memory_taken_at_most( - "Shutdown", + "Wrap up", LOCAL_MESSAGE_MEMORY_CAPACITY, REMOTE_MESSAGE_MEMORY_CAPACITY, - )?; - - if fixture.open_call_contexts_count().values().sum::() == 0 { - break; - } - - if counter > SHUTDOWN_PHASE_MAX_ROUNDS { - return fixture.failed_with_reason("shutdown phase hanging"); - } - } - - // One extra tick to make sure everything is gc'ed. - fixture.tick(); + ) + }) +} - // Check the records agree on 'no pending calls'. - if fixture - .canisters() - .into_iter() - .map(|canister| extract_metrics(&fixture.force_query_records(canister))) - .any(|metrics| metrics.pending_calls != 0) - { - return fixture.failed_with_reason("found pending calls in the records"); +#[test_strategy::proptest(ProptestConfig::with_cases(3))] +fn check_calls_conclude_with_migrating_canister( + #[strategy(any::().no_shrink())] seed: u64, + #[strategy(arb_canister_config(KB as u32, 10))] config: CanisterConfig, +) { + if let Err((err_msg, nfo)) = check_calls_conclude_with_migrating_canister_impl( + 10, // chatter_phase_round_count + 300, // shutdown_phase_max_rounds + seed, config, + ) { + unreachable!("\nerr_msg: {err_msg}\n{:#?}", nfo.records); } - - // After the fact, all memory is freed and back to 0. - fixture.expect_guaranteed_response_message_memory_taken_at_most("Final check", 0, 0) } /// Runs a state machine test with two subnets, a local subnet with 2 canisters installed and a @@ -183,7 +176,7 @@ fn check_guaranteed_response_message_memory_limits_are_respected_impl( /// /// In the first phase a number of rounds are executed on both subnets, including XNet traffic with /// the `migrating_canister` making random calls to all installed canisters (since all calls are -/// rejected except those to self, downstream calls are disabled). +/// rejected except those to self). /// /// For the second phase, `migrating_canister` stops making calls and is then migrated to the /// remote subnet. Since all other canisters are stopped, there are bound to be a number of reject @@ -195,64 +188,138 @@ fn check_guaranteed_response_message_memory_limits_are_respected_impl( /// If there are pending calls after a threshold number of rounds, there is most likely a bug /// connected to reject signals for requests, specifically with the corresponding exceptions due to /// canister migration. -#[test] -fn check_calls_conclude_with_migrating_canister() { - // The number of rounds to execute while the migrating canister is making calls. - const BUILDUP_PHASE_ROUND_COUNT: u64 = 10; - // The maximum number of rounds to execute after chatter is turned off. It it takes more than - // this number of rounds until there are no more pending calls, the test fails. - const SHUTDOWN_PHASE_MAX_ROUNDS: u64 = 300; - +fn check_calls_conclude_with_migrating_canister_impl( + chatter_phase_round_count: usize, + shutdown_phase_max_rounds: usize, + seed: u64, + mut config: CanisterConfig, +) -> Result<(), (String, DebugInfo)> { let mut fixture = Fixture::new(FixtureConfig { local_canisters_count: 2, remote_canisters_count: 5, ..FixtureConfig::default() }); + config.receivers = fixture.canisters(); + let migrating_canister = *fixture.local_canisters.first().unwrap(); - let config = CanisterConfig::try_new( - fixture.canisters(), // receivers - 0..=0, // call_bytes - 0..=0, // reply_bytes - 0..=0, // instructions_count - ) - .unwrap(); - fixture.set_config(migrating_canister, config); - fixture.seed_rng(migrating_canister, 73); - fixture.set_reply_weight(migrating_canister, 1); - fixture.set_call_weight(migrating_canister, 0); - fixture.set_max_calls_per_heartbeat(migrating_canister, 10); + // Send config to `migrating_canister` and seed its rng. + fixture.set_config(migrating_canister, config); + fixture.seed_rng(migrating_canister, seed); // Stop all canisters except `migrating_canister`. for canister in fixture.canisters() { if canister != migrating_canister { + // Make sure the canister doesn't make calls when it is + // put into running state to read its records. + fixture.stop_chatter(canister); fixture.stop_canister_non_blocking(canister); } } // Make calls on `migrating_canister`. - for _ in 0..BUILDUP_PHASE_ROUND_COUNT { + for _ in 0..chatter_phase_round_count { fixture.tick(); } // Stop making calls and migrate `migrating_canister`. - fixture.set_max_calls_per_heartbeat(migrating_canister, 0); + fixture.stop_chatter(migrating_canister); fixture.migrate_canister(migrating_canister); - // Tick until all calls have concluded. - for counter in 0.. { + // Tick until all calls have concluded; or else fail the test. + fixture.tick_to_conclusion(shutdown_phase_max_rounds, |_| Ok(())) +} + +#[test_strategy::proptest(ProptestConfig::with_cases(3))] +fn check_canister_can_be_stopped_with_remote_subnet_stalling( + #[strategy(proptest::collection::vec(any::().no_shrink(), 2))] seeds: Vec, + #[strategy(arb_canister_config(MAX_PAYLOAD_BYTES, 5))] config: CanisterConfig, +) { + if let Err((err_msg, nfo)) = check_canister_can_be_stopped_with_remote_subnet_stalling_impl( + 30, // chatter_phase_round_count + 300, // shutdown_phase_max_rounds + seeds.as_slice(), + config, + ) { + unreachable!("\nerr_msg: {err_msg}\n{:#?}", nfo.records); + } +} + +/// Runs a state machine test with two subnets, a local subnet with one canister installed that +/// only makes best-effort calls and a remote subnet with one canister installed that makes random +/// calls of all kinds. +/// +/// In the first phase a number of rounds are executed on both subnet, including XNet traffic +/// between both canisters. +/// +/// For the second phase the local canister is put into `Stopping` state and the remote subnet +/// stalls, i.e. no more ticks are made on it. The local canister should reject any incoming calls +/// and since it made only best-effort calls, all pending calls should be rejected or timed out +/// eventually making the transition to `Stopped` state possible even with the remote subnet stalling. +/// +/// If the local canister fails to reach `Stopped` state, there is most likely a bug with timing +/// out best-effort messages. +fn check_canister_can_be_stopped_with_remote_subnet_stalling_impl( + chatter_phase_round_count: usize, + shutdown_phase_max_rounds: usize, + seeds: &[u64], + mut config: CanisterConfig, +) -> Result<(), (String, DebugInfo)> { + let fixture = Fixture::new(FixtureConfig { + local_canisters_count: 1, + remote_canisters_count: 1, + ..FixtureConfig::default() + }); + + config.receivers = fixture.canisters(); + + let local_canister = *fixture.local_canisters.first().unwrap(); + let remote_canister = *fixture.remote_canisters.first().unwrap(); + + fixture.seed_rng(local_canister, seeds[0]); + fixture.seed_rng(remote_canister, seeds[1]); + + // Set the local `config` adapted such that only best-effort calls are made. + fixture.set_config( + local_canister, + CanisterConfig { + best_effort_call_percentage: 100, + ..config.clone() + }, + ); + // Set the remote `config` as is. + fixture.set_config(remote_canister, config); + + // Make calls on both canisters. + for _ in 0..chatter_phase_round_count { fixture.tick(); - if fixture.open_call_contexts_count().values().sum::() == 0 { - break; + } + // Stop chatter on the local canister. + fixture.stop_chatter(local_canister); + + // Put local canister into `Stopping` state. + let msg_id = fixture.stop_canister_non_blocking(local_canister); + + // Tick for up to `shutdown_phase_max_rounds` times on the local subnet only + // or until the local canister has stopped. + for _ in 0..shutdown_phase_max_rounds { + match fixture.local_env.ingress_status(&msg_id) { + IngressStatus::Known { + state: IngressState::Completed(_), + .. + } => return Ok(()), + _ => { + fixture.local_env.tick(); + fixture + .local_env + .advance_time(std::time::Duration::from_secs(1)); + } } - assert!(counter < SHUTDOWN_PHASE_MAX_ROUNDS); } - // Check that the records agree on 'no pending calls'. - assert_eq!( - 0, - extract_metrics(&fixture.force_query_records(migrating_canister)).pending_calls - ); + fixture.failed_with_reason(format!( + "failed to stop local canister after {shutdown_phase_max_rounds} ticks" + )) } #[derive(Debug)] @@ -298,6 +365,13 @@ impl FixtureConfig { }, HypervisorConfig { subnet_message_memory_capacity: subnet_message_memory_capacity.into(), + embedders_config: EmbeddersConfig { + feature_flags: FeatureFlags { + best_effort_responses: FlagStatus::Enabled, + ..FeatureFlags::default() + }, + ..EmbeddersConfig::default() + }, ..HypervisorConfig::default() }, ) @@ -390,10 +464,10 @@ impl Fixture { unreachable!(); } - /// Helper function for setting canister state elements; returns the current element before - /// setting it. + /// Helper function for update calls to `canister`; returns the current `T` as it was before + /// this call. /// - /// Panics if `canister` is not installed in `Self`. + /// Panics if `canister` is not installed in `self`. fn set_canister_state(&self, canister: CanisterId, method: &str, item: T) -> T where T: candid::CandidType + for<'a> candid::Deserialize<'a>, @@ -408,35 +482,14 @@ impl Fixture { /// Sets the `CanisterConfig` in `canister`; returns the current config. /// - /// Panics if `canister` is not installed in `Self`. + /// Panics if `canister` is not installed in `self`. pub fn set_config(&self, canister: CanisterId, config: CanisterConfig) -> CanisterConfig { self.set_canister_state(canister, "set_config", config) } - /// Sets the `max_calls_per_heartbeat` in `canister`; returns the current value. - /// - /// Panics if `canister` is not installed in `Self`. - pub fn set_max_calls_per_heartbeat(&self, canister: CanisterId, count: u32) -> u32 { - self.set_canister_state(canister, "set_max_calls_per_heartbeat", count) - } - - /// Sets the `reply_weight` in `canister`; returns the current weight. - /// - /// Panics if `canister` is not installed in `Self`. - pub fn set_reply_weight(&self, canister: CanisterId, weight: u32) -> u32 { - self.set_canister_state(canister, "set_reply_weight", weight) - } - - /// Sets the `call_weight` in `canister`. - /// - /// Panics if `canister` is not installed in `Self`. - pub fn set_call_weight(&self, canister: CanisterId, weight: u32) -> u32 { - self.set_canister_state(canister, "set_call_weight", weight) - } - /// Seeds the `Rng` in `canister`. /// - /// Panics if `canister` is not installed in `Self`. + /// Panics if `canister` is not installed in `self`. pub fn seed_rng(&self, canister: CanisterId, seed: u64) { let msg = candid::Encode!(&seed).unwrap(); self.get_env(&canister) @@ -444,16 +497,9 @@ impl Fixture { .unwrap(); } - /// Sets `max_calls_per_heartbeat` on all canisters to the same value. - pub fn start_chatter(&self, max_calls_per_heartbeat: u32) { - for canister in self.canisters() { - self.set_max_calls_per_heartbeat(canister, max_calls_per_heartbeat); - } - } - /// Starts `canister`. /// - /// Panics if `canister` is not installed in `Self`. + /// Panics if `canister` is not installed in `self`. pub fn start_canister(&self, canister: CanisterId) { self.get_env(&canister).start_canister(canister).unwrap(); } @@ -463,40 +509,62 @@ impl Fixture { /// This function is asynchronous. It returns the ID of the ingress message /// that can be awaited later with [await_ingress]. /// - /// Panics if `canister` is not installed in `Self`. + /// Panics if `canister` is not installed in `self`. pub fn stop_canister_non_blocking(&self, canister: CanisterId) -> MessageId { self.get_env(&canister).stop_canister_non_blocking(canister) } - /// Queries the records from `canister`. + /// Calls the `stop_chatter()` function on `canister`. /// - /// Panics if `canister` is not installed in `Self`. - pub fn query_records( + /// This stops the canister from making calls, downstream and from the heartbeat. + pub fn stop_chatter(&self, canister: CanisterId) -> CanisterConfig { + let reply = self + .get_env(&canister) + .execute_ingress(canister, "stop_chatter", candid::Encode!().unwrap()) + .unwrap(); + candid::Decode!(&reply.bytes(), CanisterConfig).unwrap() + } + + /// Queries `canister` for `method`. + /// + /// Panics if `canister` is not installed in `self`. + pub fn query candid::Deserialize<'a>>( &self, canister: CanisterId, - ) -> Result, UserError> { - let dummy_msg = candid::Encode!().unwrap(); + method: &str, + ) -> Result { let reply = self .get_env(&canister) - .query(canister, "records", dummy_msg)?; - Ok(candid::Decode!(&reply.bytes(), BTreeMap).unwrap()) + .query(canister, method, candid::Encode!().unwrap())?; + Ok(candid::Decode!(&reply.bytes(), T).unwrap()) } - /// Force queries the records from `canister` by first attempting to query them; if it fails, start - /// the canister and try querying them again. + /// Force queries `canister` for `method` by first attempting to a normal query; if it fails, start + /// the canister and try again. /// - /// Panics if `canister` is not installed in `Self`. - pub fn force_query_records(&self, canister: CanisterId) -> BTreeMap { - match self.query_records(canister) { + /// Panics if `canister` is not installed in `self`. + pub fn force_query candid::Deserialize<'a>>( + &self, + canister: CanisterId, + method: &str, + ) -> T { + match self.query::(canister, method) { Err(_) => { self.start_canister(canister); - self.query_records(canister).unwrap() + self.query::(canister, method).unwrap() } Ok(records) => records, } } - /// Return the number of bytes taken by guaranteed response memory (`local_env`, `remote_env`). + /// Returns the latest state `canister` is located on. + /// + /// Panics if `canister` is not installed on either env. + pub fn get_latest_state(&self, canister: &CanisterId) -> Arc { + self.get_env(canister).get_latest_state() + } + + /// Returns the number of bytes taken by guaranteed response memory (`local_env`, `remote_env`). pub fn guaranteed_response_message_memory_taken(&self) -> (NumBytes, NumBytes) { ( self.local_env @@ -512,16 +580,16 @@ impl Fixture { /// upper limit. pub fn expect_guaranteed_response_message_memory_taken_at_most( &self, - label: &str, + label: impl std::fmt::Display, local_memory_upper_limit: u64, remote_memory_upper_limit: u64, ) -> Result<(), (String, DebugInfo)> { let (local_memory, remote_memory) = self.guaranteed_response_message_memory_taken(); if local_memory > local_memory_upper_limit.into() { - return self.failed_with_reason(format!("{label}: local memory exceeds limit")); + return self.failed_with_reason(format!("{}: local memory exceeds limit", label)); } if remote_memory > remote_memory_upper_limit.into() { - return self.failed_with_reason(format!("{label}: remote memory exceeds limit")); + return self.failed_with_reason(format!("{}: remote memory exceeds limit", label)); } Ok(()) } @@ -534,8 +602,7 @@ impl Fixture { .into_iter() .map(|canister| { let count = self - .get_env(&canister) - .get_latest_state() + .get_latest_state(&canister) .canister_states .get(&canister) .unwrap() @@ -550,6 +617,8 @@ impl Fixture { /// Executes one round on both the `local_env` and the `remote_env` by generating a XNet /// payload on one and inducting it into the other, and vice versa; if there are no XNet /// messages either way, performs a normal `tick()` on the receiving subnet. + /// + /// Advances time on each env by 1 second. pub fn tick(&self) { if let Ok(xnet_payload) = self.local_env.generate_xnet_payload( self.remote_env.get_subnet_id(), @@ -563,6 +632,8 @@ impl Fixture { } else { self.remote_env.tick(); } + self.remote_env + .advance_time(std::time::Duration::from_secs(1)); if let Ok(xnet_payload) = self.remote_env.generate_xnet_payload( self.local_env.get_subnet_id(), @@ -575,6 +646,60 @@ impl Fixture { } else { self.local_env.tick(); } + self.local_env + .advance_time(std::time::Duration::from_secs(1)); + } + + /// Ticks until all calls have concluded; i.e. there are no more open call contexts. + /// + /// Returns `Err(_)` if + /// - `perform_checks()` fails. + /// - any call fails to conclude after `max_ticks` ticks. + /// - there is still memory reserved after all calls have concluded. + pub fn tick_to_conclusion( + &self, + max_ticks: usize, + perform_checks: F, + ) -> Result<(), (String, DebugInfo)> + where + F: Fn(&Self) -> Result<(), (String, DebugInfo)>, + { + // Keep ticking until all calls are answered. + for _ in 0..max_ticks { + self.tick(); + + perform_checks(self)?; + + // Check for open call contexts. + if self.open_call_contexts_count().values().sum::() == 0 { + // Check the records agree on 'no pending calls'. + if self + .canisters() + .into_iter() + .map(|canister| extract_metrics(&self.force_query(canister, "records"))) + .any(|metrics| metrics.pending_calls != 0) + { + return self.failed_with_reason( + "no open call contexts but found pending calls in the records", + ); + } + + // One extra tick to make sure everything is gc'ed. + self.tick(); + + // After the fact, all memory is freed and back to 0. + return self.expect_guaranteed_response_message_memory_taken_at_most( + "Message memory used despite no open call contexts", + 0, + 0, + ); + } + } + + self.failed_with_reason(format!( + "failed to conclude calls after {} ticks", + max_ticks + )) } /// Migrates `canister` between `local_env` and `remote_env` (either direction). @@ -625,10 +750,9 @@ impl Fixture { records: self .canisters() .into_iter() - .map(|canister| (canister, self.force_query_records(canister))) + .map(|canister| (canister, self.force_query(canister, "records"))) .collect(), - latest_local_state: self.local_env.get_latest_state(), - latest_remote_state: self.remote_env.get_latest_state(), + fixture: self.clone(), }, )) } @@ -638,8 +762,7 @@ impl Fixture { #[allow(dead_code)] struct DebugInfo { pub records: BTreeMap>, - pub latest_local_state: Arc, - pub latest_remote_state: Arc, + pub fixture: Fixture, } /// Installs a 'random-traffic-test-canister' in `env`. diff --git a/rs/nns/governance/canbench/canbench_results.yml b/rs/nns/governance/canbench/canbench_results.yml index 7fc2d61ada9..911234a3a49 100644 --- a/rs/nns/governance/canbench/canbench_results.yml +++ b/rs/nns/governance/canbench/canbench_results.yml @@ -13,55 +13,55 @@ benches: scopes: {} add_neuron_inactive_maximum: total: - instructions: 112624643 + instructions: 112624637 heap_increase: 1 stable_memory_increase: 0 scopes: {} add_neuron_inactive_typical: total: - instructions: 8497304 + instructions: 8497298 heap_increase: 0 stable_memory_increase: 0 scopes: {} cascading_vote_all_heap: total: - instructions: 35002536 + instructions: 35004550 heap_increase: 0 stable_memory_increase: 128 scopes: {} cascading_vote_heap_neurons_stable_index: total: - instructions: 61137575 + instructions: 61139589 heap_increase: 0 stable_memory_increase: 128 scopes: {} cascading_vote_stable_everything: total: - instructions: 188621982 + instructions: 188616472 heap_increase: 0 stable_memory_increase: 128 scopes: {} cascading_vote_stable_neurons_with_heap_index: total: - instructions: 162353547 + instructions: 162348037 heap_increase: 0 stable_memory_increase: 128 scopes: {} centralized_following_all_stable: total: - instructions: 78268135 + instructions: 78266563 heap_increase: 0 stable_memory_increase: 128 scopes: {} compute_ballots_for_new_proposal_with_stable_neurons: total: - instructions: 2169168 + instructions: 2223231 heap_increase: 0 stable_memory_increase: 0 scopes: {} draw_maturity_from_neurons_fund_heap: total: - instructions: 7598030 + instructions: 7597898 heap_increase: 0 stable_memory_increase: 0 scopes: {} @@ -85,7 +85,7 @@ benches: scopes: {} list_neurons_heap: total: - instructions: 4763239 + instructions: 4708588 heap_increase: 9 stable_memory_increase: 0 scopes: {} @@ -103,13 +103,13 @@ benches: scopes: {} list_neurons_stable: total: - instructions: 113374022 + instructions: 113290234 heap_increase: 5 stable_memory_increase: 0 scopes: {} list_proposals: total: - instructions: 168095717 + instructions: 168095016 heap_increase: 0 stable_memory_increase: 0 scopes: {} @@ -127,13 +127,13 @@ benches: scopes: {} neuron_data_validation_heap: total: - instructions: 406864991 + instructions: 406862191 heap_increase: 0 stable_memory_increase: 0 scopes: {} neuron_data_validation_stable: total: - instructions: 362661286 + instructions: 362659179 heap_increase: 0 stable_memory_increase: 0 scopes: {} @@ -145,19 +145,19 @@ benches: scopes: {} neuron_metrics_calculation_stable: total: - instructions: 3027095 + instructions: 3026795 heap_increase: 0 stable_memory_increase: 0 scopes: {} range_neurons_performance: total: - instructions: 56448740 + instructions: 56447340 heap_increase: 0 stable_memory_increase: 0 scopes: {} single_vote_all_stable: total: - instructions: 2805838 + instructions: 2803120 heap_increase: 0 stable_memory_increase: 128 scopes: {} diff --git a/rs/rust_canisters/random_traffic_test/BUILD.bazel b/rs/rust_canisters/random_traffic_test/BUILD.bazel index 23eaf726e71..356e46469df 100644 --- a/rs/rust_canisters/random_traffic_test/BUILD.bazel +++ b/rs/rust_canisters/random_traffic_test/BUILD.bazel @@ -8,14 +8,15 @@ DEPENDENCIES = [ "//rs/types/error_types", "//rs/types/types", "@crate_index//:candid", - "@crate_index//:ic-cdk", + "@crate_index//:ic_cdk_next", "@crate_index//:futures", "@crate_index//:rand", "@crate_index//:serde", + "@crate_index//:serde_bytes", ] MACRO_DEPENDENCIES = [ - "@crate_index//:ic-cdk-macros", + "@crate_index//:ic_cdk_macros_next", ] rust_library( diff --git a/rs/rust_canisters/random_traffic_test/Cargo.toml b/rs/rust_canisters/random_traffic_test/Cargo.toml index 44814930525..58bf565c329 100644 --- a/rs/rust_canisters/random_traffic_test/Cargo.toml +++ b/rs/rust_canisters/random_traffic_test/Cargo.toml @@ -10,10 +10,11 @@ path = "src/main.rs" [dependencies] candid = { workspace = true } ic-base-types = { path = "../../types/base_types" } -ic-cdk = { workspace = true } -ic-cdk-macros = { workspace = true } +ic-cdk = { git = "https://github.com/dfinity/cdk-rs", rev = "4e287ce51636b0e70768c193da38d2fc5324ea15" } +ic-cdk-macros = { git = "https://github.com/dfinity/cdk-rs", rev = "4e287ce51636b0e70768c193da38d2fc5324ea15" } ic-error-types = { path = "../../types/error_types" } ic-types = { path = "../../types/types" } futures = { workspace = true } rand = { workspace = true } serde = { workspace = true } +serde_bytes = { workspace = true } diff --git a/rs/rust_canisters/random_traffic_test/src/lib.rs b/rs/rust_canisters/random_traffic_test/src/lib.rs index e36fda86353..9577671b003 100644 --- a/rs/rust_canisters/random_traffic_test/src/lib.rs +++ b/rs/rust_canisters/random_traffic_test/src/lib.rs @@ -5,31 +5,41 @@ use ic_types::messages::MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::ops::RangeInclusive; +use std::time::Duration; -/// A full config for generating random calls and replies. Ranges are stored as individual u32 +/// A full config for generating random calls and replies. Ranges are stored as `(u32, u32)` /// because ranges don't implement `CandidType`. #[derive(Serialize, Deserialize, Clone, Debug, CandidType, Hash)] pub struct Config { + /// A list of canister IDs, i.e. receivers for calls made by this canister. pub receivers: Vec, - pub call_bytes_min: u32, - pub call_bytes_max: u32, - pub reply_bytes_min: u32, - pub reply_bytes_max: u32, - pub instructions_count_min: u32, - pub instructions_count_max: u32, + /// `(min, max)` for the payload size in bytes included in a call. + pub call_bytes_range: (u32, u32), + /// `(min, max)` for the payload size in bytes included in a reply. + pub reply_bytes_range: (u32, u32), + /// `(min, max)` for the simulated number of instructions to generate a reply. + pub instructions_count_range: (u32, u32), + /// `(min, max)` for the timeout in seconds used for best-effort calls. + pub timeout_secs_range: (u32, u32), + /// The maximum number of calls attempted per heartbeat. + pub calls_per_heartbeat: u32, + /// The probability for a making a dowstream call rather than reply in %. + pub downstream_call_percentage: u32, + /// The probability for making a best-effort call rather a guaranteed response call in %. + pub best_effort_call_percentage: u32, } impl Default for Config { - /// Default using the full range of payloads and no delayed responses. fn default() -> Self { Self { receivers: vec![], - call_bytes_min: 0, - call_bytes_max: MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 as u32, - reply_bytes_min: 0, - reply_bytes_max: MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 as u32, - instructions_count_min: 0, - instructions_count_max: 0, + call_bytes_range: (0, MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 as u32), + reply_bytes_range: (0, MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 as u32), + instructions_count_range: (0, 0), + timeout_secs_range: (1, 100), + calls_per_heartbeat: 0, + downstream_call_percentage: 0, + best_effort_call_percentage: 100, } } } @@ -50,6 +60,10 @@ impl Config { call_bytes: RangeInclusive, reply_bytes: RangeInclusive, instructions_count: RangeInclusive, + timeout_secs: RangeInclusive, + calls_per_heartbeat: u32, + downstream_call_percentage: u32, + best_effort_call_percentage: u32, ) -> Result { // Sanity checks. After passing these, the canister should run as intended. if call_bytes.is_empty() { @@ -67,24 +81,28 @@ impl Config { if instructions_count.is_empty() { return Err("empty instructions_count range".to_string()); } + if timeout_secs.is_empty() { + return Err("empty timeout range".to_string()); + } Ok(Self { receivers, - call_bytes_min: *call_bytes.start(), - call_bytes_max: *call_bytes.end(), - reply_bytes_min: *reply_bytes.start(), - reply_bytes_max: *reply_bytes.end(), - instructions_count_min: *instructions_count.start(), - instructions_count_max: *instructions_count.end(), + call_bytes_range: (*call_bytes.start(), *call_bytes.end()), + reply_bytes_range: (*reply_bytes.start(), *reply_bytes.end()), + instructions_count_range: (*instructions_count.start(), *instructions_count.end()), + timeout_secs_range: (*timeout_secs.start(), *timeout_secs.end()), + calls_per_heartbeat, + downstream_call_percentage, + best_effort_call_percentage, }) } } /// Records the outcome of an outgoing call. #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, CandidType)] -pub enum Reply { - /// A response including a data payload of a distinct size was received. - Bytes(u32), +pub enum Response { + /// A reply including a data payload of a distinct size was received. + Reply(u32), /// The call was rejected with a reject code and a reject message. Reject(u32, String), } @@ -102,38 +120,54 @@ pub struct Record { pub call_depth: u32, /// The number of bytes included in the payload. pub sent_bytes: u32, - /// The kind of reply received, i.e. a payload or a reject response. - pub reply: Option, + /// The timeout in seconds set for a best-effort call; `None` for a guaranteed response call. + pub timeout_secs: Option, + /// The kind of response received, i.e. a reply with a payload or a reject response; + /// and the duration after which the response was received (from when the call was made). + pub duration_and_response: Option<(Duration, Response)>, } /// Human readable printer. impl std::fmt::Debug for Record { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match &self.caller { - None => write!( - f, - "{} ({:x}) | ", - &self.receiver.to_string()[..5], - self.call_tree_id, - ), - Some(caller) => write!( + write!( + f, + "{} ({:x}) ", + &self.receiver.to_string()[..5], + self.call_tree_id + )?; + + // A timeout indicates a best-effort message. + if let Some(timeout_secs) = self.timeout_secs { + write!(f, "timeout[s]: {} ", timeout_secs)?; + } + + // A caller indicates a downstream call. + if let Some(caller) = self.caller { + write!( f, - "{} ({:x}) (caller {} @ depth {}) | ", - &self.receiver.to_string()[..5], - self.call_tree_id, + "(caller {} @ depth {}) ", &caller.to_string()[..5], - self.call_depth, - ), - }?; + self.call_depth + )?; + } - write!(f, "sending {} bytes | ", self.sent_bytes)?; + write!(f, " sending {} bytes | ", self.sent_bytes)?; - match &self.reply { + match &self.duration_and_response { None => write!(f, "..."), - Some(Reply::Bytes(bytes)) => write!(f, "received {} bytes", bytes), - Some(Reply::Reject(error_code, error_msg)) => write!( + Some((call_duration, Response::Reply(bytes))) => { + write!( + f, + "duration[s]: {}, received {} bytes", + call_duration.as_secs(), + bytes + ) + } + Some((call_duration, Response::Reject(error_code, error_msg))) => write!( f, - "reject({}): {error_msg}", + "duration[s]: {}, reject({}): {error_msg}", + call_duration.as_secs(), RejectCode::try_from(*error_code as u64).unwrap(), ), } @@ -148,9 +182,11 @@ pub struct Metrics { pub downstream_calls_attempted: u32, pub calls_replied: u32, pub calls_rejected: u32, + pub calls_unknown_outcome: u32, pub sent_bytes: u32, pub received_bytes: u32, pub rejected_bytes: u32, + pub unknown_outcome_bytes: u32, } /// Extracts some basic metrics from the records. @@ -164,12 +200,18 @@ pub fn extract_metrics(records: &BTreeMap) -> Metrics { metrics.downstream_calls_attempted += 1; } - match &record.reply { - Some(Reply::Bytes(received_bytes)) => { + match &record.duration_and_response { + Some((_, Response::Reply(received_bytes))) => { metrics.calls_replied += 1; metrics.received_bytes += received_bytes; } - Some(Reply::Reject(_, _)) => { + Some((_, Response::Reject(reject_code, _))) + if RejectCode::try_from(*reject_code as u64).unwrap() == RejectCode::SysUnknown => + { + metrics.calls_unknown_outcome += 1; + metrics.unknown_outcome_bytes += record.sent_bytes; + } + Some((_, Response::Reject(..))) => { metrics.calls_rejected += 1; metrics.rejected_bytes += record.sent_bytes; } diff --git a/rs/rust_canisters/random_traffic_test/src/main.rs b/rs/rust_canisters/random_traffic_test/src/main.rs index dad560394f6..90ffa7e0669 100644 --- a/rs/rust_canisters/random_traffic_test/src/main.rs +++ b/rs/rust_canisters/random_traffic_test/src/main.rs @@ -1,7 +1,11 @@ -use candid::{CandidType, Encode}; +use candid::CandidType; use futures::future::select_all; use ic_base_types::{CanisterId, PrincipalId}; -use ic_cdk::{api, caller, id}; +use ic_cdk::{ + api, + call::{Call, CallError, CallResult, ConfigurableCall, SendableCall}, + setup, +}; use ic_cdk_macros::{heartbeat, init, query, update}; use rand::{ distributions::{Distribution, WeightedIndex}, @@ -15,29 +19,29 @@ use std::cell::{Cell, RefCell}; use std::collections::BTreeMap; use std::future::Future; use std::hash::{DefaultHasher, Hasher}; -use std::ops::RangeInclusive; +use std::time::Duration; thread_local! { - /// Random number generator used for determining payload sizes et.al. - static RNG: RefCell = RefCell::new(StdRng::seed_from_u64(13)); - /// Weight for making a reply used in a weighted binomial distribution. - static REPLY_WEIGHT: Cell = const { Cell::new(1) }; - /// Weight for making a downstream call used in a weighted binomial distribution. - static CALL_WEIGHT: Cell = const { Cell::new(0) }; /// A configuration holding parameters determining the canisters behavior, such as the range /// of payload bytes it should send. static CONFIG: RefCell = RefCell::default(); - /// The maximum number of calls each heartbeat will attempt to make. - static MAX_CALLS_PER_HEARTBEAT: Cell = Cell::default(); + /// Random number generator used for determining payload sizes et.al. + static RNG: RefCell = RefCell::new(StdRng::seed_from_u64(13)); /// A hasher used to generate unique call tree IDs. static HASHER: RefCell = RefCell::default(); /// An index for each attempted call; starts at 0 and then increments with each call. static CALL_INDEX: Cell = Cell::default(); - /// A collection of records; one record for each call. Keeps track of how each call went, - /// whether it was rejected or not and how many bytes were received. - static RECORDS: RefCell> = RefCell::default(); + /// A collection of timestamps and records; one record for each call. Keeps track of whether it was + /// rejected or not and how many bytes were sent and received. + static RECORDS: RefCell> = RefCell::default(); /// A counter for synchronous rejections. static SYNCHRONOUS_REJECTIONS_COUNT: Cell = Cell::default(); + /// A `COIN` that can be 'flipped' to determine whether to make a downstream call or not. + /// The default value set here will yield only 'reply'. + static DOWNSTREAM_CALL_COIN: RefCell> = RefCell::new(WeightedIndex::::new([0, 100]).unwrap()); + /// A `COIN` that can be 'flipped' to determine whether to make a best-effort call or a guaranteed response call. + /// The default value set here will yield only 'best_effort' + static BEST_EFFORT_CALL_COIN: RefCell> = RefCell::new(WeightedIndex::::new([100, 0]).unwrap()); } /// The intercanister message sent to `handle_call()` by the heartbeat of this canister @@ -48,38 +52,31 @@ struct Message { call_tree_id: u32, /// The depth of the call starting from 0 and incrementing by 1 for each downstream call. call_depth: u32, - /// A payload of a certain size; it otherwise does not any contain information. - payload: Vec, + /// Optional padding, to bring the payload to the desired byte size. + #[serde(with = "serde_bytes")] + padding: Vec, } impl Message { /// Creates a new `Message` of size `bytes_count`; may slightly exceed the target for /// very small numbers. - fn new(call_tree_id: u32, call_depth: u32, bytes_count: usize) -> Self { + fn new(call_tree_id: u32, call_depth: u32, bytes_count: u32) -> Self { Self { call_tree_id, call_depth, - payload: vec![0_u8; bytes_count.saturating_sub(std::mem::size_of::())], + padding: vec![0_u8; (bytes_count as usize).saturating_sub(std::mem::size_of::())], } } /// Returns the number of bytes the message consists of. fn count_bytes(&self) -> usize { - std::mem::size_of::() + self.payload.len() + std::mem::size_of::() + self.padding.len() } } -/// Returns a random receiver if any. -fn choose_receiver() -> Option { - CONFIG.with_borrow(|config| { - RNG.with_borrow_mut(|rng| config.receivers.as_slice().choose(rng).cloned()) - }) -} - -/// Generates a random `u32` contained in `range`. -fn gen_range(range: RangeInclusive) -> u32 { - RNG.with_borrow_mut(|rng| rng.gen_range(range)) -} +/// Wrapper around the reply from `handle_call()` such that `serde_bytes` can be used. +#[derive(Serialize, Deserialize, CandidType)] +struct Reply(#[serde(with = "serde_bytes")] Vec); /// Returns the next call index. fn next_call_index() -> u32 { @@ -97,48 +94,59 @@ fn next_call_tree_id() -> u32 { }) } -/// Returns `true` if `error_msg` corresponds to a synchronous rejection. -fn synchronous_rejection(error_msg: &str) -> bool { - error_msg.contains("Couldn't send message") +/// Generates a random `u32` by sampling `min..=max`. +fn sample((min, max): (u32, u32)) -> u32 { + RNG.with_borrow_mut(|rng| rng.gen_range(min..=max)) } -/// Returns the call bytes range as defined in `CONFIG`. -fn call_bytes_range() -> RangeInclusive { - CONFIG.with_borrow(|config| config.call_bytes_min..=config.call_bytes_max) +/// Generates a random payload size for a call. +fn gen_call_bytes() -> u32 { + CONFIG.with_borrow(|config| sample(config.call_bytes_range)) } -/// Returns the reply bytes range as defined in `CONFIG`. -fn reply_bytes_range() -> RangeInclusive { - CONFIG.with_borrow(|config| config.reply_bytes_min..=config.reply_bytes_max) +/// Generates a random payload size for a reply. +fn gen_reply_bytes() -> u32 { + CONFIG.with_borrow(|config| sample(config.reply_bytes_range)) } -/// Returns the instructions count range as defined in `CONFIG`. -fn instructions_count_range() -> RangeInclusive { - CONFIG.with_borrow(|config| config.instructions_count_min..=config.instructions_count_max) +/// Generates a random number of simulated instructions for generating a reply. +fn gen_instructions_count() -> u32 { + CONFIG.with_borrow(|config| sample(config.instructions_count_range)) } -/// Sets the test config; returns the current config. -#[update] -fn set_config(config: Config) -> Config { - CONFIG.replace(config) +/// Generates a timeout in seconds for a best-effort call. +fn gen_timeout_secs() -> u32 { + CONFIG.with_borrow(|config| sample(config.timeout_secs_range)) } -/// Sets the requests per round to be sent each heart beat; returns the current value. -#[update] -fn set_max_calls_per_heartbeat(max_calls_per_heartbeat: u32) -> u32 { - MAX_CALLS_PER_HEARTBEAT.replace(max_calls_per_heartbeat) +/// Picks a random receiver from `config.receivers` if any; otherwise return own canister ID. +fn receiver() -> CanisterId { + match CONFIG.with_borrow(|config| { + RNG.with_borrow_mut(|rng| config.receivers.as_slice().choose(rng).cloned()) + }) { + Some(receiver) => receiver, + None => CanisterId::try_from(api::canister_self().as_slice()).unwrap(), + } } -/// Sets the reply weight; returns the current weight. +/// Sets the test config; returns the current config. #[update] -fn set_reply_weight(reply_weight: u32) -> u32 { - REPLY_WEIGHT.replace(reply_weight) -} +fn set_config(config: Config) -> Config { + fn to_weights(mut percentage: u32) -> [u32; 2] { + if percentage > 100 { + percentage = 100; + } + [percentage, 100 - percentage] + } -/// Sets the call weight; returns the current weight. -#[update] -fn set_call_weight(call_weight: u32) -> u32 { - CALL_WEIGHT.replace(call_weight) + // Update `COINS`. + DOWNSTREAM_CALL_COIN + .replace(WeightedIndex::::new(to_weights(config.downstream_call_percentage)).unwrap()); + BEST_EFFORT_CALL_COIN.replace( + WeightedIndex::::new(to_weights(config.best_effort_call_percentage)).unwrap(), + ); + + CONFIG.replace(config) } /// Seeds `RNG`. @@ -147,10 +155,26 @@ fn seed_rng(seed: u64) { RNG.with_borrow_mut(|rng| *rng = StdRng::seed_from_u64(seed)); } +/// Sets `CONFIG` such that the canister stops making calls altogether; returns the config +/// prior to modifying. +#[update] +fn stop_chatter() -> Config { + set_config(CONFIG.with_borrow(|config| Config { + calls_per_heartbeat: 0, + downstream_call_percentage: 0, + ..config.clone() + })) +} + /// Returns the canister records. #[query] fn records() -> BTreeMap { - RECORDS.with_borrow(|records| records.clone()) + RECORDS.with_borrow(|records| { + records + .iter() + .map(|(index, (_, record))| (*index, record.clone())) + .collect() + }) } /// Returns the number of synchronous rejections. @@ -159,85 +183,126 @@ fn synchronous_rejections_count() -> u32 { SYNCHRONOUS_REJECTIONS_COUNT.get() } +/// Flip the `DOWNSTREAM_CALL_COIN` to determine whether we should make a downstream call or reply +/// instead. +fn should_make_downstream_call() -> bool { + RNG.with_borrow_mut(|rng| DOWNSTREAM_CALL_COIN.with_borrow(|coin| coin.sample(rng)) == 0) +} + +/// Flip the `BEST_EFFORT_COIN` to determine whether we should make a best-effort call or a +/// guaranteed response call. +fn should_make_best_effort_call() -> bool { + RNG.with_borrow_mut(|rng| BEST_EFFORT_CALL_COIN.with_borrow(|coin| coin.sample(rng)) == 0) +} + /// Generates a future for a randomized call that can be awaited; inserts a new record at `index` -/// that must updated (or removed) after awaiting the call. For each call, the call index is +/// that must be updated (or removed) after awaiting the call. For each call, the call index is /// incremented by 1, such that successive calls have adjacent indices. fn setup_call( - receiver: CanisterId, call_tree_id: u32, call_depth: u32, -) -> (impl Future>>, u32) { - let msg = Message::new( - call_tree_id, - call_depth, - gen_range(call_bytes_range()) as usize, - ); +) -> (impl Future>, u32) { + let msg = Message::new(call_tree_id, call_depth, gen_call_bytes()); + let sent_bytes = msg.count_bytes() as u32; + let receiver = receiver(); + let caller = (call_depth > 0).then_some(CanisterId::unchecked_from_principal(PrincipalId( + api::msg_caller(), + ))); + let timeout_secs = should_make_best_effort_call().then_some(gen_timeout_secs()); + + let call = Call::new(receiver.into(), "handle_call"); + let call = call.with_arg(msg); + let call = match timeout_secs { + Some(timeout_secs) => call.change_timeout(timeout_secs), + None => call.with_guaranteed_response(), + }; - // Inserts a new call record at the next `index`. + // Once the call was successfully generated, insert a call timestamp and record at `index`. let index = next_call_index(); RECORDS.with_borrow_mut(|records| { records.insert( index, - Record { - receiver, - caller: (call_depth > 0) - .then_some(CanisterId::unchecked_from_principal(PrincipalId(caller()))), - call_tree_id, - call_depth, - sent_bytes: msg.count_bytes() as u32, - reply: None, - }, - ); + ( + api::time(), + Record { + receiver, + caller, + call_tree_id, + call_depth, + sent_bytes, + timeout_secs, + duration_and_response: None, + }, + ), + ) }); - let future = api::call::call_raw(receiver.into(), "handle_call", Encode!(&msg).unwrap(), 0); - - (future, index) + (call.call(), index) } -/// Updates the record at `index` using the `response` to the corresponding call. +/// Updates the record at `index` using the `result` of the corresponding call. /// /// Removes the record for a synchronous rejection since those can be quite numerous when the /// subnet is at its limits. Note that since the call `index` is part of the records, removing /// the records for synchronous rejections will result in gaps in these numbers thus they are /// still included indirectly. -fn update_record(response: &api::call::CallResult>, index: u32) { - // Updates the `Reply` at `index` in `RECORDS`. - let set_reply_in_call_record = move |reply: Reply| { +fn update_record(result: &CallResult, index: u32) { + // Updates the `Response` at `index` in `RECORDS`. + let set_reply_in_call_record = move |response: Response| { RECORDS.with_borrow_mut(|records| { - let record = records.get_mut(&index).unwrap(); - assert!(record.reply.is_none(), "duplicate reply received"); - record.reply = Some(reply); + let (call_timestamp, record) = records.get_mut(&index).unwrap(); + assert!( + record.duration_and_response.is_none(), + "duplicate reply received" + ); + let response_timestamp = api::time(); + assert!( + response_timestamp >= *call_timestamp, + "retrograde blocktime" + ); + + let call_duration = Duration::from_nanos(response_timestamp - *call_timestamp); + record.duration_and_response = Some((call_duration, response)); }); }; - match response { - Err((_, msg)) if synchronous_rejection(msg) => { + + match result { + Err(CallError::CallRejected(rejection)) if rejection.is_sync() => { // Remove the record for synchronous rejections. SYNCHRONOUS_REJECTIONS_COUNT.set(SYNCHRONOUS_REJECTIONS_COUNT.get() + 1); RECORDS.with_borrow_mut(|records| { - assert!(records.remove(&index).unwrap().reply.is_none()) + assert!(records + .remove(&index) + .unwrap() + .1 + .duration_and_response + .is_none()) }); } - Err((reject_code, msg)) => { - set_reply_in_call_record(Reply::Reject(*reject_code as u32, msg.to_string())); + Err(CallError::CallRejected(rejection)) => { + set_reply_in_call_record(Response::Reject( + rejection.reject_code().into(), + rejection.reject_message().to_string(), + )); } - Ok(result) => { - set_reply_in_call_record(Reply::Bytes(result.len() as u32)); + Err(CallError::CandidDecodeFailed(error)) => { + unreachable!("{error}"); + } + + Ok(reply) => { + set_reply_in_call_record(Response::Reply(reply.0.len() as u32)); } } } -/// Generates `MAX_CALLS_PER_HEARTBEAT` calls as futures. The records are updated whenever a -/// reply comes in, i.e. not only after all of them have completed. +/// Generates `calls_per_heartbeat` call futures. They are awaited; whenever a call concludes +/// its record is updated (or removed in case of a synchronous rejection). #[heartbeat] async fn heartbeat() { let (mut futures, mut record_indices) = (Vec::new(), Vec::new()); - for _ in 0..MAX_CALLS_PER_HEARTBEAT.get() { - let Some(receiver) = choose_receiver() else { - return; - }; - let (future, index) = setup_call(receiver, next_call_tree_id(), 0); - futures.push(future); + for _ in 0..CONFIG.with_borrow(|config| config.calls_per_heartbeat) { + let (future, index) = setup_call(next_call_tree_id(), 0); + futures.push(Box::pin(future)); record_indices.push(index); } @@ -256,28 +321,13 @@ async fn heartbeat() { /// Handles incoming calls; this method is called from the heartbeat method. /// /// Replies if: -/// - sampling the weighted binomial distribution tells us to do so. +/// - flipping the coin tells us to do so. /// - if it tells us not to do so but the attempted downstream call fails for any reason. #[update] -async fn handle_call(msg: Message) -> Vec { - // Samples a weighted binomial distribution to decide whether to make a downstream call (true) - // or reply (false). Defaults to `false` for bad weights (e.g. both 0). - fn should_make_downstream_call() -> bool { - RNG.with_borrow_mut(|rng| { - WeightedIndex::new([CALL_WEIGHT.get(), REPLY_WEIGHT.get()]) - .is_ok_and(|dist| dist.sample(rng) == 0) - }) - } - - // Make downstream calls until - // - sampling the distribution tells us to stop. - // - setting up a call fails. - // - a downstream call is rejected for any reason. +async fn handle_call(msg: Message) -> Reply { + // Make downstream calls as long as flipping the coin tells us to do so. while should_make_downstream_call() { - let Some(receiver) = choose_receiver() else { - break; - }; - let (future, record_index) = setup_call(receiver, msg.call_tree_id, msg.call_depth + 1); + let (future, record_index) = setup_call(msg.call_tree_id, msg.call_depth + 1); let result = future.await; update_record(&result, record_index); @@ -289,20 +339,21 @@ async fn handle_call(msg: Message) -> Vec { } } - let payload_bytes = gen_range(reply_bytes_range()); - let instructions_count = gen_range(instructions_count_range()); + let payload_bytes = gen_reply_bytes(); // Do some thinking. - let counts = api::performance_counter(0) + instructions_count as u64; + let counts = api::performance_counter(0) + gen_instructions_count() as u64; while counts > api::performance_counter(0) {} - vec![0_u8; payload_bytes as usize] + Reply(vec![0_u8; payload_bytes as usize]) } /// Initializes the `HASHER` by hashing our own canister ID. #[init] fn initialize_hasher() { - HASHER.with_borrow_mut(|hasher| hasher.write(id().as_slice())); + HASHER.with_borrow_mut(|hasher| hasher.write(api::canister_self().as_slice())); } -fn main() {} +fn main() { + setup(); +}