diff --git a/Cargo.lock b/Cargo.lock index 147be8c5..72a6c5d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cf008e5e1a9e9e22a7d3c9a4992e21a350290069e36d8fb72304ed17e8f2d2" +checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" dependencies = [ "brotli", "flate2", @@ -363,9 +363,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" +checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7" dependencies = [ "arrayref", "arrayvec 0.7.6", @@ -405,11 +405,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ - "borsh-derive 1.5.5", + "borsh-derive 1.5.7", "cfg_aliases", ] @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate 3.3.0", @@ -515,9 +515,9 @@ checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "2ff22c2722516255d1823ce3cc4bc0b154dbc9364be5c905d6baa6eccbbc8774" dependencies = [ "proc-macro2", "quote", @@ -680,9 +680,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "jobserver", "libc", @@ -1065,9 +1065,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", ] @@ -1347,18 +1347,18 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "five8_const" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b4f62f0f8ca357f93ae90c8c2dd1041a1f665fde2f889ea9b1787903829015" +checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" dependencies = [ "five8_core", ] [[package]] name = "five8_core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94474d15a76982be62ca8a39570dccce148d98c238ebb7408b0a21b2c4bdddc4" +checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" [[package]] name = "fixedbitset" @@ -1537,9 +1537,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1835,8 +1837,8 @@ dependencies = [ "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.23", - "rustls-native-certs 0.8.1", + "rustls 0.23.25", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -1926,6 +1928,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ic-metrics-encoder" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5c7628eac357aecda461130f8074468be5aa4d258a002032d82d817f79f1f8" + [[package]] name = "ic-sha3" version = "1.0.0" @@ -2032,9 +2040,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -2056,9 +2064,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -2077,9 +2085,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -2200,16 +2208,18 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -2390,9 +2400,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "logos" @@ -2940,7 +2950,7 @@ dependencies = [ "hex", "ic-certification", "ic-transport-types", - "reqwest 0.12.14", + "reqwest 0.12.15", "schemars", "serde", "serde_bytes", @@ -2976,7 +2986,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.23", + "zerocopy 0.8.24", ] [[package]] @@ -3075,34 +3085,36 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.23", + "rustls 0.23.25", "socket2", "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.2", + "rand 0.9.0", "ring", "rustc-hash", - "rustls 0.23.23", + "rustls 0.23.25", "rustls-pki-types", "rustls-platform-verifier", "slab", @@ -3114,9 +3126,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ "cfg_aliases", "libc", @@ -3165,6 +3177,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -3185,6 +3208,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3203,6 +3236,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3360,9 +3402,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64 0.22.1", "bytes", @@ -3385,8 +3427,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.23", - "rustls-native-certs 0.8.1", + "rustls 0.23.25", + "rustls-native-certs", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -3469,9 +3511,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags 2.9.0", "errno", @@ -3494,31 +3536,18 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -3528,7 +3557,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework", ] [[package]] @@ -3560,23 +3589,23 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c7dc240fec5517e6c4eab3310438636cfe6391dfc345ba013109909a90d136" +checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" dependencies = [ - "core-foundation 0.9.4", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.23", - "rustls-native-certs 0.7.3", + "rustls 0.23.25", + "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.102.8", - "security-framework 2.11.1", + "rustls-webpki 0.103.1", + "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3597,9 +3626,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -3688,20 +3717,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "num-bigint 0.4.6", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.2.0" @@ -4004,6 +4019,7 @@ dependencies = [ "hex", "http 1.3.1", "ic-cdk", + "ic-metrics-encoder", "ic-sha3", "ic-stable-structures", "maplit", @@ -4179,7 +4195,7 @@ version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ "borsh 0.10.4", - "borsh 1.5.5", + "borsh 1.5.7", ] [[package]] @@ -4434,7 +4450,7 @@ name = "solana-hash" version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ - "borsh 1.5.5", + "borsh 1.5.7", "bs58", "bytemuck", "bytemuck_derive", @@ -4464,7 +4480,7 @@ version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ "bincode", - "borsh 1.5.5", + "borsh 1.5.7", "num-traits", "serde", "serde_derive", @@ -4708,7 +4724,7 @@ dependencies = [ "bincode", "blake3", "borsh 0.10.4", - "borsh 1.5.5", + "borsh 1.5.7", "bs58", "bytemuck", "console_error_panic_hook", @@ -4793,7 +4809,7 @@ name = "solana-program-error" version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ - "borsh 1.5.5", + "borsh 1.5.7", "num-traits", "serde", "serde_derive", @@ -4831,7 +4847,7 @@ version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ "borsh 0.10.4", - "borsh 1.5.5", + "borsh 1.5.7", "bs58", "bytemuck", "bytemuck_derive", @@ -4886,7 +4902,7 @@ dependencies = [ "log", "quinn", "quinn-proto", - "rustls 0.23.23", + "rustls 0.23.25", "solana-connection-cache", "solana-keypair", "solana-measure", @@ -5191,7 +5207,7 @@ dependencies = [ "quinn", "quinn-proto", "rand 0.8.5", - "rustls 0.23.23", + "rustls 0.23.25", "smallvec", "socket2", "solana-keypair", @@ -5309,7 +5325,7 @@ name = "solana-tls-utils" version = "2.2.0" source = "git+https://github.com/lpahlavi/agave.git#fa0bacd5508c1e443db79a67d470c537fa558de0" dependencies = [ - "rustls 0.23.23", + "rustls 0.23.25", "solana-keypair", "solana-pubkey", "solana-signer", @@ -5510,9 +5526,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9156ebd5870ef293bfb43f91c7a74528d363ec0d424afe24160ed5a4343d08a" +checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" dependencies = [ "cc", "cfg-if", @@ -5681,9 +5697,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", @@ -5764,9 +5780,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -5779,15 +5795,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -5872,7 +5888,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.23", + "rustls 0.23.25", "tokio", ] @@ -6430,9 +6446,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" @@ -6447,9 +6463,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] @@ -6463,6 +6479,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6490,6 +6515,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6537,6 +6577,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6555,6 +6601,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6573,6 +6625,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6603,6 +6661,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6621,6 +6685,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6639,6 +6709,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6657,6 +6733,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -6774,11 +6856,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive 0.8.24", ] [[package]] @@ -6794,9 +6876,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", @@ -6877,18 +6959,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index b261b732..b532ae82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ http = "1.2.0" ic-canister-log = "0.2.0" ic-cdk = "0.17.1" ic-ed25519 = "0.1.0" +ic-metrics-encoder = "1.1" ic-sha3 = "1.0.0" ic-stable-structures = "0.6.7" ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", tag = "release-2025-01-23_03-04-base" } diff --git a/canister/Cargo.toml b/canister/Cargo.toml index ba43410c..6a274949 100644 --- a/canister/Cargo.toml +++ b/canister/Cargo.toml @@ -24,6 +24,7 @@ futures = { workspace = true } hex = { workspace = true } http = { workspace = true } ic-cdk = { workspace = true } +ic-metrics-encoder = { workspace = true } ic-sha3 = { workspace = true } ic-stable-structures = { workspace = true } maplit = { workspace = true } diff --git a/canister/src/candid_rpc/mod.rs b/canister/src/candid_rpc/mod.rs index 0d03efe9..5610531f 100644 --- a/canister/src/candid_rpc/mod.rs +++ b/canister/src/candid_rpc/mod.rs @@ -1,19 +1,38 @@ -use crate::rpc_client::{ReducedResult, SolRpcClient}; +use crate::{ + add_metric_entry, + metrics::RpcMethod, + providers::get_provider, + rpc_client::{ReducedResult, SolRpcClient}, + util::hostname_from_url, +}; use canhttp::multi::ReductionError; use serde::Serialize; -use sol_rpc_types::{GetSlotParams, MultiRpcResult, RpcConfig, RpcResult, RpcSources}; +use sol_rpc_types::{ + GetSlotParams, MultiRpcResult, RpcAccess, RpcAuth, RpcConfig, RpcResult, RpcSource, RpcSources, + SupportedRpcProvider, +}; use solana_clock::Slot; use std::fmt::Debug; -fn process_result(result: ReducedResult) -> MultiRpcResult { +fn process_result(method: RpcMethod, result: ReducedResult) -> MultiRpcResult { match result { Ok(value) => MultiRpcResult::Consistent(Ok(value)), Err(err) => match err { ReductionError::ConsistentError(err) => MultiRpcResult::Consistent(Err(err)), ReductionError::InconsistentResults(multi_call_results) => { let results: Vec<_> = multi_call_results.into_iter().collect(); - results.iter().for_each(|(_service, _service_result)| { - // TODO XC-296: Add metrics for inconsistent providers + results.iter().for_each(|(source, _service_result)| { + if let RpcSource::Supported(provider_id) = source { + if let Some(provider) = get_provider(provider_id) { + if let Some(host) = hostname(provider.clone()) { + add_metric_entry!( + inconsistent_responses, + (method.into(), host.into()), + 1 + ) + } + } + } }); MultiRpcResult::Inconsistent(results) } @@ -21,6 +40,17 @@ fn process_result(result: ReducedResult) -> MultiRpcResult { } } +pub fn hostname(provider: SupportedRpcProvider) -> Option { + let url = match provider.access { + RpcAccess::Authenticated { auth, .. } => match auth { + RpcAuth::BearerToken { url } => url, + RpcAuth::UrlParameter { url_pattern } => url_pattern, + }, + RpcAccess::Unauthenticated { public_url } => public_url, + }; + hostname_from_url(url.as_str()) +} + /// Adapt the `EthRpcClient` to the `Candid` interface used by the EVM-RPC canister. pub struct CandidRpcClient { client: SolRpcClient, @@ -34,7 +64,7 @@ impl CandidRpcClient { } pub async fn get_slot(&self, params: GetSlotParams) -> MultiRpcResult { - process_result(self.client.get_slot(params).await) + process_result(RpcMethod::GetSlot, self.client.get_slot(params).await) } pub async fn raw_request( @@ -44,6 +74,6 @@ impl CandidRpcClient { where I: Serialize + Clone + Debug, { - process_result(self.client.raw_request(request).await) + process_result(RpcMethod::Generic, self.client.raw_request(request).await) } } diff --git a/canister/src/http/mod.rs b/canister/src/http/mod.rs index 74efc734..9f6302f4 100644 --- a/canister/src/http/mod.rs +++ b/canister/src/http/mod.rs @@ -1,23 +1,28 @@ mod errors; use crate::{ + add_metric_entry, constants::{COLLATERAL_CYCLES_PER_NODE, CONTENT_TYPE_VALUE}, http::errors::HttpClientError, logs::Priority, - state::{next_request_id, read_state, State}, + memory::next_request_id, + memory::{read_state, State}, + metrics::{MetricRpcHost, MetricRpcMethod}, }; use canhttp::{ convert::ConvertRequestLayer, http::{ json::{ - CreateJsonRpcIdFilter, HttpJsonRpcRequest, HttpJsonRpcResponse, JsonRequestConverter, + ConsistentResponseIdFilterError, CreateJsonRpcIdFilter, HttpJsonRpcRequest, + HttpJsonRpcResponse, Id, JsonRequestConverter, JsonResponseConversionError, JsonResponseConverter, }, - FilterNonSuccessfulHttpResponse, HttpRequestConverter, HttpResponseConverter, + FilterNonSuccessfulHttpResponse, FilterNonSuccessfulHttpResponseError, + HttpRequestConverter, HttpResponseConverter, }, observability::ObservabilityLayer, retry::DoubleMaxResponseBytes, - ConvertServiceBuilder, CyclesAccounting, CyclesChargingPolicy, + ConvertServiceBuilder, CyclesAccounting, CyclesChargingPolicy, IcError, }; use canlog::log; use http::{header::CONTENT_TYPE, HeaderValue}; @@ -34,6 +39,7 @@ use tower::{ use tower_http::{set_header::SetRequestHeaderLayer, ServiceBuilderExt}; pub fn http_client( + rpc_method: MetricRpcMethod, retry: bool, ) -> impl Service, Response = HttpJsonRpcResponse, Error = RpcError> where @@ -54,25 +60,91 @@ where .map_err(|e: HttpClientError| RpcError::from(e)) .option_layer(maybe_retry) .option_layer(maybe_unique_id) - // TODO XC-296: Flesh out observability layer .layer( ObservabilityLayer::new() .on_request(move |req: &HttpJsonRpcRequest| { - log!( - Priority::TraceHttp, - "JSON-RPC request with id `{}` to {}: {:?}", - req.body().id().clone(), - req.uri().host().unwrap().to_string(), + let req_data = MetricData { + method: rpc_method.clone(), + host: MetricRpcHost(req.uri().host().unwrap().to_string()), + request_id: req.body().id().clone(), + }; + add_metric_entry!( + requests, + (req_data.method.clone(), req_data.host.clone()), + 1 + ); + log!(Priority::TraceHttp, "JSON-RPC request with id `{}` to {}: {:?}", + req_data.request_id, + req_data.host.0, req.body() ); + req_data }) - .on_response(|_req_data: (), response: &HttpJsonRpcResponse| { + .on_response(|req_data: MetricData, response: &HttpJsonRpcResponse| { + observe_response(req_data.method, req_data.host, response.status().as_u16()); log!( Priority::TraceHttp, - "JSON-RPC response: {:?}", + "Got response for request with id `{}`. Response with status {}: {:?}", + req_data.request_id, + response.status(), response.body() ); - }), + }) + .on_error( + |req_data: MetricData, error: &HttpClientError| match error { + HttpClientError::IcError(IcError { code, message: _ }) => { + add_metric_entry!( + err_http_outcall, + (req_data.method, req_data.host, *code), + 1 + ); + } + HttpClientError::UnsuccessfulHttpResponse( + FilterNonSuccessfulHttpResponseError::UnsuccessfulResponse(response), + ) => { + observe_response( + req_data.method, + req_data.host, + response.status().as_u16(), + ); + log!( + Priority::TraceHttp, + "Unsuccessful HTTP response for request with id `{}`. Response with status {}: {}", + req_data.request_id, + response.status(), + String::from_utf8_lossy(response.body()) + ); + } + HttpClientError::InvalidJsonResponse( + JsonResponseConversionError::InvalidJsonResponse { + status, + body: _, + parsing_error: _, + }, + ) => { + observe_response(req_data.method, req_data.host, *status); + log!( + Priority::TraceHttp, + "Invalid JSON RPC response for request with id `{}`: {}", + req_data.request_id, + error + ); + } + HttpClientError::InvalidJsonResponseId(ConsistentResponseIdFilterError::InconsistentId { status, request_id: _, response_id: _ }) => { + observe_response(req_data.method, req_data.host, *status); + log!( + Priority::TraceHttp, + "Invalid JSON RPC response for request with id `{}`: {}", + req_data.request_id, + error + ); + } + HttpClientError::NotHandledError(e) => { + log!(Priority::Info, "BUG: Unexpected error: {}", e); + } + HttpClientError::CyclesAccountingError(_) => {} + }, + ), ) .filter_response(CreateJsonRpcIdFilter::new()) .layer(service_request_builder()) @@ -92,6 +164,17 @@ fn generate_request_id(request: HttpJsonRpcRequest) -> HttpJsonRpcRequest< http::Request::from_parts(parts, body) } +fn observe_response(method: MetricRpcMethod, host: MetricRpcHost, status: u16) { + let status: u32 = status as u32; + add_metric_entry!(responses, (method, host, status.into()), 1); +} + +struct MetricData { + method: MetricRpcMethod, + host: MetricRpcHost, + request_id: Id, +} + type JsonRpcServiceBuilder = ServiceBuilder< Stack< ConvertRequestLayer, diff --git a/canister/src/lib.rs b/canister/src/lib.rs index f0050f6c..9e7d6ade 100644 --- a/canister/src/lib.rs +++ b/canister/src/lib.rs @@ -4,9 +4,10 @@ pub mod http; pub mod http_types; pub mod lifecycle; pub mod logs; +pub mod memory; +pub mod metrics; pub mod providers; pub mod rpc_client; -pub mod state; pub mod types; pub mod util; pub mod validate; diff --git a/canister/src/lifecycle/mod.rs b/canister/src/lifecycle/mod.rs index bf6b7c72..f43badf7 100644 --- a/canister/src/lifecycle/mod.rs +++ b/canister/src/lifecycle/mod.rs @@ -1,6 +1,6 @@ use crate::{ logs::Priority, - state::{init_state, mutate_state, State}, + memory::{init_state, mutate_state, State}, }; use canlog::log; use sol_rpc_types::InstallArgs; diff --git a/canister/src/logs/mod.rs b/canister/src/logs/mod.rs index 4a608a7f..9281ddaf 100644 --- a/canister/src/logs/mod.rs +++ b/canister/src/logs/mod.rs @@ -1,4 +1,4 @@ -use crate::state::read_state; +use crate::memory::read_state; use canlog::{GetLogFilter, LogFilter, LogPriorityLevels}; use serde::{Deserialize, Serialize}; use std::str::FromStr; diff --git a/canister/src/main.rs b/canister/src/main.rs index 4db70a1e..16920a94 100644 --- a/canister/src/main.rs +++ b/canister/src/main.rs @@ -2,12 +2,14 @@ use candid::candid_method; use canhttp::http::json::JsonRpcRequest; use canlog::{log, Log, Sort}; use ic_cdk::{api::is_controller, query, update}; +use ic_metrics_encoder::MetricsEncoder; use sol_rpc_canister::{ candid_rpc::CandidRpcClient, http_types, lifecycle, logs::Priority, + memory::{mutate_state, read_state}, + metrics::encode_metrics, providers::{get_provider, PROVIDERS}, - state::{mutate_state, read_state}, }; use sol_rpc_types::{ GetSlotParams, MultiRpcResult, RpcAccess, RpcConfig, RpcError, RpcSources, @@ -113,6 +115,21 @@ async fn request( #[query(hidden = true)] fn http_request(request: http_types::HttpRequest) -> http_types::HttpResponse { match request.path() { + "/metrics" => { + let mut writer = MetricsEncoder::new(vec![], ic_cdk::api::time() as i64 / 1_000_000); + + match encode_metrics(&mut writer) { + Ok(()) => http_types::HttpResponseBuilder::ok() + .header("Content-Type", "text/plain; version=0.0.4") + .with_body_and_content_length(writer.into_inner()) + .build(), + Err(err) => http_types::HttpResponseBuilder::server_error(format!( + "Failed to encode metrics: {}", + err + )) + .build(), + } + } "/logs" => { let max_skip_timestamp = match request.raw_query_param("time") { Some(arg) => match u64::from_str(arg) { diff --git a/canister/src/state/mod.rs b/canister/src/memory/mod.rs similarity index 85% rename from canister/src/state/mod.rs rename to canister/src/memory/mod.rs index 98cc752a..04a0d15d 100644 --- a/canister/src/state/mod.rs +++ b/canister/src/memory/mod.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests; +use crate::metrics::Metrics; use crate::types::{ApiKey, OverrideProvider}; use candid::{Deserialize, Principal}; use canhttp::http::json::Id; @@ -20,6 +21,7 @@ type StableMemory = VirtualMemory; thread_local! { // Unstable static data: these are reset when the canister is upgraded. + pub static UNSTABLE_METRICS: RefCell = RefCell::new(Metrics::default()); static UNSTABLE_HTTP_REQUEST_COUNTER: RefCell = const {RefCell::new(0)}; // Stable static data: these are preserved when the canister is upgraded. @@ -30,14 +32,14 @@ thread_local! { MEMORY_MANAGER.with_borrow(|m| m.get(STATE_MEMORY_ID)), ConfigState::default(), ) - .expect("Unable to read state from stable memory"), + .expect("Unable to read memory from stable memory"), ); } -/// Configuration state of the ledger orchestrator. +/// Configuration memory of the ledger orchestrator. #[derive(Clone, PartialEq, Debug, Default)] pub enum ConfigState { - // This state is only used between wasm module initialization and init(). + // This memory is only used between wasm module initialization and init(). #[default] Uninitialized, Initialized(State), @@ -46,7 +48,7 @@ pub enum ConfigState { impl ConfigState { fn expect_initialized(&self) -> &State { match &self { - ConfigState::Uninitialized => ic_cdk::trap("BUG: state not initialized"), + ConfigState::Uninitialized => ic_cdk::trap("BUG: memory not initialized"), ConfigState::Initialized(s) => s, } } @@ -72,13 +74,13 @@ impl Storable for ConfigState { fn encode(state: &S) -> Vec { let mut buf = vec![]; - ciborium::ser::into_writer(state, &mut buf).expect("failed to encode state"); + ciborium::ser::into_writer(state, &mut buf).expect("failed to encode memory"); buf } fn decode(bytes: &[u8]) -> T { ciborium::de::from_reader(bytes) - .unwrap_or_else(|e| panic!("failed to decode state bytes {}: {e}", hex::encode(bytes))) + .unwrap_or_else(|e| panic!("failed to decode memory bytes {}: {e}", hex::encode(bytes))) } #[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -169,9 +171,9 @@ pub fn read_state(f: impl FnOnce(&State) -> R) -> R { STATE.with(|cell| f(cell.borrow().get().expect_initialized())) } -/// Mutates (part of) the current state using `f`. +/// Mutates (part of) the current memory using `f`. /// -/// Panics if there is no state. +/// Panics if there is no memory. pub fn mutate_state(f: F) -> R where F: FnOnce(&mut State) -> R, @@ -182,7 +184,7 @@ where let result = f(&mut state); borrowed .set(ConfigState::Initialized(state)) - .expect("failed to write state in stable cell"); + .expect("failed to write memory in stable cell"); result }) } @@ -198,17 +200,17 @@ pub fn init_state(state: State) { ); borrowed .set(ConfigState::Initialized(state)) - .expect("failed to initialize state in stable cell") + .expect("failed to initialize memory in stable cell") }); } -/// Resets the state to [`ConfigState::Uninitialized`] which is useful e.g. in property tests where -/// the thread gets re-used and thus the state persists across test instances. +/// Resets the memory to [`ConfigState::Uninitialized`] which is useful e.g. in property tests where +/// the thread gets re-used and thus the memory persists across test instances. pub fn reset_state() { STATE.with(|cell| { cell.borrow_mut() .set(ConfigState::Uninitialized) - .unwrap_or_else(|err| panic!("Could not reset state: {:?}", err)); + .unwrap_or_else(|err| panic!("Could not reset memory: {:?}", err)); }) } diff --git a/canister/src/memory/tests.rs b/canister/src/memory/tests.rs new file mode 100644 index 00000000..2680e2d8 --- /dev/null +++ b/canister/src/memory/tests.rs @@ -0,0 +1,57 @@ +use crate::memory::{init_state, mutate_state, next_request_id, read_state, State}; +use candid::Principal; +use std::collections::BTreeSet; + +mod api_key_tests { + use super::*; + + #[test] + fn test_api_key_principals() { + init_state(State::default()); + + let principal1 = + Principal::from_text("k5dlc-ijshq-lsyre-qvvpq-2bnxr-pb26c-ag3sc-t6zo5-rdavy-recje-zqe") + .unwrap(); + let principal2 = + Principal::from_text("yxhtl-jlpgx-wqnzc-ysego-h6yqe-3zwfo-o3grn-gvuhm-nz3kv-ainub-6ae") + .unwrap(); + assert!(!is_api_key_principal(&principal1)); + assert!(!is_api_key_principal(&principal2)); + + set_api_key_principals(vec![principal1]); + assert!(is_api_key_principal(&principal1)); + assert!(!is_api_key_principal(&principal2)); + + set_api_key_principals(vec![principal2]); + assert!(!is_api_key_principal(&principal1)); + assert!(is_api_key_principal(&principal2)); + + set_api_key_principals(vec![principal1, principal2]); + assert!(is_api_key_principal(&principal1)); + assert!(is_api_key_principal(&principal2)); + + set_api_key_principals(vec![]); + assert!(!is_api_key_principal(&principal1)); + assert!(!is_api_key_principal(&principal2)); + } + + fn set_api_key_principals(new_principals: Vec) { + mutate_state(|state| state.set_api_key_principals(new_principals)); + } + + fn is_api_key_principal(principal: &Principal) -> bool { + read_state(|state| state.is_api_key_principal(principal)) + } +} + +mod request_counter_tests { + use super::*; + + #[test] + fn should_increment_request_id() { + let request_ids = (0..10) + .map(|_| next_request_id().to_string()) + .collect::>(); + assert_eq!(request_ids.len(), 10); + } +} diff --git a/canister/src/metrics/mod.rs b/canister/src/metrics/mod.rs new file mode 100644 index 00000000..c65848f3 --- /dev/null +++ b/canister/src/metrics/mod.rs @@ -0,0 +1,247 @@ +use candid::CandidType; +use derive_more::From; +use ic_cdk::api::call::RejectionCode; +use serde::Deserialize; +use std::collections::HashMap; + +#[macro_export] +macro_rules! add_metric { + ($metric:ident, $amount:expr) => {{ + $crate::memory::UNSTABLE_METRICS.with_borrow_mut(|m| m.$metric += $amount); + }}; +} + +#[macro_export] +macro_rules! add_metric_entry { + ($metric:ident, $key:expr, $amount:expr) => {{ + $crate::memory::UNSTABLE_METRICS.with_borrow_mut(|m| { + let amount = $amount; + if amount != 0 { + m.$metric + .entry($key) + .and_modify(|counter| *counter += amount) + .or_insert(amount); + } + }); + }}; +} + +pub trait MetricValue { + fn metric_value(&self) -> f64; +} + +impl MetricValue for u32 { + fn metric_value(&self) -> f64 { + *self as f64 + } +} + +impl MetricValue for u64 { + fn metric_value(&self) -> f64 { + *self as f64 + } +} + +impl MetricValue for u128 { + fn metric_value(&self) -> f64 { + *self as f64 + } +} + +pub trait MetricLabels { + fn metric_labels(&self) -> Vec<(&str, &str)>; +} + +impl MetricLabels for (A, B) { + fn metric_labels(&self) -> Vec<(&str, &str)> { + [self.0.metric_labels(), self.1.metric_labels()].concat() + } +} + +impl MetricLabels for (A, B, C) { + fn metric_labels(&self) -> Vec<(&str, &str)> { + [ + self.0.metric_labels(), + self.1.metric_labels(), + self.2.metric_labels(), + ] + .concat() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, CandidType, Deserialize, From)] +pub struct MetricRpcMethod(pub String); + +impl From for MetricRpcMethod { + fn from(method: RpcMethod) -> Self { + MetricRpcMethod(method.name().to_string()) + } +} + +impl MetricLabels for MetricRpcMethod { + fn metric_labels(&self) -> Vec<(&str, &str)> { + vec![("method", &self.0)] + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, CandidType, Deserialize, From)] +pub struct MetricRpcHost(pub String); + +impl From<&str> for MetricRpcHost { + fn from(hostname: &str) -> Self { + MetricRpcHost(hostname.to_string()) + } +} + +impl MetricLabels for MetricRpcHost { + fn metric_labels(&self) -> Vec<(&str, &str)> { + vec![("host", &self.0)] + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, CandidType, Deserialize)] +pub struct MetricHttpStatusCode(pub String); + +impl From for MetricHttpStatusCode { + fn from(value: u32) -> Self { + MetricHttpStatusCode(value.to_string()) + } +} + +impl MetricLabels for MetricHttpStatusCode { + fn metric_labels(&self) -> Vec<(&str, &str)> { + vec![("status", &self.0)] + } +} + +impl MetricLabels for RejectionCode { + fn metric_labels(&self) -> Vec<(&str, &str)> { + let code = match self { + RejectionCode::NoError => "NO_ERROR", + RejectionCode::SysFatal => "SYS_FATAL", + RejectionCode::SysTransient => "SYS_TRANSIENT", + RejectionCode::DestinationInvalid => "DESTINATION_INVALID", + RejectionCode::CanisterReject => "CANISTER_REJECT", + RejectionCode::CanisterError => "CANISTER_ERROR", + RejectionCode::Unknown => "UNKNOWN", + }; + vec![("code", code)] + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, CandidType, Deserialize)] +pub struct Metrics { + pub requests: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, + pub responses: HashMap<(MetricRpcMethod, MetricRpcHost, MetricHttpStatusCode), u64>, + #[serde(rename = "inconsistentResponses")] + pub inconsistent_responses: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, + #[serde(rename = "errHttpOutcall")] + pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost, RejectionCode), u64>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RpcMethod { + GetSlot, + Generic, +} + +impl RpcMethod { + fn name(self) -> &'static str { + match self { + RpcMethod::GetSlot => "getSlot", + RpcMethod::Generic => "generic", + } + } +} + +trait EncoderExtensions { + fn counter_entries( + &mut self, + name: &str, + map: &HashMap, + help: &str, + ); +} + +impl EncoderExtensions for ic_metrics_encoder::MetricsEncoder> { + fn counter_entries( + &mut self, + name: &str, + map: &HashMap, + help: &str, + ) { + map.iter().for_each(|(k, v)| { + self.counter_vec(name, help) + .and_then(|m| { + m.value(&k.metric_labels(), v.metric_value())?; + Ok(()) + }) + .unwrap_or(()); + }) + } +} + +pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> std::io::Result<()> { + const WASM_PAGE_SIZE_IN_BYTES: f64 = 65536.0; + + crate::memory::UNSTABLE_METRICS.with(|m| { + let m = m.borrow(); + + w.gauge_vec("cycle_balance", "Cycle balance of this canister")? + .value( + &[("canister", "solrpc")], + ic_cdk::api::canister_balance128().metric_value(), + )?; + w.encode_gauge( + "solrpc_canister_version", + ic_cdk::api::canister_version().metric_value(), + "Canister version", + )?; + w.encode_gauge( + "stable_memory_bytes", + ic_cdk::api::stable::stable_size() as f64 * WASM_PAGE_SIZE_IN_BYTES, + "Size of the stable memory allocated by this canister.", + )?; + + w.encode_gauge( + "heap_memory_bytes", + heap_memory_size_bytes() as f64, + "Size of the heap memory allocated by this canister.", + )?; + + w.counter_entries( + "solrpc_requests", + &m.requests, + "Number of JSON-RPC requests", + ); + w.counter_entries( + "solrpc_responses", + &m.responses, + "Number of JSON-RPC responses", + ); + w.counter_entries( + "solrpc_inconsistent_responses", + &m.inconsistent_responses, + "Number of inconsistent RPC responses", + ); + w.counter_entries( + "solrpc_err_http_outcall", + &m.err_http_outcall, + "Number of unsuccessful HTTP outcalls", + ); + + Ok(()) + }) +} + +/// Returns the amount of heap memory in bytes that has been allocated. +#[cfg(target_arch = "wasm32")] +pub fn heap_memory_size_bytes() -> usize { + const WASM_PAGE_SIZE_BYTES: usize = 65536; + core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE_BYTES +} + +#[cfg(not(any(target_arch = "wasm32")))] +pub fn heap_memory_size_bytes() -> usize { + 0 +} diff --git a/canister/src/providers/mod.rs b/canister/src/providers/mod.rs index 2043bd3d..beab23da 100644 --- a/canister/src/providers/mod.rs +++ b/canister/src/providers/mod.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests; -use crate::{constants::API_KEY_REPLACE_STRING, state::read_state, types::OverrideProvider}; +use crate::{constants::API_KEY_REPLACE_STRING, memory::read_state, types::OverrideProvider}; use ic_cdk::api::management_canister::http_request::HttpHeader; use maplit::btreemap; use sol_rpc_types::{ @@ -235,8 +235,7 @@ fn choose_providers( pub fn resolve_rpc_provider(service: RpcSource) -> RpcEndpoint { match service { - RpcSource::Supported(provider_id) => PROVIDERS - .with(|providers| providers.get(&provider_id).cloned()) + RpcSource::Supported(provider_id) => get_provider(&provider_id) .map(|provider| resolve_api_key(provider.access, provider_id)) .expect("Unknown provider"), RpcSource::Custom(api) => api, diff --git a/canister/src/rpc_client/mod.rs b/canister/src/rpc_client/mod.rs index 98a31a6c..a652fcb2 100644 --- a/canister/src/rpc_client/mod.rs +++ b/canister/src/rpc_client/mod.rs @@ -5,9 +5,10 @@ mod tests; use crate::{ http::http_client, logs::Priority, + memory::read_state, + metrics::MetricRpcMethod, providers::{request_builder, resolve_rpc_provider, Providers}, rpc_client::sol_rpc::{ResponseSizeEstimate, ResponseTransform, HEADER_SIZE_LIMIT}, - state::read_state, }; use canhttp::{ http::json::JsonRpcRequest, @@ -115,13 +116,15 @@ impl SolRpcClient { requests.insert_once(provider.clone(), request); } - let client = http_client(true).map_result(|r| match r?.into_body().into_result() { - Ok(value) => Ok(value), - Err(json_rpc_error) => Err(RpcError::JsonRpcError(JsonRpcError { - code: json_rpc_error.code, - message: json_rpc_error.message, - })), - }); + let rpc_method = MetricRpcMethod::from(request_body.method().to_string()); + let client = + http_client(rpc_method, true).map_result(|r| match r?.into_body().into_result() { + Ok(value) => Ok(value), + Err(json_rpc_error) => Err(RpcError::JsonRpcError(JsonRpcError { + code: json_rpc_error.code, + message: json_rpc_error.message, + })), + }); let (requests, errors) = requests.into_inner(); let (_client, mut results) = canhttp::multi::parallel_call(client, requests).await; diff --git a/canister/src/state/tests.rs b/canister/src/state/tests.rs deleted file mode 100644 index 70a63a77..00000000 --- a/canister/src/state/tests.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::state::{init_state, mutate_state, read_state, State}; -use candid::Principal; - -#[test] -fn test_api_key_principals() { - init_state(State::default()); - - let principal1 = - Principal::from_text("k5dlc-ijshq-lsyre-qvvpq-2bnxr-pb26c-ag3sc-t6zo5-rdavy-recje-zqe") - .unwrap(); - let principal2 = - Principal::from_text("yxhtl-jlpgx-wqnzc-ysego-h6yqe-3zwfo-o3grn-gvuhm-nz3kv-ainub-6ae") - .unwrap(); - assert!(!is_api_key_principal(&principal1)); - assert!(!is_api_key_principal(&principal2)); - - set_api_key_principals(vec![principal1]); - assert!(is_api_key_principal(&principal1)); - assert!(!is_api_key_principal(&principal2)); - - set_api_key_principals(vec![principal2]); - assert!(!is_api_key_principal(&principal1)); - assert!(is_api_key_principal(&principal2)); - - set_api_key_principals(vec![principal1, principal2]); - assert!(is_api_key_principal(&principal1)); - assert!(is_api_key_principal(&principal2)); - - set_api_key_principals(vec![]); - assert!(!is_api_key_principal(&principal1)); - assert!(!is_api_key_principal(&principal2)); -} - -fn set_api_key_principals(new_principals: Vec) { - mutate_state(|state| state.set_api_key_principals(new_principals)); -} - -fn is_api_key_principal(principal: &Principal) -> bool { - read_state(|state| state.is_api_key_principal(principal)) -} diff --git a/canister/src/types/tests.rs b/canister/src/types/tests.rs index d3bb6f0d..790d891c 100644 --- a/canister/src/types/tests.rs +++ b/canister/src/types/tests.rs @@ -1,6 +1,6 @@ use crate::{ + memory::{init_state, reset_state, State}, providers::{resolve_rpc_provider, PROVIDERS}, - state::{init_state, reset_state, State}, types::{ApiKey, OverrideProvider}, }; use proptest::{ diff --git a/libs/types/src/solana/mod.rs b/libs/types/src/solana/mod.rs index 41179e5d..c373d0e9 100644 --- a/libs/types/src/solana/mod.rs +++ b/libs/types/src/solana/mod.rs @@ -13,7 +13,7 @@ pub struct GetSlotParams { } /// [Commitment levels](https://solana.com/docs/rpc#configuring-state-commitment) in Solana, -/// representing finality guarantees of transactions and state queries. +/// representing finality guarantees of transactions and memory queries. #[derive(Debug, Clone, Deserialize, Serialize, CandidType)] pub enum CommitmentLevel { /// The transaction is processed by a leader, but may be dropped.