diff --git a/.gitignore b/.gitignore index 9a64cedc..2ebe5bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules activity-generator/releases/* .DS_Store /results +/.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e1d93ea4..7f9f4f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crc32fast" version = "1.4.2" @@ -581,6 +597,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "downcast" version = "0.11.0" @@ -599,6 +626,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -641,10 +677,10 @@ dependencies = [ "hex", "http-body 0.4.6", "hyper 0.14.32", - "hyper-rustls", + "hyper-rustls 0.24.2", "prost 0.12.6", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "tokio", "tokio-stream", "tonic 0.10.2", @@ -680,6 +716,30 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fragile" version = "2.0.0" @@ -1047,6 +1107,23 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.5.2", + "hyper-util", + "rustls 0.23.23", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.4.1" @@ -1072,6 +1149,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.5.2", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -1091,6 +1184,145 @@ dependencies = [ "tracing", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1111,6 +1343,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itertools" version = "0.10.5" @@ -1188,6 +1426,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -1231,6 +1475,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1313,6 +1567,23 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.29.0" @@ -1408,6 +1679,50 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1479,6 +1794,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1838,6 +2159,51 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.2", + "hyper-rustls 0.27.5", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower 0.5.2", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "ring" version = "0.16.20" @@ -1919,10 +2285,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1932,6 +2311,21 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1942,6 +2336,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -1954,6 +2359,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1990,6 +2404,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.8.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.217" @@ -2031,6 +2468,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2103,6 +2552,7 @@ dependencies = [ "rand", "rand_chacha", "rand_distr", + "reqwest", "serde", "serde_json", "serde_millis", @@ -2162,6 +2612,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "std-ext" version = "0.3.1" @@ -2177,6 +2633,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -2210,6 +2672,41 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.8.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "tempfile" @@ -2294,6 +2791,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.43.0" @@ -2334,6 +2841,16 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -2355,6 +2872,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls 0.23.23", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -2421,7 +2948,7 @@ dependencies = [ "pin-project", "prost 0.11.9", "prost-derive 0.11.9", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "tokio", "tokio-rustls 0.23.4", "tokio-stream", @@ -2453,7 +2980,7 @@ dependencies = [ "pin-project", "prost 0.12.6", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "tokio", "tokio-rustls 0.24.1", "tokio-stream", @@ -2549,6 +3076,7 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper 1.0.2", + "tokio", "tower-layer", "tower-service", ] @@ -2635,6 +3163,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.15" @@ -2659,12 +3193,41 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2700,6 +3263,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] @@ -2717,6 +3281,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -2803,6 +3380,36 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2960,6 +3567,42 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2981,8 +3624,51 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/README.md b/README.md index 6d8c6ad8..0d5f5877 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,15 @@ lightning network development. It may be useful to you if you are: * A signet operator interested in a hands-off way to run an active node. * A researcher generating synthetic data for a target topology. +## LN Implementation Support +* LND ✅ +* CLN ✅ +* Eclair ✅️ +* LDK-node 🏗️ + +See our [tracking issue](https://github.com/bitcoin-dev-project/sim-ln/issues/26) +for updates on implementation support (contributions welcome!). + ## Pre-Requisites SimLN requires you to "bring your own network" to generate activity on. You will need: @@ -21,14 +30,12 @@ on. You will need: * Rust compiler [installed](https://www.rust-lang.org/tools/install). * Protoc [installed](https://grpc.io/docs/protoc-installation). -## LN Implementation Support -* LND ✅ -* CLN ✅ -* Eclair 🏗️ -* LDK-node 🏗️ - -See our [tracking issue](https://github.com/bitcoin-dev-project/sim-ln/issues/26) -for updates on implementation support (contributions welcome!). +The simulator requires access details for a set of `nodes` that you +have permission to execute commands on. Note that the current version +of the simulator uses keysend to execute payments, which must be enabled as follows: +* LND: `--accept-keysend` +* CLN: enabled by default +* Eclair: `--features.keysend=optional` ## Getting Started @@ -54,13 +61,8 @@ Run `sim-cli -h` for details on `--data-dir` and `--sim-file` options that allow Interested in contributing to the project? See [CONTRIBUTING](CONTRIBUTING.md) for more details. ### Simulation File Setup -The simulator requires access details for a set of `nodes` that you -have permission to execute commands on. Note that the current version -of the simulator uses keysend to execute payments, which must be -enabled in LND using `--accept-keysend` (for CLN node it is enabled by default). - -The required access details will depend on the node implementation. For LND, the following -information is required: +The required access details will depend on the node implementation. +* LND: ``` { @@ -70,9 +72,7 @@ information is required: "cert": } ``` - -Whereas for CLN nodes, the following information is required: - +* CLN: ``` { "id": , @@ -82,12 +82,20 @@ Whereas for CLN nodes, the following information is required: "client_key": } ``` - +* Eclair: +``` +{ + "id": , + "base_url": , + "api_username": , + "api_password": +} +``` Payment activity can be simulated in two different ways: -* Random activity: generate random activity on the `nodes` provided, +* [Random activity](#setup---random-activity): generate random activity on the `nodes` provided, using the graph topology to determine payment frequency and size. -* Defined activity: provide descriptions of specific payments that +* [Defined activity](#setup---defined-activity): provide descriptions of specific payments that you would like the generator to execute. ### Setup - Random Activity @@ -113,6 +121,12 @@ not "drain" from the simulation. "ca_cert": "/path/ca.pem", "client_cert": "/path/client.pem", "client_key": "/path/client-key.pem" + }, + { + "id": "carol", + "base_url": "127.0.0.1:8286", + "api_username": "", + "api_password": "eclairpw" } ] } diff --git a/sim-cli/src/main.rs b/sim-cli/src/main.rs index 1baba0da..76d6866b 100644 --- a/sim-cli/src/main.rs +++ b/sim-cli/src/main.rs @@ -4,8 +4,8 @@ use clap::builder::TypedValueParser; use clap::Parser; use log::LevelFilter; use simln_lib::{ - cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection, - NodeId, SimParams, Simulation, SimulationCfg, WriteResults, + cln::ClnNode, eclair::EclairNode, lnd::LndNode, ActivityDefinition, LightningError, + LightningNode, NodeConnection, NodeId, SimParams, Simulation, SimulationCfg, WriteResults, }; use simple_logger::SimpleLogger; use std::collections::HashMap; @@ -112,6 +112,7 @@ async fn main() -> anyhow::Result<()> { let node: Arc> = match connection { NodeConnection::LND(c) => Arc::new(Mutex::new(LndNode::new(c).await?)), NodeConnection::CLN(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)), + NodeConnection::ECLAIR(c) => Arc::new(Mutex::new(EclairNode::new(c).await?)), }; let node_info = node.lock().await.get_info().clone(); diff --git a/simln-lib/Cargo.toml b/simln-lib/Cargo.toml index e1a41c78..cc28116e 100644 --- a/simln-lib/Cargo.toml +++ b/simln-lib/Cargo.toml @@ -32,6 +32,7 @@ serde_millis = "0.1.1" rand_distr = "0.4.3" mockall = "0.12.1" rand_chacha = "0.3.1" +reqwest = { version = "0.12", features = ["json", "multipart"] } tokio-util = { version = "0.7.13", features = ["rt"] } [dev-dependencies] diff --git a/simln-lib/src/eclair.rs b/simln-lib/src/eclair.rs new file mode 100644 index 00000000..c13a1b2c --- /dev/null +++ b/simln-lib/src/eclair.rs @@ -0,0 +1,382 @@ +use crate::{ + serializers, LightningError, LightningNode, NodeId, NodeInfo, PaymentOutcome, PaymentResult, +}; +use async_trait::async_trait; +use bitcoin::secp256k1::PublicKey; +use bitcoin::Network; +use lightning::ln::features::NodeFeatures; +use lightning::ln::{PaymentHash, PaymentPreimage}; +use reqwest::multipart::Form; +use reqwest::{Client, Method, Url}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::error::Error; +use std::str::FromStr; +use std::time::Duration; +use tokio::time; +use triggered::Listener; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EclairConnection { + #[serde(with = "serializers::serde_node_id")] + pub id: NodeId, + #[serde(with = "serializers::serde_address")] + pub base_url: String, + pub api_username: String, + pub api_password: String, +} + +struct EclairClient { + base_url: Url, + api_username: String, + api_password: String, + http_client: Client, +} + +impl EclairClient { + async fn request Deserialize<'de>>( + &self, + endpoint: &str, + params: Option>, + ) -> Result> { + let url = self.base_url.join(endpoint)?; + let mut request = self + .http_client + .request(Method::POST, url) + .basic_auth(self.api_username.clone(), Some(self.api_password.clone())); + + if let Some(params) = params { + let mut form = Form::new(); + for (key, value) in params { + form = form.text(key, value); + } + request = request.multipart(form); + } + + let response = request + .send() + .await? + .error_for_status()? + .json::() + .await?; + + Ok(response) + } +} + +impl TryInto for EclairConnection { + type Error = LightningError; + + fn try_into(self) -> Result { + let base_url = Url::parse(&self.base_url) + .map_err(|err| LightningError::GetInfoError(err.to_string()))?; + let http_client = Client::new(); + Ok(EclairClient { + base_url, + api_username: self.api_username, + api_password: self.api_password, + http_client, + }) + } +} + +pub struct EclairNode { + client: EclairClient, + info: NodeInfo, + network: Network, +} + +impl EclairNode { + pub async fn new(connection: EclairConnection) -> Result { + let client: EclairClient = connection.try_into()?; + let info: GetInfoResponse = client + .request("getinfo", None) + .await + .map_err(|err| LightningError::GetInfoError(err.to_string()))?; + let pubkey = PublicKey::from_str(info.node_id.as_str()) + .map_err(|err| LightningError::GetInfoError(err.to_string()))?; + let network = Network::from_str(match info.network.as_str() { + "mainnet" => "bitcoin", + "simnet" => { + return Err(LightningError::ValidationError( + "simnet is not supported".to_string(), + )) + }, + x => x, + }) + .map_err(|err| LightningError::ValidationError(err.to_string()))?; + let features = parse_json_to_node_features(&info.features); + + Ok(Self { + client, + info: NodeInfo { + pubkey, + alias: info.alias, + features, + }, + network, + }) + } +} + +#[async_trait] +impl LightningNode for EclairNode { + fn get_info(&self) -> &NodeInfo { + &self.info + } + + async fn get_network(&mut self) -> Result { + Ok(self.network) + } + + async fn send_payment( + &mut self, + dest: PublicKey, + amount_msat: u64, + ) -> Result { + let preimage = PaymentPreimage(rand::random()).0; + let mut params = HashMap::new(); + params.insert("nodeId".to_string(), hex::encode(dest.serialize())); + params.insert("amountMsat".to_string(), amount_msat.to_string()); + params.insert("paymentHash".to_string(), hex::encode(preimage)); + let uuid: String = self + .client + .request("sendtonode", Some(params)) + .await + .map_err(|err| LightningError::SendPaymentError(err.to_string()))?; + + let mut params = HashMap::new(); + params.insert("paymentHash".to_string(), hex::encode(preimage)); + params.insert("id".to_string(), uuid); + let payment_parts: PaymentInfoResponse = self + .client + .request("getsentinfo", Some(params)) + .await + .map_err(|_| LightningError::InvalidPaymentHash)?; + let payment_hash: [u8; 32] = hex::decode(&payment_parts[0].payment_hash) + .map_err(|_| LightningError::InvalidPaymentHash)? + .try_into() + .map_err(|_| LightningError::InvalidPaymentHash)?; + + Ok(PaymentHash(payment_hash)) + } + + async fn track_payment( + &mut self, + hash: &PaymentHash, + shutdown: Listener, + ) -> Result { + loop { + tokio::select! { + biased; + _ = shutdown.clone() => { + return Err(LightningError::TrackPaymentError("Shutdown before tracking results".to_string())); + }, + _ = time::sleep(Duration::from_millis(500)) => { + let mut params = HashMap::new(); + params.insert("paymentHash".to_string(), hex::encode(hash.0)); + + let payment_parts: PaymentInfoResponse = self + .client + .request("getsentinfo", Some(params)) + .await + .map_err(|err| LightningError::TrackPaymentError(err.to_string()))?; + + if let Some(payment) = payment_parts.first() { + let htlc_count = payment_parts.len(); + let payment_outcome = match payment.status.r#type { + PaymentStatus::Pending => continue, + PaymentStatus::Sent => PaymentOutcome::Success, + // PaymentStatus::Failed means API call failed (can happen for various reasons). + // Task to improve it: https://github.com/bitcoin-dev-project/sim-ln/issues/26#issuecomment-1691780018 + PaymentStatus::Failed => PaymentOutcome::UnexpectedError, + }; + + return Ok(PaymentResult { + htlc_count, + payment_outcome, + }); + } + }, + } + } + } + + async fn get_node_info(&mut self, node_id: &PublicKey) -> Result { + let mut params = HashMap::new(); + params.insert("nodeId".to_string(), hex::encode(node_id.serialize())); + + let node_info: NodeResponse = self + .client + .request("node", Some(params)) + .await + .map_err(|err| LightningError::GetNodeInfoError(err.to_string()))?; + let features = parse_json_to_node_features(&node_info.announcement.features); + + Ok(NodeInfo { + pubkey: *node_id, + alias: node_info.announcement.alias, + features, + }) + } + + async fn list_channels(&mut self) -> Result, LightningError> { + let channels: ChannelsResponse = self + .client + .request("channels", None) + .await + .map_err(|err| LightningError::ListChannelsError(err.to_string()))?; + + let capacities_msat: Vec = channels + .iter() + .map(|channel| { + channel + .data + .commitments + .active_channels + .iter() + .map(|ac| ac.local_commit.spec.to_local * 1_000) + .sum() + }) + .collect(); + + Ok(capacities_msat) + } +} + +#[derive(Debug, Deserialize)] +struct GetInfoResponse { + #[serde(rename = "nodeId")] + node_id: String, + alias: String, + network: String, + features: Value, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +enum PaymentStatus { + Pending, + Failed, + Sent, +} + +#[derive(Debug, Deserialize)] +struct PaymentStatusDetails { + #[serde(rename = "type")] + r#type: PaymentStatus, +} + +#[derive(Debug, Deserialize)] +struct PaymentPart { + status: PaymentStatusDetails, + #[serde(rename = "paymentHash")] + payment_hash: String, +} + +type PaymentInfoResponse = Vec; + +#[derive(Debug, Deserialize)] +struct Announcement { + alias: String, + features: Value, +} + +#[derive(Debug, Deserialize)] +struct NodeResponse { + announcement: Announcement, +} + +type ChannelsResponse = Vec; + +#[derive(Debug, Deserialize)] +struct Channel { + #[serde(rename = "data")] + data: ChannelData, +} + +#[derive(Debug, Deserialize)] +struct ChannelData { + #[serde(rename = "commitments")] + commitments: Commitments, +} + +#[derive(Debug, Deserialize)] +struct Commitments { + #[serde(rename = "active")] + active_channels: Vec, +} + +#[derive(Debug, Deserialize)] +struct ActiveChannel { + #[serde(rename = "localCommit")] + local_commit: LocalCommit, +} + +#[derive(Debug, Deserialize)] +struct LocalCommit { + #[serde(rename = "spec")] + spec: LocalCommitSpec, +} + +#[derive(Debug, Deserialize)] +struct LocalCommitSpec { + #[serde(rename = "toLocal")] + to_local: u64, +} + +fn parse_json_to_node_features(json: &Value) -> NodeFeatures { + let mut flags = vec![0u8; 8]; + + let feature_mapping: HashMap<&str, usize> = [ + ("option_data_loss_protect", 0), + ("option_upfront_shutdown_script", 4), + ("gossip_queries", 6), + ("var_onion_optin", 8), + ("gossip_queries_ex", 10), + ("option_static_remotekey", 12), + ("payment_secret", 14), + ("basic_mpp", 16), + ("option_support_large_channel", 18), + ("option_anchors_zero_fee_htlc_tx", 22), + ("option_route_blinding", 24), + ("option_shutdown_anysegwit", 26), + ("option_dual_fund", 28), + ("option_quiesce", 34), + ("option_onion_messages", 38), + ("option_provide_storage", 42), + ("option_channel_type", 44), + ("option_scid_alias", 46), + ("option_payment_metadata", 48), + ("option_zeroconf", 50), + ("keysend", 54), + ("option_simple_close", 60), + ] + .iter() + .cloned() + .collect(); + + if let Some(activated) = json["activated"].as_object() { + for (feature, level) in activated.iter() { + if let Some(&bit_position) = feature_mapping.get(feature.as_str()) { + // Determine if the feature is mandatory or optional. + let is_mandatory = level == "mandatory"; + // Calculate the exact bit position. + let bit = if is_mandatory { + bit_position // Even bit for mandatory + } else { + bit_position + 1 // Odd bit for optional + }; + // Calculate the byte index and bit offset. + let byte_index = bit / 8; + let bit_offset = bit % 8; + + // Set the corresponding bit. + flags[byte_index] |= 1 << bit_offset; + } + } + } + + NodeFeatures::from_le_bytes(flags) +} diff --git a/simln-lib/src/lib.rs b/simln-lib/src/lib.rs index 7cb53ae1..f915940e 100644 --- a/simln-lib/src/lib.rs +++ b/simln-lib/src/lib.rs @@ -28,6 +28,7 @@ use self::random_activity::{NetworkGraphView, RandomPaymentActivity}; pub mod cln; mod defined_activity; +pub mod eclair; pub mod lnd; mod random_activity; mod serializers; @@ -40,6 +41,7 @@ mod test_utils; pub enum NodeConnection { LND(lnd::LndConnection), CLN(cln::ClnConnection), + ECLAIR(eclair::EclairConnection), } #[derive(Serialize, Debug, Clone)] diff --git a/simln-lib/src/serializers.rs b/simln-lib/src/serializers.rs index 37bbb2c1..c15d5217 100644 --- a/simln-lib/src/serializers.rs +++ b/simln-lib/src/serializers.rs @@ -60,7 +60,7 @@ pub mod serde_address { D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; - if s.starts_with("https://") { + if s.starts_with("https://") || s.starts_with("http://") { Ok(s) } else { Ok(format!("https://{}", s))