From 27257ceadf0d0a28cadee61f67eccf0431cfbd98 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 31 Oct 2023 02:05:58 -0400 Subject: [PATCH 01/42] feat: go from websocket-server to json rpc websocket/http server Includes: - re-purposing of feature flags * metrics is always a thing (on) * monitoring is the gated feature * websocket-server flag is gone, we only gate push notifications - jsonrpc setup and rpc method register - prometheus exposition format to json parser - Added todo around split networking config Notes: - will not merge this in until example app has been restored with the client changes --- .envrc | 2 +- .gitignore | 1 + Cargo.lock | 1495 +++++++++++------ README.md | 10 + examples/websocket-relay/README.md | 9 +- flake.lock | 6 +- homestar-functions/README.md | 7 +- homestar-runtime/Cargo.toml | 48 +- homestar-runtime/fixtures/settings.toml | 2 +- homestar-runtime/src/event_handler.rs | 30 +- homestar-runtime/src/event_handler/event.rs | 12 +- homestar-runtime/src/logger.rs | 1 + homestar-runtime/src/metrics.rs | 26 +- homestar-runtime/src/metrics/exporter.rs | 38 +- homestar-runtime/src/metrics/node.rs | 17 +- homestar-runtime/src/network/mod.rs | 11 +- homestar-runtime/src/network/rpc.rs | 1 + homestar-runtime/src/network/webserver.rs | 430 +++++ .../src/network/{ws => webserver}/listener.rs | 16 +- .../src/network/{ws => webserver}/notifier.rs | 0 .../src/network/webserver/prom.rs | 434 +++++ homestar-runtime/src/network/webserver/rpc.rs | 235 +++ homestar-runtime/src/network/ws.rs | 325 ---- homestar-runtime/src/runner.rs | 352 ++-- homestar-runtime/src/settings.rs | 29 +- .../src/test_utils/proc_macro/src/lib.rs | 2 +- homestar-runtime/src/worker.rs | 19 +- homestar-runtime/tests/cli.rs | 49 +- .../tests/fixtures/test_mdns1.toml | 4 +- .../tests/fixtures/test_mdns2.toml | 4 +- .../tests/fixtures/test_metrics.toml | 4 +- .../tests/fixtures/test_network1.toml | 4 +- .../tests/fixtures/test_network2.toml | 4 +- .../tests/fixtures/test_rendezvous1.toml | 2 +- .../tests/fixtures/test_rendezvous2.toml | 2 +- .../tests/fixtures/test_rendezvous3.toml | 2 +- .../tests/fixtures/test_rendezvous4.toml | 2 +- .../tests/fixtures/test_rendezvous5.toml | 2 +- .../tests/fixtures/test_rendezvous6.toml | 6 +- homestar-runtime/tests/fixtures/test_v4.toml | 5 +- .../tests/fixtures/test_v4_alt.toml | 3 +- homestar-runtime/tests/fixtures/test_v6.toml | 4 +- .../tests/fixtures/test_workflow.toml | 3 +- homestar-runtime/tests/main.rs | 1 + homestar-runtime/tests/metrics.rs | 4 +- homestar-runtime/tests/network.rs | 4 +- homestar-runtime/tests/utils.rs | 11 +- 47 files changed, 2414 insertions(+), 1264 deletions(-) create mode 100644 homestar-runtime/src/network/webserver.rs rename homestar-runtime/src/network/{ws => webserver}/listener.rs (87%) rename homestar-runtime/src/network/{ws => webserver}/notifier.rs (100%) create mode 100644 homestar-runtime/src/network/webserver/prom.rs create mode 100644 homestar-runtime/src/network/webserver/rpc.rs delete mode 100644 homestar-runtime/src/network/ws.rs diff --git a/.envrc b/.envrc index 8ea2cc72..6e5c116c 100644 --- a/.envrc +++ b/.envrc @@ -1,5 +1,5 @@ use_flake -export RUST_LOG=homestar=debug,homestar_runtime=debug,libp2p=info,libp2p_gossipsub::behaviour=debug,tarpc=info,tower_http=debug,moka=debug +export RUST_LOG=homestar=debug,homestar_runtime=debug,libp2p=info,libp2p_gossipsub::behaviour=debug,tarpc=info,tower_http=debug,jsonrpsee_server=debug,moka=debug export RUST_BACKTRACE=full export RUSTFLAGS="--cfg tokio_unstable" diff --git a/.gitignore b/.gitignore index 0b74cf8d..8e6e9508 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ report.json homestar.err homestar.out homestar.pid +homestar.log* # locks homestar-wasm/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 7db5ee22..3916070e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -79,20 +79,30 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.5" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -146,15 +156,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -189,9 +199,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "arrayref" @@ -279,9 +289,9 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.25", + "rustix 0.37.27", "slab", - "socket2 0.4.9", + "socket2 0.4.10", "waker-fn", ] @@ -308,7 +318,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -330,7 +340,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -341,7 +351,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -388,11 +398,9 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "base64 0.21.4", "bitflags 1.3.2", "bytes", "futures-util", - "headers", "http", "http-body", "hyper", @@ -404,10 +412,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sha1", "sync_wrapper", - "tokio", - "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -468,9 +473,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -478,6 +483,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "bimap" version = "0.6.3" @@ -522,9 +536,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "blake2" @@ -559,9 +573,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", @@ -599,12 +613,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", - "regex-automata 0.3.8", + "regex-automata 0.4.3", "serde", ] @@ -625,9 +639,9 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" @@ -683,7 +697,7 @@ dependencies = [ "io-lifetimes 1.0.11", "ipnet", "maybe-owned", - "rustix 0.37.25", + "rustix 0.37.27", "windows-sys", "winx 0.35.1", ] @@ -707,7 +721,7 @@ dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 1.0.11", - "rustix 0.37.25", + "rustix 0.37.27", ] [[package]] @@ -718,7 +732,7 @@ checksum = "e95002993b7baee6b66c8950470e59e5226a23b3af39fc59c47fe416dd39821a" dependencies = [ "cap-primitives", "once_cell", - "rustix 0.37.25", + "rustix 0.37.27", "winx 0.35.1", ] @@ -917,7 +931,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -956,9 +970,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] @@ -1063,37 +1077,37 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "cranelift-bforest" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b9d1a9e776c27ad55d7792a380785d1fe8c2d7b099eed8dbd8f4af2b598192" +checksum = "751cbf89e513f283c0641eb7f95dc72fda5051dd95ca203d1dc45e26bc89dba8" dependencies = [ - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", ] [[package]] name = "cranelift-codegen" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5528483314c2dd5da438576cd8a9d0b3cedad66fb8a4727f90cd319a81950038" +checksum = "210730edc05121e915201cc36595e1f00062094669fa07ac362340e3627b3dc5" dependencies = [ "bumpalo", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-control", - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", "cranelift-isle", "gimli 0.28.0", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "log", "regalloc2", "smallvec", @@ -1102,24 +1116,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f46a8318163f7682e35b8730ba93c1b586a2da8ce12a0ed545efc1218550f70" +checksum = "b5dc7fdf210c53db047f3eaf49b3a89efee0cc3d9a2ce0c0f0236933273d0c53" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d1239cfd50eecfaed468d46943f8650e32969591868ad50111613704da6c70" +checksum = "f46875cc87d963119d78fe5c19852757dc6eea3cb9622c0df69c26b242cd44b4" [[package]] name = "cranelift-control" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc530560c8f16cc1d4dd7ea000c56f519c60d1a914977abe849ce555c35a61d" +checksum = "375dca8f58d8a801a85e11730c1529c5c4a9c3593dfb12118391ac437b037155" dependencies = [ "arbitrary", ] @@ -1135,9 +1149,9 @@ dependencies = [ [[package]] name = "cranelift-entity" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f333fa641a9ad2bff0b107767dcb972c18c2bfab7969805a1d7e42449ccb0408" +checksum = "cc619b86fe3c72f43fc417c9fd67a04ec0c98296e5940922d9fd9e6eedf72521" dependencies = [ "serde", "serde_derive", @@ -1145,9 +1159,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abf6563015a80f03f8bc4df307d0a81363f4eb73108df3a34f6e66fb6d5307" +checksum = "7eb607fd19ae264da18f9f2532e7302b826f7fbf77bf88365fc075f2e3419436" dependencies = [ "cranelift-codegen", "log", @@ -1157,15 +1171,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb29d0edc8a5c029ed0f7ca77501f272738e3c410020b4a00f42ffe8ad2a8aa" +checksum = "9fe806a6470dddfdf79e878af6a96afb1235a09fe3e21f9e0c2f18d402820432" [[package]] name = "cranelift-native" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006056a7fa920870bad06bf8e1b3033d70cbb7ee625b035efa9d90882a931868" +checksum = "fac7f1722660b10af1f7229c0048f716bfd8bd344549b0e06e3eb6417ec3fe5b" dependencies = [ "cranelift-codegen", "libc", @@ -1174,18 +1188,18 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.100.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3d08c05f82903a1f6a04d89c4b9ecb47a4035710f89a39a21a147a80214672" +checksum = "b1b65810be56b619c3c55debade92798d999f34bf0670370c578afab5d905f06" dependencies = [ "cranelift-codegen", - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", "cranelift-frontend", "itertools 0.10.5", "log", "smallvec", "wasmparser 0.112.0", - "wasmtime-types 13.0.0", + "wasmtime-types 13.0.1", ] [[package]] @@ -1354,13 +1368,13 @@ dependencies = [ [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1405,7 +1419,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1416,7 +1430,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1426,7 +1440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "lock_api", "once_cell", "parking_lot_core", @@ -1494,10 +1508,11 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -1516,14 +1531,14 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e054665eaf6d97d1e7125512bb2d35d07c73ac86cc6920174cb42d1ab697a554" +checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1543,7 +1558,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1621,7 +1636,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1648,6 +1663,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + [[package]] name = "ecolor" version = "0.23.0" @@ -1656,9 +1677,9 @@ checksum = "cfdf4e52dbbb615cfd30cf5a5265335c217b5fd8d669593cea74a517d9c605af" [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", @@ -1722,6 +1743,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.5.1" @@ -1743,7 +1770,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1759,15 +1786,15 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "3.1.13" +version = "3.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" dependencies = [ "num-bigint", "num-traits", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -1791,24 +1818,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.3.3" +name = "erased-serde" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", + "serde", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ - "cc", "libc", + "windows-sys", ] [[package]] @@ -1828,12 +1853,12 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exr" -version = "1.7.0" +version = "1.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" dependencies = [ "bit_field", - "flume 0.10.14", + "flume", "half 2.2.1", "lebe", "miniz_oxide", @@ -1865,9 +1890,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "faststr" @@ -1886,48 +1911,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0377f1edc77dbd1118507bc7a66e4ab64d2b90c66f90726dc801e73a8c68f9" dependencies = [ "cfg-if", - "rustix 0.38.13", + "rustix 0.38.21", "windows-sys", ] [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" dependencies = [ "simd-adler32", ] [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin 0.9.8", -] - [[package]] name = "flume" version = "0.11.0" @@ -1961,7 +1973,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d167b646a876ba8fda6b50ac645cfd96242553cbaf0ca4fccaa39afcbf0801f" dependencies = [ "io-lifetimes 1.0.11", - "rustix 0.38.13", + "rustix 0.38.21", "windows-sys", ] @@ -1977,9 +1989,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -2002,9 +2014,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -2012,15 +2024,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -2030,9 +2042,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -2051,13 +2063,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -2072,15 +2084,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-ticker" @@ -2098,12 +2110,16 @@ name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper", +] [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -2132,7 +2148,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "debugid", "fxhash", "serde", @@ -2161,9 +2177,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -2210,6 +2226,52 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.3.21" @@ -2261,9 +2323,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash", "allocator-api2", @@ -2288,7 +2350,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -2317,9 +2379,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -2426,7 +2488,6 @@ dependencies = [ "assert_cmd", "async-trait", "atomic_refcell", - "axum", "byte-unit", "chrono", "clap", @@ -2440,9 +2501,10 @@ dependencies = [ "diesel", "diesel_migrations", "dotenvy", + "dyn-clone", "enum-assoc", "faststr", - "flume 0.11.0", + "flume", "fnv", "futures", "headers", @@ -2456,11 +2518,14 @@ dependencies = [ "ipfs-api", "ipfs-api-backend-hyper", "itertools 0.11.0", + "jsonrpsee", "libipld", "libp2p", "libsqlite3-sys", + "maplit", "metrics", "metrics-exporter-prometheus", + "metrics-util", "miette", "moka", "names", @@ -2472,6 +2537,7 @@ dependencies = [ "puffin", "puffin_egui", "rand", + "regex", "reqwest", "retry", "rm_rf", @@ -2491,13 +2557,17 @@ dependencies = [ "thiserror", "tokio", "tokio-serde", + "tokio-stream", "tokio-tungstenite", "tokio-util", + "tower", + "tower-http", "tracing", "tracing-appender", "tracing-logfmt", "tracing-subscriber", "tryhard", + "typetag", "url", "wait-timeout", "wnfs-common", @@ -2509,7 +2579,7 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -2534,11 +2604,11 @@ dependencies = [ "tracing", "wasi-common", "wasmparser 0.115.0", - "wasmtime 13.0.0", - "wasmtime-component-util 13.0.0", + "wasmtime 13.0.1", + "wasmtime-component-util 13.0.1", "wasmtime-wasi", "wat", - "wit-component 0.14.2", + "wit-component 0.14.7", ] [[package]] @@ -2574,6 +2644,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "http-serde" version = "1.1.3" @@ -2619,7 +2695,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -2639,6 +2715,22 @@ dependencies = [ "hyper", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-timeout" version = "0.4.1" @@ -2653,16 +2745,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.48.0", + "windows-core", ] [[package]] @@ -2733,7 +2825,7 @@ dependencies = [ "rtnetlink", "system-configuration", "tokio", - "windows 0.51.1", + "windows", ] [[package]] @@ -2789,7 +2881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "serde", ] @@ -2805,6 +2897,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "inventory" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0508c56cfe9bfd5dfeb0c22ab9a6abfda2f27bdca422132e494266351ed8d83c" + [[package]] name = "io-extras" version = "0.17.4" @@ -2838,7 +2936,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.4", + "socket2 0.5.5", "widestring", "windows-sys", "winreg 0.50.0", @@ -2898,9 +2996,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -2909,7 +3007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.13", + "rustix 0.38.21", "windows-sys", ] @@ -2945,9 +3043,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "ittapi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e0d0b7b3b53d92a7e8b80ede3400112a6b8b4c98d1f5b8b16bb787c780582c" +checksum = "25a5c0b993601cad796222ea076565c5d9f337d35592f8622c753724f06d7271" dependencies = [ "anyhow", "ittapi-sys", @@ -2956,18 +3054,18 @@ dependencies = [ [[package]] name = "ittapi-sys" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f8763c96e54e6d6a0dccc2990d8b5e33e3313aaeae6185921a3f4c1614a77c" +checksum = "cb7b5e473765060536a660eed127f758cf1a810c73e49063264959c60d1727d9" dependencies = [ "cc", ] [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -2980,9 +3078,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -2993,6 +3091,152 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "jsonrpsee" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-wasm-client", + "jsonrpsee-ws-client", + "tokio", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +dependencies = [ + "futures-channel", + "futures-util", + "gloo-net", + "http", + "jsonrpsee-core", + "pin-project", + "rustls-native-certs", + "soketto", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +dependencies = [ + "anyhow", + "async-lock", + "async-trait", + "beef", + "futures-timer", + "futures-util", + "hyper", + "jsonrpsee-types", + "parking_lot", + "rand", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen-futures", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" +dependencies = [ + "async-trait", + "hyper", + "hyper-rustls", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" +dependencies = [ + "futures-util", + "http", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "jsonrpsee-wasm-client" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7cbb3447cf14fd4d2f407c3cc96e6c9634d5440aa1fbed868a31f3c02b27f0" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" +dependencies = [ + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", + "url", +] + [[package]] name = "keccak" version = "0.1.4" @@ -3022,9 +3266,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libipld" @@ -3118,9 +3362,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libp2p" @@ -3233,7 +3477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f9624e2a843b655f1c1b8262b8d5de6f309413fca4d66f01bb0662429f84dc" dependencies = [ "asynchronous-codec", - "base64 0.21.4", + "base64 0.21.5", "byteorder", "bytes", "either", @@ -3345,7 +3589,7 @@ dependencies = [ "log", "rand", "smallvec", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", "trust-dns-proto 0.22.0", "void", @@ -3410,9 +3654,9 @@ dependencies = [ "parking_lot", "quinn", "rand", - "ring", + "ring 0.16.20", "rustls", - "socket2 0.5.4", + "socket2 0.5.5", "thiserror", "tokio", ] @@ -3494,7 +3738,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -3510,7 +3754,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", ] @@ -3525,7 +3769,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "rcgen", - "ring", + "ring 0.16.20", "rustls", "rustls-webpki", "thiserror", @@ -3562,6 +3806,17 @@ dependencies = [ "yamux", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + [[package]] name = "libsecp256k1" version = "0.7.1" @@ -3635,15 +3890,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -3661,7 +3916,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efa59af2ddfad1854ae27d75009d538d0998b4b2fd47083e743ac1a10e46c60" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.2", ] [[package]] @@ -3697,6 +3952,12 @@ dependencies = [ "libc", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -3720,9 +3981,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "maybe-owned" @@ -3738,17 +3999,17 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memfd" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.37.25", + "rustix 0.38.21", ] [[package]] @@ -3786,7 +4047,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "hyper", "indexmap 1.9.3", "ipnet", @@ -3805,7 +4066,7 @@ checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -3814,12 +4075,16 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" dependencies = [ + "aho-corasick 0.7.20", "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.13.2", + "indexmap 1.9.3", "metrics", "num_cpus", + "ordered-float", "quanta", + "radix_trie", "sketches-ddsketch", ] @@ -3852,7 +4117,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -3910,9 +4175,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -4081,15 +4346,6 @@ dependencies = [ "rand", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "natord" version = "1.0.9" @@ -4162,6 +4418,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.24.3" @@ -4179,7 +4444,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "libc", ] @@ -4252,9 +4517,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -4289,7 +4554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "crc32fast", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "indexmap 2.1.0", "memchr", ] @@ -4321,6 +4586,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "opentelemetry" version = "0.18.0" @@ -4364,11 +4635,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ "ttf-parser", ] @@ -4392,9 +4672,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -4408,13 +4688,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets", ] @@ -4472,7 +4752,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -4505,9 +4785,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" -version = "3.1.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] name = "plotters" @@ -4591,9 +4871,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.4.3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -4671,14 +4957,14 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -4703,7 +4989,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -4726,7 +5012,7 @@ checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.0", + "bitflags 2.4.1", "lazy_static", "num-traits", "rand", @@ -4758,7 +5044,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -4897,13 +5183,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", "rand", - "ring", + "ring 0.16.20", "rustc-hash", "rustls", "slab", @@ -4920,7 +5206,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.4", + "socket2 0.5.5", "tracing", "windows-sys", ] @@ -4945,6 +5231,16 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -4995,9 +5291,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -5005,14 +5301,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -5022,45 +5316,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", - "ring", + "ring 0.16.20", "time", "yasna", ] [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regalloc2" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b4dcbd3a2ae7fb94b5813fa0e957c6ab51bf5d0a8ee1b69e0c2d0f1e6eb8485" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", @@ -5075,7 +5360,7 @@ version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ - "aho-corasick", + "aho-corasick 1.1.2", "memchr", "regex-automata 0.4.3", "regex-syntax 0.8.2", @@ -5090,19 +5375,13 @@ dependencies = [ "regex-syntax 0.6.29", ] -[[package]] -name = "regex-automata" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" - [[package]] name = "regex-automata" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ - "aho-corasick", + "aho-corasick 1.1.2", "memchr", "regex-syntax 0.8.2", ] @@ -5131,7 +5410,7 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -5189,11 +5468,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "rm_rf" version = "0.6.2" @@ -5225,6 +5518,12 @@ dependencies = [ "serde", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "rtnetlink" version = "0.10.1" @@ -5282,9 +5581,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.25" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -5298,37 +5597,58 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.7", + "linux-raw-sys 0.4.11", "windows-sys", ] [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring", + "ring 0.17.5", "rustls-webpki", "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.5", +] + [[package]] name = "rustls-webpki" -version = "0.101.5" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -5375,6 +5695,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.7" @@ -5392,12 +5721,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -5411,6 +5740,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.20" @@ -5420,11 +5772,17 @@ dependencies = [ "serde", ] +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "serde" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -5449,13 +5807,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -5483,9 +5841,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] @@ -5504,11 +5862,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "chrono", "hex", "indexmap 1.9.3", @@ -5521,14 +5879,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -5552,14 +5910,27 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -5602,9 +5973,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -5686,9 +6057,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smawk" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "snafu" @@ -5723,7 +6094,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek", "rand_core", - "ring", + "ring 0.16.20", "rustc_version", "sha2 0.10.8", "subtle", @@ -5731,9 +6102,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -5741,14 +6112,30 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "futures", + "http", + "httparse", + "log", + "rand", + "sha-1", +] + [[package]] name = "spdx" version = "0.10.2" @@ -5857,15 +6244,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -5876,9 +6263,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "supports-color" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" dependencies = [ "is-terminal", "is_ci", @@ -5915,9 +6302,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.33" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -5996,12 +6383,12 @@ version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10081a99cbecbc363d381b9503563785f0b02735fccbb0d4c1a2cb3d39f7e7fe" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cap-fs-ext", "cap-std", "fd-lock", "io-lifetimes 2.0.2", - "rustix 0.38.13", + "rustix 0.38.21", "windows-sys", "winx 0.36.2", ] @@ -6038,9 +6425,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "tarpc" @@ -6079,14 +6466,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", - "fastrand 2.0.0", - "redox_syscall 0.3.5", - "rustix 0.38.13", + "fastrand 2.0.1", + "redox_syscall", + "rustix 0.38.21", "windows-sys", ] @@ -6134,7 +6521,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -6149,12 +6536,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -6213,7 +6601,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "tracing", "windows-sys", @@ -6237,7 +6625,17 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", ] [[package]] @@ -6264,6 +6662,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -6280,12 +6679,13 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "slab", @@ -6316,9 +6716,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -6345,7 +6745,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.4", + "base64 0.21.5", "bytes", "h2", "http", @@ -6383,6 +6783,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -6427,7 +6846,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -6506,7 +6925,7 @@ dependencies = [ "lazy_static", "rand", "smallvec", - "socket2 0.4.9", + "socket2 0.4.10", "thiserror", "tinyvec", "tokio", @@ -6579,9 +6998,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "tungstenite" @@ -6615,9 +7034,33 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typetag" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80960fd143d4c96275c0e60b08f14b81fbb468e79bc0ef8fbda69fb0afafae43" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "bfc13d450dc4a695200da3074dacf43d449b968baee95e341920e47f61a3b40f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "ucan" @@ -6628,7 +7071,7 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "bs58", "cid 0.10.1", "futures", @@ -6708,9 +7151,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -6744,6 +7187,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -6845,9 +7294,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" @@ -6891,7 +7340,7 @@ dependencies = [ "io-lifetimes 1.0.11", "is-terminal", "once_cell", - "rustix 0.37.25", + "rustix 0.37.27", "system-interface", "tracing", "wasi-common", @@ -6910,7 +7359,7 @@ dependencies = [ "cap-std", "io-extras", "log", - "rustix 0.37.25", + "rustix 0.37.27", "thiserror", "tracing", "wasmtime 11.0.2", @@ -6928,7 +7377,7 @@ dependencies = [ "cap-std", "io-extras", "io-lifetimes 1.0.11", - "rustix 0.37.25", + "rustix 0.37.27", "tokio", "wasi-cap-std-sync", "wasi-common", @@ -6937,9 +7386,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -6947,24 +7396,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -6974,9 +7423,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6984,22 +7433,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-encoder" @@ -7012,27 +7461,27 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.33.1" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39de0723a53d3c8f54bed106cfbc0d06b3e4d945c5c5022115a61e3b29183ae" +checksum = "34180c89672b3e4825c3a8db4b61a674f1447afd5fe2445b2d22c3d8b6ea086c" dependencies = [ "leb128", ] [[package]] name = "wasm-encoder" -version = "0.35.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca90ba1b5b0a70d3d49473c5579951f3bddc78d47b59256d2f9d4922b150aca" +checksum = "822b645bf4f2446b949776ffca47e2af60b167209ffb70814ef8779d299cd421" dependencies = [ "leb128", ] [[package]] name = "wasm-metadata" -version = "0.10.9" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14abc161bfda5b519aa229758b68f2a52b45a12b993808665c857d1a9a00223c" +checksum = "2167ce53b2faa16a92c6cafd4942cff16c9a4fa0c5a5a0a41131ee4e49fc055f" dependencies = [ "anyhow", "indexmap 2.1.0", @@ -7040,8 +7489,8 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.35.0", - "wasmparser 0.115.0", + "wasm-encoder 0.36.2", + "wasmparser 0.116.1", ] [[package]] @@ -7084,14 +7533,24 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.1.0", + "semver", +] + [[package]] name = "wasmprinter" -version = "0.2.64" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ddf5892036cd4b780d505eff1194a0cbc10ed896097656fdcea3744b5e7c2f" +checksum = "9aff4df0cdf1906ec040e97d78c3fc8fd26d3f8d70adaac81f07f80957b63b54" dependencies = [ "anyhow", - "wasmparser 0.112.0", + "wasmparser 0.116.1", ] [[package]] @@ -7127,9 +7586,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ed7db409c1acf60d33128b2a38bee25aaf38c4bd955ab98a5b623c8294593c" +checksum = "b0263693caa1486bd4d26a5f18511948a706c9290689386b81b851ce088063ce" dependencies = [ "anyhow", "async-trait", @@ -7153,13 +7612,13 @@ dependencies = [ "wasm-encoder 0.32.0", "wasmparser 0.112.0", "wasmtime-cache", - "wasmtime-component-macro 13.0.0", - "wasmtime-component-util 13.0.0", + "wasmtime-component-macro 13.0.1", + "wasmtime-component-util 13.0.1", "wasmtime-cranelift", - "wasmtime-environ 13.0.0", - "wasmtime-fiber 13.0.0", - "wasmtime-jit 13.0.0", - "wasmtime-runtime 13.0.0", + "wasmtime-environ 13.0.1", + "wasmtime-fiber 13.0.1", + "wasmtime-jit 13.0.1", + "wasmtime-runtime 13.0.1", "wasmtime-winch", "wat", "windows-sys", @@ -7176,25 +7635,25 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53af0f8f6271bd687fe5632c8fe0a0f061d0aa1b99a0cd4e1df8e4cbeb809d2f" +checksum = "4711e5969236ecfbe70c807804ff9ffb5206c1dbb5c55c5e8200d9f7e8e76adf" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41376a7c094335ee08abe6a4eff79a32510cc805a249eff1b5e7adf0a42e7cdf" +checksum = "5b79f9f79188e5a26b6911b79d3171c06699d9a17ae07f6a265c51635b8d80c2" dependencies = [ "anyhow", - "base64 0.21.4", + "base64 0.21.5", "bincode", "directories-next", "log", - "rustix 0.38.13", + "rustix 0.38.21", "serde", "serde_derive", "sha2 0.10.8", @@ -7220,17 +7679,17 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ab5b291f2dad56f1e6929cc61fb7cac68845766ca77c3838b5d05d82c33976" +checksum = "ed724d0f41c21bcf8754651a59d0423c530069ddca4cf3822768489ad313a812" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.33", - "wasmtime-component-util 13.0.0", - "wasmtime-wit-bindgen 13.0.0", - "wit-parser 0.11.1", + "syn 2.0.39", + "wasmtime-component-util 13.0.1", + "wasmtime-wit-bindgen 13.0.1", + "wit-parser 0.11.3", ] [[package]] @@ -7241,21 +7700,21 @@ checksum = "31bd6b1c6d8ece2aa852bf5dad0ea91be63e81c7571d7bcf24238b05405adb70" [[package]] name = "wasmtime-component-util" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21436177bf19f6b60dc0b83ad5872e849892a4a90c3572785e1a28c0e2e1132c" +checksum = "7e7d69464b94bd312a27d93d0b482cd74bedf01f030199ef0740d6300ebca1d3" [[package]] name = "wasmtime-cranelift" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920e42058862d1f7a3dd3fca73cb495a20d7506e3ada4bbc0a9780cd636da7ca" +checksum = "4e63f53c61ba05eb815f905c1738ad82c95333dd42ef5a8cc2aa3d7dfb2b08d7" dependencies = [ "anyhow", "cfg-if", "cranelift-codegen", "cranelift-control", - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", "cranelift-frontend", "cranelift-native", "cranelift-wasm", @@ -7266,15 +7725,15 @@ dependencies = [ "thiserror", "wasmparser 0.112.0", "wasmtime-cranelift-shared", - "wasmtime-environ 13.0.0", + "wasmtime-environ 13.0.1", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-cranelift-shared" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516d63bbe18219e64a9705cf3a2c865afe1fb711454ea03091dc85a1d708194d" +checksum = "4f6b197d68612f7dc3a17aa9f9587533715ecb8b4755609ce9baf7fb92b74ddc" dependencies = [ "anyhow", "cranelift-codegen", @@ -7283,7 +7742,7 @@ dependencies = [ "gimli 0.28.0", "object 0.32.1", "target-lexicon", - "wasmtime-environ 13.0.0", + "wasmtime-environ 13.0.1", ] [[package]] @@ -7307,12 +7766,12 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cef239d663885f1427f8b8f4fde7be6075249c282580d94b480f11953ca194" +checksum = "18e2558c8b04fd27764d8601d46b8dc39555b79720a41e626bce210a80758932" dependencies = [ "anyhow", - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", "gimli 0.28.0", "indexmap 2.1.0", "log", @@ -7324,8 +7783,8 @@ dependencies = [ "wasm-encoder 0.32.0", "wasmparser 0.112.0", "wasmprinter", - "wasmtime-component-util 13.0.0", - "wasmtime-types 13.0.0", + "wasmtime-component-util 13.0.1", + "wasmtime-types 13.0.1", ] [[package]] @@ -7336,21 +7795,21 @@ checksum = "e0e22d42113a1181fee3477f96639fd88c757b303f7083e866691f47a06065c5" dependencies = [ "cc", "cfg-if", - "rustix 0.37.25", + "rustix 0.37.27", "wasmtime-asm-macros 11.0.2", "windows-sys", ] [[package]] name = "wasmtime-fiber" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef118b557df6193cd82cfb45ab57cd12388fedfe2bb76f090b2d77c96c1b56e" +checksum = "a615a2cf64a49c0dc659c7d850c6cd377b975e0abfdcf0888b282d274a82e730" dependencies = [ "cc", "cfg-if", - "rustix 0.38.13", - "wasmtime-asm-macros 13.0.0", + "rustix 0.38.21", + "wasmtime-asm-macros 13.0.1", "wasmtime-versioned-export-macros", "windows-sys", ] @@ -7370,7 +7829,7 @@ dependencies = [ "log", "object 0.30.4", "rustc-demangle", - "rustix 0.37.25", + "rustix 0.37.27", "serde", "target-lexicon", "wasmtime-environ 11.0.2", @@ -7381,9 +7840,9 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8089d5909b8f923aad57702ebaacb7b662aa9e43a3f71e83e025c5379a1205f" +checksum = "cd775514b8034b85b0323bfdc60abb1c28d27dbf6e22aad083ed57dac95cf72e" dependencies = [ "addr2line 0.21.0", "anyhow", @@ -7395,14 +7854,14 @@ dependencies = [ "log", "object 0.32.1", "rustc-demangle", - "rustix 0.38.13", + "rustix 0.38.21", "serde", "serde_derive", "target-lexicon", - "wasmtime-environ 13.0.0", - "wasmtime-jit-debug 13.0.0", - "wasmtime-jit-icache-coherence 13.0.0", - "wasmtime-runtime 13.0.0", + "wasmtime-environ 13.0.1", + "wasmtime-jit-debug 13.0.1", + "wasmtime-jit-icache-coherence 13.0.1", + "wasmtime-runtime 13.0.1", "windows-sys", ] @@ -7417,13 +7876,13 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b13924aedf6799ad66edb25500a20e3226629978b30a958c55285352bad130a" +checksum = "c054e27c6ce2a6191edabe89e646da013044dd5369e1d203c89f977f9bd32937" dependencies = [ "object 0.32.1", "once_cell", - "rustix 0.38.13", + "rustix 0.38.21", "wasmtime-versioned-export-macros", ] @@ -7440,9 +7899,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ff5f3707a5e3797deeeeac6ac26b2e1dd32dbc06693c0ab52e8ac4d18ec706" +checksum = "7f323977cddf4a262d1b856366b665c5b4d01793c57b79fb42505b9fd9e61e5b" dependencies = [ "cfg-if", "libc", @@ -7466,7 +7925,7 @@ dependencies = [ "memoffset 0.8.0", "paste", "rand", - "rustix 0.37.25", + "rustix 0.37.27", "sptr", "wasmtime-asm-macros 11.0.2", "wasmtime-environ 11.0.2", @@ -7477,9 +7936,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ab4ce04ac05342edfa7f42895f2a5d8b16ee914330869acb865cd1facf265f" +checksum = "29e26461bba043f73cb4183f4ce0d606c0eaac112475867b11e5ea36fe1cac8e" dependencies = [ "anyhow", "cc", @@ -7493,13 +7952,13 @@ dependencies = [ "memoffset 0.9.0", "paste", "rand", - "rustix 0.38.13", + "rustix 0.38.21", "sptr", "wasm-encoder 0.32.0", - "wasmtime-asm-macros 13.0.0", - "wasmtime-environ 13.0.0", - "wasmtime-fiber 13.0.0", - "wasmtime-jit-debug 13.0.0", + "wasmtime-asm-macros 13.0.1", + "wasmtime-environ 13.0.1", + "wasmtime-fiber 13.0.1", + "wasmtime-jit-debug 13.0.1", "wasmtime-versioned-export-macros", "wasmtime-wmemcheck", "windows-sys", @@ -7519,11 +7978,11 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf61e21d5bd95e1ad7fa42b7bdabe21220682d6a6046d376edca29760849222" +checksum = "6fd7e9b29fee64eea5058cb5e7cb3480b52c2f1312d431d16ea8617ceebeb421" dependencies = [ - "cranelift-entity 0.100.0", + "cranelift-entity 0.100.1", "serde", "serde_derive", "thiserror", @@ -7532,13 +7991,13 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe877472cbdd6d96b4ecdc112af764e3b9d58c2e4175a87828f892ab94c60643" +checksum = "6362c557c36d8ad4aaab735f14ed9e4f78d6b40ec85a02a88fd859af87682e52" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -7550,7 +8009,7 @@ dependencies = [ "anyhow", "io-extras", "libc", - "rustix 0.37.25", + "rustix 0.37.27", "wasi-common", "wasi-tokio", "wasmtime 11.0.2", @@ -7560,9 +8019,9 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc5a770003807c55f2187a0092dea01722b0e24151e35816bd5091538bb8e88" +checksum = "aa5fc7212424c04c01a20bfa66c4c518e8749dde6546f5e05815dcacbec80723" dependencies = [ "anyhow", "cranelift-codegen", @@ -7571,7 +8030,7 @@ dependencies = [ "target-lexicon", "wasmparser 0.112.0", "wasmtime-cranelift-shared", - "wasmtime-environ 13.0.0", + "wasmtime-environ 13.0.1", "winch-codegen", ] @@ -7588,21 +8047,21 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62003d48822f89cc393e93643366ddbee1766779c0874353b8ba2ede4679fbf9" +checksum = "dcc03bd58f77a68dc6a0b2ba2f8e64b1f902b50389d21bbcc690ef2f3bb87198" dependencies = [ "anyhow", "heck", "indexmap 2.1.0", - "wit-parser 0.11.1", + "wit-parser 0.11.3", ] [[package]] name = "wasmtime-wmemcheck" -version = "13.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5412bb464066d64c3398c96e6974348f90fa2a55110ad7da3f9295438cd4de84" +checksum = "1e485bf54eba675ca615f8f55788d3a8cd44e7bd09b8b4011edc22c2c41d859e" [[package]] name = "wast" @@ -7615,30 +8074,30 @@ dependencies = [ [[package]] name = "wast" -version = "66.0.2" +version = "67.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cb43b0ac6dd156f2c375735ccfd72b012a7c0a6e6d09503499b8d3cb6e6072" +checksum = "a974d82fac092b5227c1663e16514e7a85f32014e22e6fdcb08b71aec9d3fb1e" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.35.0", + "wasm-encoder 0.36.2", ] [[package]] name = "wat" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e367582095d2903caeeea9acbb140e1db9c7677001efa4347c3687fd34fe7072" +checksum = "adb220934f92f8551144c0003d1bc57a060674c99139f45ed623fbbf6d9262e7" dependencies = [ - "wast 66.0.2", + "wast 67.0.1", ] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -7646,14 +8105,20 @@ dependencies = [ [[package]] name = "web-time" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8208e3fdbc243c8fd30805721869242a7f6de3e2e9f3b057652ab36e52ae1e87" +checksum = "57099a701fb3a8043f993e8228dc24229c7b942e2b009a1b962e54489ba1d3bf" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "websocket-relay" version = "0.1.0" @@ -7732,9 +8197,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -7747,9 +8212,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50647204d600a2a112eefac0645ba6653809a15bd362c7e4e6a049a5bdff0de9" +checksum = "b9b01ca6722f7421c9cdbe4c9b62342ce864d0a9e8736d56dac717a86b1a65ae" dependencies = [ "anyhow", "cranelift-codegen", @@ -7758,16 +8223,7 @@ dependencies = [ "smallvec", "target-lexicon", "wasmparser 0.112.0", - "wasmtime-environ 13.0.0", -] - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", + "wasmtime-environ 13.0.1", ] [[package]] @@ -7857,9 +8313,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -7900,94 +8356,94 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357bb8e2932df531f83b052264b050b81ba0df90ee5a59b2d1d3949f344f81e5" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "windows-sys", ] [[package]] name = "wit-bindgen" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d92ce0ca6b6074059413a9581a637550c3a740581c854f9847ec293c8aed71" +checksum = "38726c54a5d7c03cac28a2a8de1006cfe40397ddf6def3f836189033a413bc08" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565b945ae074886071eccf9cdaf8ccd7b959c2b0d624095bea5fe62003e8b3e0" +checksum = "c8bf1fddccaff31a1ad57432d8bfb7027a7e552969b6c68d6d8820dcf5c2371f" dependencies = [ "anyhow", - "wit-component 0.16.0", - "wit-parser 0.12.1", + "wit-component 0.17.0", + "wit-parser 0.12.2", ] [[package]] name = "wit-bindgen-rust" -version = "0.13.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5695ff4e41873ed9ce56d2787e6b5772bdad9e70e2c1d2d160621d1762257f4f" +checksum = "0e7200e565124801e01b7b5ddafc559e1da1b2e1bed5364d669cd1d96fb88722" dependencies = [ "anyhow", "heck", "wasm-metadata", "wit-bindgen-core", - "wit-component 0.16.0", + "wit-component 0.17.0", ] [[package]] name = "wit-bindgen-rust-macro" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91835ea4231da1fe7971679d505ba14be7826e192b6357f08465866ef482e08" +checksum = "4ae33920ad8119fe72cf59eb00f127c0b256a236b9de029a1a10397b1f38bdbd" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", "wit-bindgen-core", "wit-bindgen-rust", - "wit-component 0.16.0", + "wit-component 0.17.0", ] [[package]] name = "wit-component" -version = "0.14.2" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af872ef43ecb73cc49c7bd2dd19ef9117168e183c78cf70000dca0e14b6a5473" +checksum = "66981fe851118de3b6b7a92f51ce8a86b919569c37becbeca8df9bd30141da25" dependencies = [ "anyhow", - "bitflags 2.4.0", + "bitflags 2.4.1", "indexmap 2.1.0", "log", "serde", "serde_json", - "wasm-encoder 0.33.1", + "wasm-encoder 0.33.2", "wasm-metadata", "wasmparser 0.113.3", - "wit-parser 0.11.1", + "wit-parser 0.11.3", ] [[package]] name = "wit-component" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87488b57a08e2cbbd076b325acbe7f8666965af174d69d5929cd373bd54547f" +checksum = "480cc1a078b305c1b8510f7c455c76cbd008ee49935f3a6c5fd5e937d8d95b1e" dependencies = [ "anyhow", - "bitflags 2.4.0", + "bitflags 2.4.1", "indexmap 2.1.0", "log", "serde", "serde_derive", "serde_json", - "wasm-encoder 0.35.0", + "wasm-encoder 0.36.2", "wasm-metadata", - "wasmparser 0.115.0", - "wit-parser 0.12.1", + "wasmparser 0.116.1", + "wit-parser 0.12.2", ] [[package]] @@ -8008,9 +8464,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcd022610436a1873e60bfdd9b407763f2404adf7d1cb57912c7ae4059e57a5" +checksum = "a39edca9abb16309def3843af73b58d47d243fe33a9ceee572446bcc57556b9a" dependencies = [ "anyhow", "id-arena", @@ -8026,9 +8482,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ace9943d89bbf3dbbc71b966da0e7302057b311f36a4ac3d65ddfef17b52cf" +checksum = "43771ee863a16ec4ecf9da0fc65c3bbd4a1235c8e3da5f094b562894843dfa76" dependencies = [ "anyhow", "id-arena", @@ -8157,6 +8613,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -8174,7 +8650,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.39", ] [[package]] @@ -8198,12 +8674,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/README.md b/README.md index 4567d7eb..ddf889d9 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ ## Outline - [Quickstart](#quickstart) +- [Packages](#packages) - [Running Examples](#running-examples) - [Workspace](#workspace) - [Contributing](#contributing) @@ -80,6 +81,14 @@ components (currently focused on authoring in Rust), please jump into our [`homestar-functions`](./homestar-functions) directory and check out our examples there. +## Packages + +Each `homestar` release will also build packages for distribution across +different platforms. + +- [homebrew][homebrew]: `brew install fission-codes/fission/homestar` + This includes `ipfs` in the install by default. + ## Running Examples All [examples](./examples) contain instructions for running @@ -187,6 +196,7 @@ conditions. [demo-1]: https://www.loom.com/share/3204037368fe426ba3b4c952b0691c5c [foundations-for-openworld-compute]: https://youtu.be/dRz5mau6fsY [guest]: https://github.com/bytecodealliance/wit-bindgen#supported-guest-languages +[homebrew]: https://brew.sh/ [host-runtime]: https://github.com/bytecodealliance/wit-bindgen#host-runtimes-for-components [ipfs-thing-ipvm]: https://www.youtube.com/watch?v=rzJWk1nlYvs [ipld]: https://ipld.io/ diff --git a/examples/websocket-relay/README.md b/examples/websocket-relay/README.md index a6ac7bd5..46a41427 100644 --- a/examples/websocket-relay/README.md +++ b/examples/websocket-relay/README.md @@ -63,7 +63,14 @@ if they've been previously run. On macOS, for example, a simple homebrew install would install everything you need: `brew install rust npm ipfs` -We have packaged homestar binaries via brew, so `brew install fission-codes/fission/homestar` will install everything you need, including `ipfs`. You will still need npm to run this example. From this folder, you can run the example via `homestar start --config ./config/settings.toml --db homestar.db`. +We have packaged homestar binaries via brew, so +`brew install fission-codes/fission/homestar` will install everything you need, +including `ipfs`. You will still need npm to run this example. From this folder, +you can then run the example like this: + +``` +homestar start --config ./config/settings.toml --db homestar.db` +``` Running `homestar` via `cargo run` requires a minimum Rust version of `1.70.0`. If you've got an older install of rust, update it with diff --git a/flake.lock b/flake.lock index 76142117..4c33b930 100644 --- a/flake.lock +++ b/flake.lock @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1699236891, - "narHash": "sha256-J0uhoYlufJncIFbM/pAoggzHK/qERB9KfQRkmYD56yo=", + "lastModified": 1699323235, + "narHash": "sha256-ZFRItRv0dDSzsfpqSjj9qWM/SA1kRrOk6R04qhBZuxM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a7f9bf91dc5065d470cd57169a9f2ebdbdfe1f24", + "rev": "8a9d6f544c08ee898c7f3761cc9587be7565db5e", "type": "github" }, "original": { diff --git a/homestar-functions/README.md b/homestar-functions/README.md index 5ad4c1a1..c81354b3 100644 --- a/homestar-functions/README.md +++ b/homestar-functions/README.md @@ -51,9 +51,8 @@ cd test && cargo build --target wasm32-unknown-unknown --profile release-wasm-fn cargo build -p homestar-functions-test --target wasm32-unknown-unknown --profile release-wasm-fn ``` -Guest Wasm modules will be generated within the -`../target/wasm32-unknown-unknown/release` directory, e.g. -`../target/wasm32-unknown-unknown/release-wasm-fn/homestar_functions_test.wasm`. +Guest Wasm modules will be generated within the top-level `homestar` directory: +`./target/wasm32-unknown-unknown/release-wasm-fn/homestar_functions_test.wasm`. Sadly, this module is **not yet** an actual `component`. But, we can leverage the [wasm-tools][wasm-tools] tooling ([wit-component][wit-component] in @@ -99,7 +98,7 @@ conditions. [kv-demo]: https://github.com/Mossaka/keyvalue-component-model-demo [spiderlightning]: https://github.com/deislabs/spiderlightning [wasi]: https://github.com/WebAssembly/WASI -[wasm32]: https://doc.rust-lang.org/rustc/platform-support/wasm64-unknown-unknown.html +[wasm32]: https://rustwasm.github.io/docs/wasm-pack/prerequisites/non-rustup-setups.html#manually-add-wasm32-unknown-unknown [wasmtime]: https://github.com/bytecodealliance/wasmtime [wasm-tools]: https://github.com/bytecodealliance/wasm-tools [wit-bindgen]: https://github.com/bytecodealliance/wit-bindgen diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 59f5aa20..3a9233d1 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -39,10 +39,6 @@ path = "tests/main.rs" anyhow = { workspace = true } async-trait = "0.1" atomic_refcell = { workspace = true } -axum = { version = "0.6", default-features = false, features = [ - "ws", - "headers", -], optional = true } byte-unit = { workspace = true } chrono = { workspace = true } clap = { version = "4.4", default-features = false, features = [ @@ -68,6 +64,7 @@ diesel = { version = "2.1", default-features = false, features = [ ] } diesel_migrations = "2.1" dotenvy = "0.15" +dyn-clone = "1.0" enum-assoc = { workspace = true } faststr = { workspace = true } flume = { version = "0.11", default-features = false, features = ["async"] } @@ -86,6 +83,9 @@ ipfs-api-backend-hyper = { version = "0.6", default-features = false, features = "with-send-sync", ], optional = true } itertools = { workspace = true } +jsonrpsee = { version = "0.20", default-features = false, features = [ + "server", +] } libipld = { workspace = true } libp2p = { version = "0.52", default-features = false, features = [ "kad", @@ -106,20 +106,23 @@ libp2p = { version = "0.52", default-features = false, features = [ libsqlite3-sys = { version = "0.26", default-features = false, features = [ "bundled", ] } -metrics = { version = "0.21", default-features = false, optional = true } +metrics = { version = "0.21", default-features = false } metrics-exporter-prometheus = { version = "0.12.1", default-features = false, features = [ "http-listener", -], optional = true } +] } +metrics-util = "0.15" miette = { version = "5.10", default-features = false, features = ["fancy"] } moka = { version = "0.12.1", default-features = false, features = [ "future", "sync", ] } -names = { version = "0.14", default-features = false, optional = true } +names = { version = "0.14", default-features = false } +once_cell = { version = "1.18", default-features = false } proptest = { version = "1.2", optional = true } puffin = { version = "0.17", default-features = false, optional = true } puffin_egui = { version = "0.23.0", default-features = false, optional = true } rand = { workspace = true } +regex = "1.10" reqwest = { version = "0.11", default-features = false, features = [ "blocking", "json", @@ -128,7 +131,7 @@ sec1 = { version = "0.7", default-features = false, features = ["pem"] } semver = { version = "1.0", default-features = false } serde = { workspace = true } serde_ipld_dagcbor = { workspace = true } -serde_json = { version = "1.0", optional = true, default-features = false, features = [ +serde_json = { version = "1.0", default-features = false, features = [ "raw_value", ] } serde_with = { version = "3.3", default-features = false, features = [ @@ -152,7 +155,19 @@ tokio = { workspace = true } tokio-serde = { version = "0.8", default-features = false, features = [ "messagepack", ] } +tokio-stream = { version = "0.1", default-features = false, features = [ + "sync", +] } tokio-util = { version = "0.7", default-features = false } +tower = { version = "0.4", default-features = false, features = [ + "log", + "timeout", +] } +tower-http = { version = "0.4", default-features = false, features = [ + "trace", + "catch-panic", + "cors", +] } tracing = { workspace = true } tracing-appender = "0.2" tracing-logfmt = "0.3" @@ -162,6 +177,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ "registry", ] } tryhard = "0.5" +typetag = "0.2" url = "2.4" wnfs-common = "0.1" @@ -175,8 +191,11 @@ homestar-core = { version = "0.1", path = "../homestar-core", features = [ "test-utils", ] } homestar_runtime_proc_macro = { path = "src/test_utils/proc_macro", package = "homestar-runtime-tests-proc-macro" } +jsonrpsee = { version = "0.20", default-features = false, features = [ + "client", +] } +maplit = "1.0" nix = { version = "0.27", features = ["signal"] } -once_cell = { version = "1.18", default-features = false } predicates = { version = "3.0", default-features = false } prometheus-parse = "0.2.4" rand = { workspace = true } @@ -186,7 +205,10 @@ serial_test = { version = "2.0", default-features = false, features = [ "file_locks", ] } strip-ansi-escapes = "0.2.0" -tokio-tungstenite = { version = "0.20", default-features = false } +sysinfo = { version = "0.29", default-features = false } +tokio-tungstenite = { version = "0.20", default-features = false, features = [ + "connect", +] } wait-timeout = "0.2" [features] @@ -195,13 +217,11 @@ dev = ["ansi-logs", "ipfs", "monitoring", "websocket-notify"] ansi-logs = ["tracing-logfmt/ansi_logs"] console = ["dep:console-subscriber"] ipfs = ["dep:ipfs-api", "dep:ipfs-api-backend-hyper"] -metrics = ["dep:metrics", "dep:metrics-exporter-prometheus"] -monitoring = ["metrics", "dep:sysinfo"] +monitoring = ["dep:sysinfo"] profile = ["dep:puffin", "dep:puffin_egui"] test-utils = ["dep:proptest"] wasmtime-default = ["homestar-wasm/default"] -websocket-notify = ["websocket-server", "dep:serde_json", "dep:names"] -websocket-server = ["dep:axum"] +websocket-notify = [] [package.metadata.docs.rs] all-features = true diff --git a/homestar-runtime/fixtures/settings.toml b/homestar-runtime/fixtures/settings.toml index edf2cf1b..9561abe7 100644 --- a/homestar-runtime/fixtures/settings.toml +++ b/homestar-runtime/fixtures/settings.toml @@ -2,5 +2,5 @@ [node.network] events_buffer_len = 1000 -websocket_port = 9999 +webserver_port = 9999 node_addresses = ["/ip4/127.0.0.1/tcp/9998/ws"] diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index 5af1044c..1fe7e920 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -1,7 +1,7 @@ //! [EventHandler] implementation for handling network events and messages. -#[cfg(feature = "websocket-server")] -use crate::network::ws; +#[cfg(feature = "websocket-notify")] +use crate::network::webserver; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -45,11 +45,8 @@ where } /// Event loop handler for [libp2p] network events and commands. -#[cfg(feature = "websocket-server")] -#[cfg_attr( - docsrs, - doc(cfg(all(feature = "websocket-server", feature = "websocket-notify"))) -)] +#[cfg(feature = "websocket-notify")] +#[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(missing_debug_implementations, dead_code)] pub(crate) struct EventHandler { receipt_quorum: usize, @@ -65,7 +62,7 @@ pub(crate) struct EventHandler { request_response_senders: FnvHashMap, rendezvous: Rendezvous, pubsub_enabled: bool, - ws_msg_sender: ws::Notifier, + ws_msg_sender: webserver::Notifier, node_addresses: Vec, announce_addresses: Vec, external_address_limit: u32, @@ -73,7 +70,7 @@ pub(crate) struct EventHandler { } /// Event loop handler for [libp2p] network events and commands. -#[cfg(not(feature = "websocket-server"))] +#[cfg(not(feature = "websocket-notify"))] #[allow(missing_debug_implementations, dead_code)] pub(crate) struct EventHandler { receipt_quorum: usize, @@ -123,12 +120,12 @@ where } /// Create an [EventHandler] with channel sender/receiver defaults. - #[cfg(feature = "websocket-server")] + #[cfg(feature = "websocket-notify")] pub(crate) fn new( swarm: Swarm, db: DB, settings: &settings::Node, - ws_msg_sender: ws::Notifier, + ws_msg_sender: webserver::Notifier, ) -> Self { let (sender, receiver) = Self::setup_channel(settings); let sender = Arc::new(sender); @@ -163,7 +160,7 @@ where } /// Create an [EventHandler] with channel sender/receiver defaults. - #[cfg(not(feature = "websocket-server"))] + #[cfg(not(feature = "websocket-notify"))] pub(crate) fn new(swarm: Swarm, db: DB, settings: &settings::Node) -> Self { let (sender, receiver) = Self::setup_channel(settings); let sender = Arc::new(sender); @@ -206,13 +203,10 @@ where /// [tokio::sync::broadcast::Sender] for sending messages through the /// webSocket server to subscribers. - #[cfg(all(feature = "websocket-server", feature = "websocket-notify"))] - #[cfg_attr( - docsrs, - doc(cfg(all(feature = "websocket-server", feature = "websocket-notify"))) - )] + #[cfg(feature = "websocket-notify")] + #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(dead_code)] - pub(crate) fn ws_sender(&self) -> ws::Notifier { + pub(crate) fn ws_sender(&self) -> webserver::Notifier { self.ws_msg_sender.clone() } diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 9a770efe..9e07587d 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -2,7 +2,7 @@ use super::EventHandler; #[cfg(feature = "websocket-notify")] -use crate::network::ws::notifier::NotifyReceipt; +use crate::network::webserver::notifier::NotifyReceipt; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -16,9 +16,9 @@ use crate::{ }; use anyhow::Result; use async_trait::async_trait; -use homestar_core::workflow::Pointer; +use homestar_core::workflow::Receipt as InvocationReceipt; #[cfg(feature = "websocket-notify")] -use homestar_core::{ipld::DagJson, workflow::Receipt as InvocationReceipt}; +use homestar_core::{ipld::DagJson, workflow::Pointer}; use libipld::{Cid, Ipld}; use libp2p::{ kad::{record::Key, Quorum, Record}, @@ -32,6 +32,7 @@ use tokio::sync::oneshot; use tracing::{error, info, warn}; /// A [Receipt] captured (inner) event. +#[allow(dead_code)] #[derive(Debug, Clone)] pub(crate) struct Captured { /// The captured receipt. @@ -229,6 +230,7 @@ impl Captured { } } + #[allow(dead_code)] fn store_and_notify( mut self, event_handler: &mut EventHandler, @@ -238,12 +240,12 @@ impl Captured { { let receipt = Db::find_receipt_by_cid(self.receipt, &mut event_handler.db.conn()?)?; let invocation_receipt = InvocationReceipt::from(&receipt); - let invocation_notification = invocation_receipt.clone(); let instruction_bytes = receipt.instruction_cid_as_bytes(); let receipt_cid = receipt.cid(); - #[cfg(all(feature = "websocket-server", feature = "websocket-notify"))] + #[cfg(feature = "websocket-notify")] { + let invocation_notification = invocation_receipt.clone(); let ws_tx = event_handler.ws_sender(); let metadata = self.metadata.to_owned(); let receipt = NotifyReceipt::with(invocation_notification, receipt_cid, metadata); diff --git a/homestar-runtime/src/logger.rs b/homestar-runtime/src/logger.rs index 4deb4d87..767d975f 100644 --- a/homestar-runtime/src/logger.rs +++ b/homestar-runtime/src/logger.rs @@ -59,6 +59,7 @@ fn init( .add_directive("tarpc=info".parse().expect(DIRECTIVE_EXPECT)) .add_directive("tower_http=info".parse().expect(DIRECTIVE_EXPECT)) .add_directive("moka=info".parse().expect(DIRECTIVE_EXPECT)) + .add_directive("jsonrpsee=info".parse().expect(DIRECTIVE_EXPECT)) }); #[cfg(all( diff --git a/homestar-runtime/src/metrics.rs b/homestar-runtime/src/metrics.rs index bba7533f..e0b308e9 100644 --- a/homestar-runtime/src/metrics.rs +++ b/homestar-runtime/src/metrics.rs @@ -2,19 +2,33 @@ use crate::settings; use anyhow::Result; +use metrics_exporter_prometheus::PrometheusHandle; +#[cfg(feature = "monitoring")] use tokio::runtime::Handle; mod exporter; +#[cfg(feature = "monitoring")] mod node; /// Start metrics collection and setup scrape endpoint. -pub(crate) async fn start(settings: &settings::Monitoring) -> Result<()> { - let handle = Handle::current(); - exporter::setup_metrics_recorder(settings)?; +#[cfg(feature = "monitoring")] +pub(crate) async fn start( + monitor_settings: &settings::Monitoring, + network_settings: &settings::Network, +) -> Result { + let metrics_hdl = exporter::setup_metrics_recorder(network_settings)?; // Spawn tick-driven process collection task - #[cfg(feature = "monitoring")] - handle.spawn(node::collect_metrics(settings.process_collector_interval)); + let handle = Handle::current(); + handle.spawn(node::collect_metrics( + monitor_settings.process_collector_interval, + )); + + Ok(metrics_hdl) +} - Ok(()) +#[cfg(not(feature = "monitoring"))] +pub(crate) async fn start(network_settings: &settings::Network) -> Result { + let metrics_hdl = exporter::setup_metrics_recorder(network_settings)?; + Ok(metrics_hdl) } diff --git a/homestar-runtime/src/metrics/exporter.rs b/homestar-runtime/src/metrics/exporter.rs index 675c53a5..f3e0c11d 100644 --- a/homestar-runtime/src/metrics/exporter.rs +++ b/homestar-runtime/src/metrics/exporter.rs @@ -1,31 +1,43 @@ //! Metrics Prometheus recorder. -use crate::{metrics::node, settings}; -use metrics_exporter_prometheus::{Matcher, PrometheusBuilder}; +#[cfg(feature = "monitoring")] +use crate::metrics::node; +use crate::settings; +use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle}; +use metrics_util::layers::{PrefixLayer, Stack}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use tokio::runtime::Handle; /// Set up Prometheus buckets for matched metrics and install recorder. -pub(crate) fn setup_metrics_recorder(settings: &settings::Monitoring) -> anyhow::Result<()> { +pub(crate) fn setup_metrics_recorder( + settings: &settings::Network, +) -> anyhow::Result { const EXPONENTIAL_SECONDS: &[f64] = &[ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, ]; - let socket = SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - settings.metrics_port, - ); + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), settings.metrics_port); - #[cfg(feature = "monitoring")] - node::describe(); - - PrometheusBuilder::new() + let (recorder, exporter) = PrometheusBuilder::new() .set_buckets_for_metric( Matcher::Suffix("_duration_seconds".to_string()), EXPONENTIAL_SECONDS, )? .with_http_listener(socket) - .install() + .build() .expect("failed to install recorder/exporter"); - Ok(()) + let rt_hdl = Handle::current(); + rt_hdl.spawn(exporter); + + let hdl = recorder.handle(); + + Stack::new(recorder) + .push(PrefixLayer::new("homestar")) + .install()?; + + #[cfg(feature = "monitoring")] + node::describe(); + + Ok(hdl) } diff --git a/homestar-runtime/src/metrics/node.rs b/homestar-runtime/src/metrics/node.rs index 2544692e..bb58f389 100644 --- a/homestar-runtime/src/metrics/node.rs +++ b/homestar-runtime/src/metrics/node.rs @@ -1,14 +1,13 @@ //! Node metrics, including system, process, network, and database information -use crate::db::ENV as DATABASE_URL; +use crate::Db; use anyhow::{anyhow, Context, Result}; use metrics::{describe_counter, describe_gauge, Unit}; -use std::{env, time::Duration}; +use std::time::Duration; use sysinfo::{ get_current_pid, CpuRefreshKind, Disk, DiskExt, NetworkExt, Networks, NetworksExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt, }; -use tokio::fs; use tracing::{info, warn}; /// Create and describe gauges for node metrics. @@ -152,11 +151,11 @@ async fn collect_stats(sys: System) -> Result<()> { .iter() .fold(0, |acc, interface| acc + interface.1.received()) } - async fn compute_database_size() -> Option { - let url = env::var(DATABASE_URL).unwrap(); - match fs::metadata(url).await { - Ok(metadata) => Some(metadata.len()), - Err(_) => None, + async fn compute_database_size() -> Option { + if let Ok(size) = Db::size().await { + Some(size.get_value()) + } else { + None } } @@ -223,7 +222,7 @@ async fn collect_stats(sys: System) -> Result<()> { // Database metrics if let Some(database_size) = compute_database_size().await { - metrics::gauge!("database_size_bytes", database_size as f64); + metrics::gauge!("database_size_bytes", database_size); } Ok(()) diff --git a/homestar-runtime/src/network/mod.rs b/homestar-runtime/src/network/mod.rs index 257f0ae0..01a55a31 100644 --- a/homestar-runtime/src/network/mod.rs +++ b/homestar-runtime/src/network/mod.rs @@ -1,7 +1,9 @@ -//! [libp2p], [websocket], and [ipfs] networking interfaces. +//! [libp2p], multi-use [http] and [websocket] server, and [ipfs] networking +//! interfaces. //! //! [libp2p]: libp2p -//! [websocket]: axum::extract::ws +//! [http]: jsonrpsee::server +//! [websocket]: jsonrpsee::server //! [ipfs]: ipfs_api pub(crate) mod error; @@ -10,8 +12,9 @@ pub(crate) mod ipfs; pub(crate) mod pubsub; pub mod rpc; pub(crate) mod swarm; -#[cfg(feature = "websocket-server")] -pub(crate) mod ws; +pub(crate) mod webserver; +#[allow(unused_imports)] +pub(crate) use error::Error; #[cfg(feature = "ipfs")] pub(crate) use ipfs::IpfsCli; diff --git a/homestar-runtime/src/network/rpc.rs b/homestar-runtime/src/network/rpc.rs index 9f4f0e72..0452118e 100644 --- a/homestar-runtime/src/network/rpc.rs +++ b/homestar-runtime/src/network/rpc.rs @@ -47,6 +47,7 @@ pub(crate) enum ServerMessage { /// /// [Workflow]: homestar_core::Workflow RunErr(runner::Error), + /// For skipping server messages. Skip, } diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs new file mode 100644 index 00000000..e0332a3e --- /dev/null +++ b/homestar-runtime/src/network/webserver.rs @@ -0,0 +1,430 @@ +//! Sets up a websocket server for sending and receiving messages from browser +//! clients. + +use crate::{runner, runner::WsSender, settings}; +use anyhow::{anyhow, Result}; +use faststr::FastStr; +use homestar_core::Workflow; +use homestar_wasm::io::Arg; +use http::{header::CONTENT_TYPE, Method}; +use jsonrpsee::{ + self, + server::{middleware::ProxyGetRequestLayer, ServerHandle}, +}; +use metrics_exporter_prometheus::PrometheusHandle; +use std::{ + net::{IpAddr, SocketAddr, TcpListener}, + str::FromStr, + time::Duration, +}; +use tokio::runtime::Handle; +#[cfg(feature = "websocket-notify")] +use tokio::sync::broadcast; +use tower_http::cors::{self, CorsLayer}; +use tracing::info; + +pub(crate) mod listener; +#[cfg(feature = "websocket-notify")] +pub(crate) mod notifier; +mod prom; +mod rpc; + +#[cfg(feature = "websocket-notify")] +pub(crate) use notifier::Notifier; +use rpc::{Context, JsonRpc}; + +/// Message type for messages sent back from the +/// websocket server to the [runner] for example. +/// +/// [runner]: crate::Runner +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum Message { + /// Error attempting to run a [Workflow]. + RunErr(runner::Error), + /// Run a workflow, given a tuple of name, and [Workflow]. + RunWorkflow((FastStr, Workflow<'static, Arg>)), + /// Acknowledgement of a [Workflow] run. + AckWorkflow, +} + +/// WebSocket server fields. +#[cfg(feature = "websocket-notify")] +#[derive(Clone, Debug)] +pub(crate) struct Server { + /// Address of the websocket server. + addr: SocketAddr, + /// TODO + capacity: usize, + /// Message sender for broadcasting to clients connected to the + /// websocket server. + notifier: Notifier, + /// Receiver timeout for the websocket server. + receiver_timeout: Duration, + /// TODO + webserver_timeout: Duration, +} + +#[cfg(not(feature = "websocket-notify"))] +#[derive(Clone, Debug)] +pub(crate) struct Server { + /// Address of the websocket server. + addr: SocketAddr, + /// TODO + capacity: usize, + /// Receiver timeout for the websocket server. + receiver_timeout: Duration, + /// TODO + webserver_timeout: Duration, +} + +impl Server { + /// Setup bounded, MPMC channel for runtime to send and received messages + /// through the websocket connection(s). + #[cfg(feature = "websocket-notify")] + fn setup_channel( + capacity: usize, + ) -> (broadcast::Sender>, broadcast::Receiver>) { + broadcast::channel(capacity) + } + + #[cfg(feature = "websocket-notify")] + pub(crate) fn new(settings: &settings::Network) -> Result { + let (sender, _receiver) = Self::setup_channel(settings.websocket_capacity); + let host = IpAddr::from_str(&settings.webserver_host.to_string())?; + let port_setting = settings.webserver_port; + let addr = if port_available(host, port_setting) { + SocketAddr::from((host, port_setting)) + } else { + let port = (port_setting..port_setting + 1000) + .find(|port| port_available(host, *port)) + .ok_or_else(|| anyhow!("no free TCP ports available"))?; + SocketAddr::from((host, port)) + }; + + Ok(Self { + addr, + capacity: settings.websocket_capacity, + notifier: Notifier::new(sender), + receiver_timeout: settings.websocket_receiver_timeout, + webserver_timeout: settings.webserver_timeout, + }) + } + + #[cfg(not(feature = "websocket-notify"))] + pub(crate) fn new(settings: &settings::Network) -> Result { + let host = IpAddr::from_str(&settings.webserver_host.to_string())?; + let port_setting = settings.webserver_port; + let addr = if port_available(host, port_setting) { + SocketAddr::from((host, port_setting)) + } else { + let port = (port_setting..port_setting + 1000) + .find(|port| port_available(host, *port)) + .ok_or_else(|| anyhow!("no free TCP ports available"))?; + SocketAddr::from((host, port)) + }; + + Ok(Self { + addr, + capacity: settings.websocket_capacity, + receiver_timeout: settings.websocket_receiver_timeout, + webserver_timeout: settings.webserver_timeout, + }) + } + + /// Start the websocket server. + #[cfg(feature = "websocket-notify")] + pub(crate) async fn start( + &self, + runner_sender: WsSender, + metrics_hdl: PrometheusHandle, + ) -> Result { + let module = JsonRpc::new(Context::new( + metrics_hdl, + self.notifier.clone(), + runner_sender, + self.receiver_timeout, + )) + .await?; + + self.start_inner(module).await + } + + /// Start the websocket server. + #[cfg(not(feature = "websocket-notify"))] + pub(crate) async fn start( + &self, + runner_sender: WsSender, + metrics_hdl: PrometheusHandle, + ) -> Result { + let module = JsonRpc::new(Context::new( + metrics_hdl, + runner_sender, + self.receiver_timeout, + )) + .await?; + self.start_inner(module).await + } + + /// Get websocket message sender for broadcasting messages to websocket + /// clients. + #[cfg(feature = "websocket-notify")] + pub(crate) fn notifier(&self) -> Notifier { + self.notifier.clone() + } + + async fn start_inner(&self, module: JsonRpc) -> Result { + let addr = self.addr; + info!("webserver listening on {}", addr); + + let cors = CorsLayer::new() + // Allow `POST` when accessing the resource + .allow_methods([Method::GET, Method::POST]) + // Allow requests from any origin + .allow_origin(cors::Any) + .allow_headers([CONTENT_TYPE]); + + let middleware = tower::ServiceBuilder::new() + .layer(ProxyGetRequestLayer::new("/health", rpc::HEALTH_ENDPOINT)?) + .layer(ProxyGetRequestLayer::new( + "/metrics", + rpc::METRICS_ENDPOINT, + )?) + .layer(cors) + .timeout(self.webserver_timeout); + + let runtime_hdl = Handle::current(); + + let server = jsonrpsee::server::Server::builder() + .custom_tokio_runtime(runtime_hdl.clone()) + .set_middleware(middleware) + .set_message_buffer_capacity(self.capacity as u32) + .build(addr) + .await + .expect("Webserver to startup"); + + let hdl = server.start(module.into_inner()); + runtime_hdl.spawn(hdl.clone().stopped()); + + Ok(hdl) + } +} + +fn port_available(host: IpAddr, port: u16) -> bool { + TcpListener::bind((host.to_string(), port)).is_ok() +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{metrics, settings::Settings}; + #[cfg(feature = "websocket-notify")] + use homestar_core::{ + ipld::DagJson, + test_utils, + workflow::{config::Resources, instruction::RunInstruction, prf::UcanPrf, Task}, + }; + #[cfg(feature = "websocket-notify")] + use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; + #[cfg(feature = "websocket-notify")] + use jsonrpsee::types::error::ErrorCode; + use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; + #[cfg(feature = "websocket-notify")] + use notifier::NotifyReceipt; + use tokio::sync::mpsc; + + #[tokio::test] + async fn ws_connect() { + let mut settings = Settings::load().unwrap(); + settings.node.network.metrics_port = 6000; + settings.node.network.webserver_port = 1200; + let server = Server::new(settings.node().network()).unwrap(); + #[cfg(feature = "monitoring")] + let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + #[cfg(not(feature = "monitoring"))] + let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + let http_url = format!("http://{}", server.addr); + + tokio_tungstenite::connect_async(ws_url.clone()) + .await + .unwrap(); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let ws_resp: serde_json::Value = client + .request(rpc::HEALTH_ENDPOINT, rpc_params![]) + .await + .unwrap(); + + assert_eq!(ws_resp, serde_json::json!({"healthy": true})); + let http_resp = reqwest::get(format!("{}/health", http_url)).await.unwrap(); + assert_eq!(http_resp.status(), 200); + let http_resp = http_resp.json::().await.unwrap(); + assert_eq!(http_resp, serde_json::json!({"healthy": true})); + } + + #[cfg(feature = "monitoring")] + #[tokio::test] + async fn ws_metrics_no_prefix() { + let mut settings = Settings::load().unwrap(); + settings.node.network.metrics_port = 6001; + settings.node.network.webserver_port = 1201; + let server = Server::new(settings.node().network()).unwrap(); + + let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let ws_resp1: serde_json::Value = client + .request(rpc::METRICS_ENDPOINT, rpc_params![]) + .await + .unwrap(); + + let len = if let serde_json::Value::Array(array) = &ws_resp1["metrics"] { + array.len() + } else { + panic!("expected array"); + }; + + assert!(len > 0); + } + + #[cfg(feature = "websocket-notify")] + #[tokio::test] + async fn ws_subscribe_unsubscribe_network_events() { + let mut settings = Settings::load().unwrap(); + settings.node.network.metrics_port = 6002; + settings.node.network.webserver_port = 1202; + let server = Server::new(settings.node().network()).unwrap(); + #[cfg(feature = "monitoring")] + let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + #[cfg(not(feature = "monitoring"))] + let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let client1 = WsClientBuilder::default().build(ws_url).await.unwrap(); + let mut sub: Subscription> = client1 + .subscribe( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + rpc_params![], + rpc::UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + ) + .await + .unwrap(); + + // send any bytes through (vec>, jsonrpsee::core::error::Error> = client + .subscribe( + rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![], + rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await; + + assert!(sub.is_err()); + + if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { + let check = ErrorCode::InvalidParams; + assert_eq!(err.code(), check.code()); + } else { + panic!("expected same error code"); + } + } + + #[cfg(feature = "websocket-notify")] + #[tokio::test] + async fn ws_subscribe_workflow_runner_timeout() { + let mut settings = Settings::load().unwrap(); + settings.node.network.metrics_port = 6004; + settings.node.network.webserver_port = 1204; + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let config = Resources::default(); + let instruction1 = test_utils::workflow::instruction::(); + let (instruction2, _) = test_utils::workflow::wasm_instruction_with_nonce::(); + + let task1 = Task::new( + RunInstruction::Expanded(instruction1), + config.clone().into(), + UcanPrf::default(), + ); + let task2 = Task::new( + RunInstruction::Expanded(instruction2), + config.into(), + UcanPrf::default(), + ); + + let workflow = Workflow::new(vec![task1.clone(), task2.clone()]); + let run_str = format!( + r#"{{"name": "test","workflow": {}}}"#, + workflow.to_json_string().unwrap() + ); + let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let sub: Result>, jsonrpsee::core::error::Error> = client + .subscribe( + rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![run], + rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await; + + assert!(sub.is_err()); + + // Assure error is not on parse of params, but due to runner + // timeout (as runner is not available). + if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { + let check = ErrorCode::InternalError; + assert_eq!(err.code(), check.code()); + } else { + panic!("expected same error code"); + } + } +} diff --git a/homestar-runtime/src/network/ws/listener.rs b/homestar-runtime/src/network/webserver/listener.rs similarity index 87% rename from homestar-runtime/src/network/ws/listener.rs rename to homestar-runtime/src/network/webserver/listener.rs index e933f7d7..47a37c7a 100644 --- a/homestar-runtime/src/network/ws/listener.rs +++ b/homestar-runtime/src/network/webserver/listener.rs @@ -1,9 +1,7 @@ -use std::borrow::Cow; - use faststr::FastStr; use homestar_core::{ipld::DagJson, Workflow}; use homestar_wasm::io::Arg; -use names::Name; +use names::{Generator, Name}; use serde::{de, Deserialize, Deserializer, Serialize}; use serde_json::value::RawValue; @@ -13,7 +11,6 @@ use serde_json::value::RawValue; /// implementation, which is not a direct [Deserialize] implementation. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub(crate) struct Run<'a> { - pub(crate) action: Cow<'static, str>, #[serde(default = "default_name")] pub(crate) name: FastStr, #[serde(deserialize_with = "from_raw_value")] @@ -21,7 +18,7 @@ pub(crate) struct Run<'a> { } fn default_name() -> FastStr { - let mut name_gen = names::Generator::with_naming(Name::Numbered); + let mut name_gen = Generator::with_naming(Name::Numbered); name_gen .next() .unwrap_or_else(|| "workflow".to_string()) @@ -36,6 +33,12 @@ where Workflow::from_json(raw_value.get().as_bytes()).map_err(de::Error::custom) } +/// Filter metrics by prefix. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) struct MetricsPrefix { + pub(crate) prefix: String, +} + #[cfg(test)] mod test { use super::*; @@ -64,13 +67,12 @@ mod test { let workflow = Workflow::new(vec![task1.clone(), task2.clone()]); let run = Run { - action: Cow::Borrowed("run"), name: "test".into(), workflow: workflow.clone(), }; let run_str = format!( - r#"{{"action": "run","name": "test","workflow": {}}}"#, + r#"{{"name": "test","workflow": {}}}"#, workflow.to_json_string().unwrap() ); diff --git a/homestar-runtime/src/network/ws/notifier.rs b/homestar-runtime/src/network/webserver/notifier.rs similarity index 100% rename from homestar-runtime/src/network/ws/notifier.rs rename to homestar-runtime/src/network/webserver/notifier.rs diff --git a/homestar-runtime/src/network/webserver/prom.rs b/homestar-runtime/src/network/webserver/prom.rs new file mode 100644 index 00000000..622b8850 --- /dev/null +++ b/homestar-runtime/src/network/webserver/prom.rs @@ -0,0 +1,434 @@ +/// A module to parse prometheus metrics data into json +/// +/// Influenced by https://crates.io/crates/prom2jsonrs/0.1.0. +use anyhow::{anyhow, bail, Result}; +use dyn_clone::DynClone; +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +const HISTOGRAM_TYPE: &str = "HISTOGRAM"; +const SUMMARY_TYPE: &str = "SUMMARY"; + +static METRIC_REGEX_NO_LABEL: Lazy<&Regex> = Lazy::new(|| { + static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + RE.get_or_init(|| Regex::new(r"([a-zA-Z_:][a-zA-Z0-9_:]*)\s(-?[\d.]+(?:e-?\d+)?|NaN)").unwrap()) +}); + +static METRIC_REGEX_WITH_LABEL: Lazy<&Regex> = Lazy::new(|| { + static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + RE.get_or_init(|| { + Regex::new(r"[a-zA-Z_:][a-zA-Z0-9_:]*\{(.*)\}\s(-?[\d.]+(?:e-?\d+)?|NaN)").unwrap() + }) +}); + +static LABELS_REGEX: Lazy<&Regex> = Lazy::new(|| { + static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + RE.get_or_init(|| Regex::new("([a-zA-Z0-9_:]*)=\"([^\"]+)\"").unwrap()) +}); + +static MULTI_NEWLINE: Lazy<&Regex> = Lazy::new(|| { + static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + RE.get_or_init(|| Regex::new(r"\n\n").unwrap()) +}); + +type Labels = HashMap; +type Value = String; + +#[derive(Clone, Serialize)] +/// A parsed representation of the prometheus metrics data +pub(crate) struct PrometheusData { + metrics: Vec, +} + +impl PrometheusData { + /// Parse promethues metric data from string + pub(crate) fn from_string(s: &str) -> Result { + let text = MULTI_NEWLINE.replace_all(s, "\n"); + let mut metrics = Vec::new(); + let mut metric_lines = Vec::new(); + let mut num_comment_lines = 0; + for line in text.lines() { + if line.starts_with('#') { + if num_comment_lines == 2 { + // One set complete + metrics.push(MetricFamily::from_raw(&metric_lines)?); + metric_lines = vec![line]; + num_comment_lines = 1; + } else { + num_comment_lines += 1; + metric_lines.push(line); + } + } else { + metric_lines.push(line) + } + } + Ok(PrometheusData { metrics }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Metric { + labels: Option, + value: Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Summary { + labels: Option, + quantiles: Labels, + count: Value, + sum: Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Histogram { + labels: Option>, + buckets: Labels, + count: Value, + sum: Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +enum MetricType { + Gauge, + Histogram, + Summary, +} + +#[derive(Clone, Serialize)] +struct MetricFamily { + metric_type: MetricType, + metric_name: String, + help: String, + data: Vec>, +} + +#[typetag::serde(tag = "type")] +trait MetricLike: DynClone { + fn parse_from_string(s: &str) -> Result<(Value, Option)> + where + Self: Sized, + { + if let Some(caps) = METRIC_REGEX_NO_LABEL.captures(s) { + Ok((caps[2].to_string(), None)) + } else if let Some(caps) = METRIC_REGEX_WITH_LABEL.captures(s) { + let value = caps[2].to_string(); + let mut labels: HashMap = HashMap::new(); + for cap in LABELS_REGEX.captures_iter(&caps[1]) { + labels.insert(cap[1].to_string(), cap[2].to_string()); + } + Ok((value, Some(labels))) + } else { + Err(anyhow!("invalid format {}", s)) + } + } + + fn metric_type() -> String + where + Self: Sized; +} + +dyn_clone::clone_trait_object!(MetricLike); + +impl Metric { + fn from_string(s: &str) -> Result { + let (value, labels) = Self::parse_from_string(s)?; + Ok(Metric { labels, value }) + } +} + +#[typetag::serde(name = "metric")] +impl MetricLike for Metric { + fn metric_type() -> String { + String::from("DEFAULT") + } +} + +impl Summary { + fn from_raw(metric_name: &str, raw_lines: &Vec<&str>) -> Result { + let mut sum = String::from(""); + let mut count = String::from(""); + let sum_prefix = format!("{}_sum", metric_name); + let count_prefix = format!("{}_count", metric_name); + let mut labels = HashMap::new(); + let mut quantiles = HashMap::new(); + for raw_line in raw_lines { + if raw_line.starts_with(&sum_prefix) { + sum = Summary::parse_from_string(raw_line)?.0; + } else if raw_line.starts_with(&count_prefix) { + count = Summary::parse_from_string(raw_line)?.0; + } else if let Some(caps) = METRIC_REGEX_WITH_LABEL.captures(raw_line) { + for cap in LABELS_REGEX.captures_iter(&caps[1]) { + let key = &cap[1]; + let value = &cap[2]; + match key { + "quantile" => quantiles.insert(key.to_string(), value.to_string()), + _ => labels.insert(key.to_string(), value.to_string()), + }; + } + } else { + bail!("invalid format {}", raw_line); + } + } + + Ok(Summary { + sum, + count, + labels: Some(labels), + quantiles, + }) + } +} + +#[typetag::serde] +impl MetricLike for Summary { + fn metric_type() -> String { + String::from(SUMMARY_TYPE) + } +} + +impl Histogram { + fn from_raw(metric_name: &str, raw_lines: &Vec<&str>) -> Result { + let mut sum = String::from(""); + let mut count = String::from(""); + let sum_prefix = format!("{}_sum", metric_name); + let count_prefix = format!("{}_count", metric_name); + let mut labels: HashMap = HashMap::new(); + let mut buckets: HashMap = HashMap::new(); + for raw_line in raw_lines { + if raw_line.starts_with(&sum_prefix) { + sum = Summary::parse_from_string(raw_line)?.0; + } else if raw_line.starts_with(&count_prefix) { + count = Summary::parse_from_string(raw_line)?.0; + } else if let Some(caps) = METRIC_REGEX_WITH_LABEL.captures(raw_line) { + for cap in LABELS_REGEX.captures_iter(&caps[1]) { + let key = &cap[1]; + let value = &cap[2]; + match key { + "le" => buckets.insert(value.to_string(), caps[2].to_string()), + _ => labels.insert(key.to_string(), value.to_string()), + }; + } + } else { + bail!("invalid format {}", raw_line) + } + } + + Ok(Histogram { + sum, + count, + labels: Some(labels), + buckets, + }) + } +} + +#[typetag::serde] +impl MetricLike for Histogram { + fn metric_type() -> String { + String::from(HISTOGRAM_TYPE) + } +} + +impl MetricFamily { + fn from_raw(raw: &[&str]) -> Result { + let mut raw_iter = raw.iter(); + let help = MetricFamily::metric_help_fron_raw( + raw_iter + .next() + .ok_or(anyhow!("invalid metric help{}", raw.join("\n")))?, + ); + let (metric_name, metric_type) = MetricFamily::metric_name_and_type( + raw_iter + .next() + .ok_or(anyhow!("invalid metric name/type {}", raw.join("\n")))?, + )?; + let mut data: Vec> = Vec::new(); + match metric_type { + MetricType::Gauge => { + for raw_line in raw_iter { + data.push(Box::new(Metric::from_string(raw_line)?)) + } + } + MetricType::Histogram => { + let count_prefix = format!("{}_count", metric_name); + let mut histogram_lines: Vec<&str> = Vec::new(); + for raw_line in raw_iter { + histogram_lines.push(raw_line); + if raw_line.starts_with(&count_prefix) { + data.push(Box::new(Histogram::from_raw( + &metric_name, + &histogram_lines, + )?)); + histogram_lines = Vec::new(); + } + } + } + MetricType::Summary => { + let count_prefix = format!("{}_count", metric_name); + let mut summary_lines: Vec<&str> = Vec::new(); + for raw_line in raw_iter { + summary_lines.push(raw_line); + if raw_line.starts_with(&count_prefix) { + data.push(Box::new(Summary::from_raw(&metric_name, &summary_lines)?)); + summary_lines = Vec::new(); + } + } + } + } + Ok(MetricFamily { + metric_type, + metric_name, + help, + data, + }) + } + + fn metric_name_and_type(type_line: &str) -> Result<(String, MetricType)> { + let tags: Vec<&str> = type_line.split_whitespace().collect(); + let (name, type_raw) = (tags[2], tags[3]); + let metric_type = match type_raw { + "gauge" => MetricType::Gauge, + "counter" => MetricType::Gauge, + "histogram" => MetricType::Histogram, + "summary" => MetricType::Summary, + _ => bail!("invalid metric type {}", type_raw), + }; + + Ok((name.to_string(), metric_type)) + } + + fn metric_help_fron_raw(help_line: &str) -> String { + let tags: Vec<&str> = help_line.split_whitespace().collect(); + tags[3..].join(" ").to_string() + } +} + +#[cfg(test)] +mod test { + use super::*; + use maplit::hashmap; + + #[test] + fn parse_metric() { + assert_eq!( + Metric { + labels: None, + value: String::from("205632") + }, + Metric::from_string("go_memstats_mspan_inuse_bytes 205632").unwrap() + ); + assert_eq!( + Metric { + labels: Some(hashmap!{ + "dialer_name".to_string() => "default".to_string(), + "reason".to_string() => "unknown".to_string(), + }), + value: String::from("0") + }, + Metric::from_string("net_conntrack_dialer_conn_failed_total{dialer_name=\"default\",reason=\"unknown\"} 0").unwrap() + ) + } + + #[test] + fn parse_metric_raw_data() { + let raw_data = "# HELP go_goroutines Number of goroutines that currently exist. +# TYPE go_goroutines gauge +go_goroutines 31 +# HELP go_info Information about the Go environment. +# TYPE go_info gauge +go_info{version=\"go1.15.5\"} 1"; + let prom_data = PrometheusData::from_string(raw_data).unwrap(); + assert_eq!(MetricType::Gauge, prom_data.metrics[0].metric_type) + } + + #[test] + fn parse_metric_summary() { + let raw_data = + "prometheus_engine_query_duration_seconds{slice=\"inner_eval\",quantile=\"0.5\"} NaN +prometheus_engine_query_duration_seconds{slice=\"inner_eval\",quantile=\"0.9\"} NaN +prometheus_engine_query_duration_seconds{slice=\"inner_eval\",quantile=\"0.99\"} NaN +prometheus_engine_query_duration_seconds_sum{slice=\"inner_eval\"} 12 +prometheus_engine_query_duration_seconds_count{slice=\"inner_eval\"} 0"; + let summary = Summary::from_raw( + "prometheus_engine_query_duration_seconds", + &raw_data.lines().collect(), + ) + .unwrap(); + assert_eq!(summary.sum, "12".to_string()); + assert_eq!( + summary.labels, + Some(hashmap! {"slice".to_string() => "inner_eval".to_string()}) + ); + } + + #[test] + fn parse_metric_histogram() { + let raw_data = r#"prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="0.1"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="0.2"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="0.4"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="1"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="3"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="8"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="20"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="60"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="120"} 10871 +prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="+Inf"} 10871 +prometheus_http_request_duration_seconds_sum{handler="/metrics"} 67.48398663499978 +prometheus_http_request_duration_seconds_count{handler="/metrics"} 10871"#; + let histogram = Histogram::from_raw( + "prometheus_http_request_duration_seconds", + &raw_data.lines().collect(), + ) + .unwrap(); + assert_eq!(histogram.sum, "67.48398663499978"); + assert_eq!( + histogram.labels, + Some(hashmap! {"handler".to_string() => "/metrics".to_string()}) + ); + } + + #[test] + fn parse_metric_collection_to_json() { + let raw_data = r#"# HELP homestar_process_disk_total_read_bytes Total bytes read from disk. +# TYPE homestar_process_disk_total_read_bytes gauge +homestar_process_disk_total_read_bytes 45969408 + +# HELP homestar_process_virtual_memory_bytes The virtual memory size in bytes. +# TYPE homestar_process_virtual_memory_bytes gauge +homestar_process_virtual_memory_bytes 418935930880 + +# HELP homestar_network_received_bytes The bytes received since last refresh. +# TYPE homestar_network_received_bytes gauge +homestar_network_received_bytes 0 + +# HELP homestar_system_available_memory_bytes The amount of available memory. +# TYPE homestar_system_available_memory_bytes gauge +homestar_system_available_memory_bytes 0 + +# HELP homestar_system_disk_available_space_bytes The total amount of available disk space. +# TYPE homestar_system_disk_available_space_bytes gauge +homestar_system_disk_available_space_bytes 0 + +# HELP homestar_system_load_average_percentage The load average over a five minute interval. +# TYPE homestar_system_load_average_percentage gauge +homestar_system_load_average_percentage 6.26611328125"#; + + let prom_data = PrometheusData::from_string(raw_data).unwrap(); + let json_string = serde_json::to_string(&prom_data).unwrap(); + let root: serde_json::Value = serde_json::from_str(&json_string).unwrap(); + + let check = root + .get("metrics") + .and_then(|v| v.get(0)) + .and_then(|v| v.get("data")) + .and_then(|v| v.get(0)) + .and_then(|v| v.get("value")) + .unwrap(); + + assert_eq!(check, &serde_json::Value::String("45969408".to_string())); + } +} diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs new file mode 100644 index 00000000..da449917 --- /dev/null +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -0,0 +1,235 @@ +use super::{listener, prom::PrometheusData}; +#[cfg(feature = "websocket-notify")] +use super::{Message, Notifier}; +use crate::runner::WsSender; +#[cfg(feature = "websocket-notify")] +use anyhow::anyhow; +use anyhow::Result; +#[cfg(feature = "websocket-notify")] +use futures::StreamExt; +use jsonrpsee::{ + server::RpcModule, + types::{error::ErrorCode, ErrorObjectOwned}, +}; +#[cfg(feature = "websocket-notify")] +use jsonrpsee::{SubscriptionMessage, SubscriptionSink, TrySendError}; +use metrics_exporter_prometheus::PrometheusHandle; +use std::time::Duration; +#[cfg(feature = "websocket-notify")] +use tokio::{ + runtime::Handle, + select, + sync::oneshot, + time::{self, Instant}, +}; +#[cfg(feature = "websocket-notify")] +use tokio_stream::wrappers::BroadcastStream; +#[cfg(feature = "websocket-notify")] +use tracing::{error, info, warn}; + +/// Health endpoint. +pub(crate) const HEALTH_ENDPOINT: &str = "health"; +/// Metrics endpoint for prometheus / openmetrics polling. +pub(crate) const METRICS_ENDPOINT: &str = "metrics"; + +/// Run a workflow and subscribe to that workflow's events. +#[cfg(feature = "websocket-notify")] +pub(crate) const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; +/// Unsubscribe from a workflow's events. +#[cfg(feature = "websocket-notify")] +pub(crate) const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; +/// Subscribe to network events. +#[cfg(feature = "websocket-notify")] +pub(crate) const SUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "subscribe_network_events"; +/// Unsubscribe from network events. +#[cfg(feature = "websocket-notify")] +pub(crate) const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_network_events"; + +/// TODO +#[cfg(feature = "websocket-notify")] +pub(crate) struct Context { + metrics_hdl: PrometheusHandle, + notifier: Notifier, + runner_sender: WsSender, + receiver_timeout: Duration, +} + +/// TODO +#[allow(dead_code)] +#[cfg(not(feature = "websocket-notify"))] +pub(crate) struct Context { + metrics_hdl: PrometheusHandle, + runner_sender: WsSender, + receiver_timeout: Duration, +} + +impl Context { + /// TODO + #[cfg(feature = "websocket-notify")] + #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] + pub(crate) fn new( + metrics_hdl: PrometheusHandle, + notifier: Notifier, + runner_sender: WsSender, + receiver_timeout: Duration, + ) -> Self { + Self { + metrics_hdl, + notifier, + runner_sender, + receiver_timeout, + } + } + + /// TODO + #[cfg(not(feature = "websocket-notify"))] + pub(crate) fn new( + metrics_hdl: PrometheusHandle, + runner_sender: WsSender, + receiver_timeout: Duration, + ) -> Self { + Self { + metrics_hdl, + runner_sender, + receiver_timeout, + } + } +} + +/// TODO +pub(crate) struct JsonRpc(RpcModule); + +impl JsonRpc { + /// Create a new [JsonRpc] instance, registering methods on initialization. + pub(crate) async fn new(ctx: Context) -> Result { + let module = Self::register(ctx).await?; + Ok(Self(module)) + } + + #[allow(dead_code)] + /// Get a reference to the inner [RpcModule]. + pub(crate) fn inner(&self) -> &RpcModule { + &self.0 + } + + /// Get and take ownership of the inner [RpcModule]. + pub(crate) fn into_inner(self) -> RpcModule { + self.0 + } + + async fn register(ctx: Context) -> Result> { + let mut module = RpcModule::new(ctx); + + module.register_async_method(HEALTH_ENDPOINT, |_, _| async move { + serde_json::json!({ "healthy": true }) + })?; + + module.register_async_method(METRICS_ENDPOINT, |params, ctx| async move { + let render = ctx.metrics_hdl.render(); + + // TODO: Handle prefix specific metrics in parser. + match params.one::() { + Ok(listener::MetricsPrefix { prefix: _prefix }) => { + PrometheusData::from_string(&render) + .map_err(|_err| ErrorObjectOwned::from(ErrorCode::InternalError)) + } + Err(_) => PrometheusData::from_string(&render) + .map_err(|_err| ErrorObjectOwned::from(ErrorCode::InternalError)), + } + })?; + + #[cfg(feature = "websocket-notify")] + module.register_subscription( + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + |_, pending, ctx| async move { + let sink = pending.accept().await?; + let rx = ctx.notifier.inner().subscribe(); + let stream = BroadcastStream::new(rx); + Self::handle_event_subscription(sink, stream).await?; + Ok(()) + }, + )?; + + #[cfg(feature = "websocket-notify")] + module.register_subscription( + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + |params, pending, ctx| async move { + match params.one::>() { + Ok(listener::Run { name, workflow }) => { + let (tx, rx) = oneshot::channel(); + ctx.runner_sender + .send((Message::RunWorkflow((name, workflow)), Some(tx))) + .await?; + + if (time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await) + .is_err() + { + error!("did not acknowledge message in time"); + let _ = pending + .reject(ErrorObjectOwned::from(ErrorObjectOwned::from( + ErrorCode::InternalError, + ))) + .await; + return Ok(()); + } + } + Err(err) => { + warn!("failed to parse run workflow params: {}", err); + let _ = pending.reject(err).await; + return Ok(()); + } + } + let sink = pending.accept().await?; + let rx = ctx.notifier.inner().subscribe(); + let stream = BroadcastStream::new(rx); + Self::handle_event_subscription(sink, stream).await?; + Ok(()) + }, + )?; + + Ok(module) + } + + #[cfg(feature = "websocket-notify")] + async fn handle_event_subscription( + mut sink: SubscriptionSink, + mut stream: BroadcastStream>, + ) -> Result<()> { + let rt_hdl = Handle::current(); + rt_hdl.spawn(async move { + loop { + select! { + _ = sink.closed() => { + break Ok(()); + } + next_msg = stream.next() => { + let msg = match next_msg { + Some(Ok(msg)) => msg, + Some(Err(err)) => { + error!("subscription stream error: {}", err); + break Err(err.into()); + } + None => break Ok(()), + }; + let sub_msg = SubscriptionMessage::from_json(&msg)?; + match sink.try_send(sub_msg) { + Ok(()) => (), + Err(TrySendError::Closed(_)) => { + break Err(anyhow!("subscription sink closed")); + } + Err(TrySendError::Full(_)) => { + info!("subscription sink full"); + } + } + } + } + } + }); + + Ok(()) + } +} diff --git a/homestar-runtime/src/network/ws.rs b/homestar-runtime/src/network/ws.rs deleted file mode 100644 index 0493cb10..00000000 --- a/homestar-runtime/src/network/ws.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! Sets up a websocket server for sending and receiving messages from browser -//! clients. - -use crate::{ - channel::AsyncBoundedChannelReceiver, - runner::{self, WsSender}, - settings, -}; -use anyhow::{anyhow, Result}; -use axum::{ - extract::{ - ws::{self, Message as AxumMsg, WebSocketUpgrade}, - ConnectInfo, State, TypedHeader, - }, - response::IntoResponse, - routing::get, - Router, -}; -use faststr::FastStr; -use futures::{stream::StreamExt, SinkExt}; -use homestar_core::Workflow; -use homestar_wasm::io::Arg; -use std::{ - net::{IpAddr, SocketAddr, TcpListener}, - ops::ControlFlow, - str::FromStr, - time::Duration, -}; -use tokio::{ - runtime::Handle, - select, - sync::{broadcast, oneshot}, - time::{self, Instant}, -}; -use tracing::{debug, error, info, warn}; - -#[cfg(feature = "websocket-notify")] -pub(crate) mod listener; -#[cfg(feature = "websocket-notify")] -pub(crate) mod notifier; -#[cfg(feature = "websocket-notify")] -pub(crate) use notifier::Notifier; - -/// Message type for messages sent back from the -/// websocket server to the [runner] for example. -/// -/// [runner]: crate::Runner -#[allow(dead_code)] -#[derive(Debug)] -pub(crate) enum Message { - /// Notify the listener that the websocket server is shutting down - /// gracefully. - GracefulShutdown(oneshot::Sender<()>), - /// Error attempting to run a [Workflow]. - RunErr(runner::Error), - /// Run a workflow, given a tuple of name, and [Workflow]. - RunWorkflow((FastStr, Workflow<'static, Arg>)), - /// Acknowledgement of a [Workflow] run. - /// - /// TODO: Temporary Ack until we define semantics for JSON-RPC or similar. - RunWorkflowAck, -} - -/// WebSocket server fields. -#[allow(dead_code)] -#[derive(Clone, Debug)] -pub(crate) struct Server { - /// Address of the websocket server. - addr: SocketAddr, - /// Message sender for broadcasting to clients connected to the - /// websocket server. - notifier: Notifier, - /// Receiver timeout for the websocket server. - receiver_timeout: Duration, -} - -/// State used for the websocket server routes. -#[derive(Clone, Debug)] -struct ServerState { - notifier: Notifier, - runner_sender: WsSender, - receiver_timeout: Duration, -} - -impl Server { - /// Setup bounded, MPMC channel for runtime to send and received messages - /// through the websocket connection(s). - fn setup_channel( - capacity: usize, - ) -> (broadcast::Sender>, broadcast::Receiver>) { - broadcast::channel(capacity) - } - - pub(crate) fn new(settings: &settings::Network) -> Result { - let (sender, _receiver) = Self::setup_channel(settings.websocket_capacity); - - let host = IpAddr::from_str(&settings.websocket_host.to_string())?; - let port_setting = settings.websocket_port; - let addr = if port_available(host, port_setting) { - SocketAddr::from((host, port_setting)) - } else { - let port = (port_setting..port_setting + 1000) - .find(|port| port_available(host, *port)) - .ok_or_else(|| anyhow!("no free TCP ports available"))?; - SocketAddr::from((host, port)) - }; - - Ok(Self { - addr, - notifier: Notifier::new(sender), - receiver_timeout: settings.websocket_receiver_timeout, - }) - } - - /// Start the websocket server given settings. - pub(crate) async fn start( - &self, - rx: AsyncBoundedChannelReceiver, - runner_sender: WsSender, - ) -> Result<()> { - let addr = self.addr; - info!("websocket server listening on {}", addr); - let app = Router::new().route( - "/", - get(ws_handler).with_state(ServerState { - notifier: self.notifier.clone(), - runner_sender, - receiver_timeout: self.receiver_timeout, - }), - ); - - axum::Server::bind(&addr) - .serve(app.into_make_service_with_connect_info::()) - .with_graceful_shutdown(async { - if let Ok(Message::GracefulShutdown(tx)) = rx.recv_async().await { - info!("websocket server shutting down"); - let _ = tx.send(()); - } - }) - .await?; - - Ok(()) - } - - /// Get websocket message sender for broadcasting messages to websocket - /// clients. - pub(crate) fn notifier(&self) -> Notifier { - self.notifier.clone() - } -} - -async fn ws_handler( - ws: WebSocketUpgrade, - user_agent: Option>, - State(state): State, - ConnectInfo(addr): ConnectInfo, -) -> impl IntoResponse { - let user_agent = if let Some(TypedHeader(user_agent)) = user_agent { - user_agent.to_string() - } else { - String::from("Unknown browser") - }; - info!("`{user_agent}` at {addr} connected."); - - // Finalize the upgrade process by returning upgrade callback. - // We can customize the callback by sending additional info such as address. - ws.on_upgrade(move |socket| handle_socket(socket, addr, state)) -} - -async fn handle_socket(mut socket: ws::WebSocket, addr: SocketAddr, state: ServerState) { - // Send a ping (unsupported by some browsers) just to kick things off and - // get a response. - if socket.send(AxumMsg::Ping(vec![1, 2, 3])).await.is_ok() { - debug!("Pinged {}...", addr); - } else { - info!("Could not send ping {}!", addr); - // no Error here since the only thing we can do is to close the connection. - // If we can not send messages, there is no way to salvage the statemachine anyway. - return; - } - - // Receive single message from a client (we can either receive or send with - // the socket). This will likely be the Pong for our Ping or a processed - // message from client. - // Waiting for message from a client will block this task, but will not - // block other client's connections. - if let Some(msg) = socket.recv().await { - if let Ok(msg) = msg { - if process_message(msg, addr, &state).await.is_break() { - return; - } - } else { - info!("client {} abruptly disconnected", addr); - return; - } - } - - // By splitting socket we can send and receive at the same time. - let (mut socket_sender, mut socket_receiver) = socket.split(); - let mut subscribed_rx = state.notifier.inner().subscribe(); - let handle = Handle::current(); - - let mut send_task = handle.spawn(async move { - while let Ok(msg) = subscribed_rx.recv().await { - // In any websocket error, break loop. - if socket_sender.send(AxumMsg::Binary(msg)).await.is_err() { - break; - } - } - }); - - let mut recv_task = handle.spawn(async move { - let mut cnt = 0; - while let Some(Ok(msg)) = socket_receiver.next().await { - cnt += 1; - if process_message(msg, addr, &state).await.is_break() { - break; - } - } - cnt - }); - - // If any one of the tasks exit, abort the other. - select! { - _ = (&mut send_task) => recv_task.abort(), - _ = (&mut recv_task) => send_task.abort(), - }; - - info!("Websocket context {} destroyed", addr); -} - -/// Process [messages]. -/// -/// [messages]: Message -async fn process_message( - msg: AxumMsg, - addr: SocketAddr, - state: &ServerState, -) -> ControlFlow<(), ()> { - match msg { - AxumMsg::Text(t) => { - debug!(">>> {} sent str: {:?}", addr, t); - } - AxumMsg::Binary(bytes) => { - debug!(">>> {} sent {}", addr, bytes.len()); - match serde_json::from_slice::>(&bytes) { - Ok(listener::Run { - action, - name, - workflow, - }) if action.eq("run") => { - let (tx, rx) = oneshot::channel(); - if let Err(err) = state - .runner_sender - .send((Message::RunWorkflow((name, workflow)), Some(tx))) - .await - { - error!(err=?err, "error sending message to runner"); - } - - if (time::timeout_at(Instant::now() + state.receiver_timeout, rx).await) - .is_err() - { - error!("did not acknowledge action=run message in time"); - } - } - Ok(_) => warn!("unknown action or message shape"), - // another message - Err(_err) => debug!( - "{}", - std::str::from_utf8(&bytes).unwrap_or(format!("{:?}", bytes).as_ref()) - ), - } - } - AxumMsg::Close(c) => { - if let Some(cf) = c { - info!( - ">>> {} sent close with code {} and reason `{}`", - addr, cf.code, cf.reason - ); - } else { - info!(">>> {} sent close message without CloseFrame", addr); - } - return ControlFlow::Break(()); - } - - AxumMsg::Pong(v) => { - debug!(">>> {} sent pong with {:?}", addr, v); - } - // You should never need to manually handle AxumMsg::Ping, as axum's websocket library - // will do so for you automagically by replying with Pong and copying the v according to - // spec. But if you need the contents of the pings you can see them here. - AxumMsg::Ping(v) => { - debug!(">>> {} sent ping with {:?}", addr, v); - } - } - ControlFlow::Continue(()) -} - -fn port_available(host: IpAddr, port: u16) -> bool { - TcpListener::bind((host.to_string(), port)).is_ok() -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{channel, settings::Settings}; - use tokio::sync::mpsc; - - #[tokio::test] - async fn ws_connect() { - let settings = Settings::load().unwrap(); - let server = Server::new(settings.node().network()).unwrap(); - let (_ws_tx, ws_rx) = channel::AsyncBoundedChannel::oneshot(); - let (runner_tx, _runner_rx) = mpsc::channel(1); - let _ws_hdl = tokio::spawn({ - let ws_server = server.clone(); - async move { ws_server.start(ws_rx, runner_tx).await } - }); - - tokio_tungstenite::connect_async("ws://localhost:1337".to_string()) - .await - .unwrap(); - } -} diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 0d721a48..4708aece 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -1,16 +1,14 @@ //! General [Runner] interface for working across multiple workers //! and executing workflows. -#[cfg(feature = "websocket-server")] -use crate::network::ws; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelReceiver, AsyncBoundedChannelSender}, + channel::AsyncBoundedChannelSender, db::Database, event_handler::{Event, EventHandler}, metrics, - network::{rpc, swarm}, + network::{rpc, swarm, webserver}, worker::WorkerMessage, workflow, Settings, Worker, }; @@ -22,7 +20,9 @@ use faststr::FastStr; use futures::future::poll_fn; use homestar_core::Workflow; use homestar_wasm::io::Arg; +use jsonrpsee::server::ServerHandle; use libipld::Cid; +use metrics_exporter_prometheus::PrometheusHandle; #[cfg(not(test))] use std::sync::atomic::{AtomicUsize, Ordering}; use std::{ops::ControlFlow, rc::Rc, sync::Arc, task::Poll}; @@ -72,12 +72,16 @@ pub(crate) type RpcReceiver = mpsc::Receiver<( )>; /// [mpsc::Sender] for sending messages websocket server clients. -#[cfg(feature = "websocket-server")] -pub(crate) type WsSender = mpsc::Sender<(ws::Message, Option>)>; +pub(crate) type WsSender = mpsc::Sender<( + webserver::Message, + Option>, +)>; /// [mpsc::Receiver] for receiving messages from websocket server clients. -#[cfg(feature = "websocket-server")] -pub(crate) type WsReceiver = mpsc::Receiver<(ws::Message, Option>)>; +pub(crate) type WsReceiver = mpsc::Receiver<( + webserver::Message, + Option>, +)>; impl ModifiedSet for RunningTaskSet { fn append_or_insert(&self, cid: Cid, mut handles: Vec) { @@ -93,26 +97,6 @@ impl ModifiedSet for RunningTaskSet { /// Used to manage workers and execute/run [Workflows]. /// /// [Workflows]: homestar_core::Workflow -#[cfg(feature = "websocket-server")] -#[cfg_attr(docsrs, doc(cfg(feature = "websocket-server")))] -#[allow(dead_code)] -#[derive(Debug)] -pub struct Runner { - event_sender: Arc>, - expiration_queue: Rc>>, - running_tasks: Arc, - running_workers: RunningWorkerSet, - runtime: tokio::runtime::Runtime, - settings: Arc, - ws_server: Arc, -} - -/// Runner interface. -/// Used to manage workers and execute/run [Workflows]. -/// -/// [Workflows]: homestar_core::Workflow -#[cfg(not(feature = "websocket-server"))] -#[allow(dead_code)] #[derive(Debug)] pub struct Runner { event_sender: Arc>, @@ -121,6 +105,7 @@ pub struct Runner { running_workers: RunningWorkerSet, runtime: tokio::runtime::Runtime, settings: Arc, + webserver: Arc, } impl Runner { @@ -136,20 +121,8 @@ impl Runner { mpsc::channel(capacity) } - /// Oneshot channel for sending direct messages to the websocket server, - /// e.g. for shutdown. - #[cfg(feature = "websocket-server")] - pub(crate) fn setup_ws_oneshot_channel() -> ( - AsyncBoundedChannelSender, - AsyncBoundedChannelReceiver, - ) { - let (tx, rx) = AsyncBoundedChannel::oneshot(); - (tx, rx) - } - /// MPSC channel for sending and receiving messages through to/from /// websocket server clients. - #[cfg(feature = "websocket-server")] pub(crate) fn setup_ws_mpsc_channel(capacity: usize) -> (WsSender, WsReceiver) { mpsc::channel(capacity) } @@ -187,57 +160,35 @@ impl Runner { ) -> Result { let swarm = runtime.block_on(swarm::new(settings.node()))?; - #[cfg(feature = "websocket-server")] - { - let ws_server = ws::Server::new(settings.node().network())?; - let ws_msg_tx = ws_server.notifier(); - - let event_handler = EventHandler::new(swarm, db, settings.node(), ws_msg_tx); - let event_sender = event_handler.sender(); - - #[cfg(feature = "ipfs")] - let _event_handler_hdl = runtime.spawn({ - let ipfs = IpfsCli::default(); - event_handler.start(ipfs) - }); - - #[cfg(not(feature = "ipfs"))] - let _event_handler_hdl = runtime.spawn(event_handler.start()); - - Ok(Self { - event_sender, - expiration_queue: Rc::new(AtomicRefCell::new(DelayQueue::new())), - running_tasks: DashMap::new().into(), - running_workers: DashMap::new(), - runtime, - settings: settings.into(), - ws_server: ws_server.into(), - }) - } + let webserver = webserver::Server::new(settings.node().network())?; + #[cfg(feature = "websocket-notify")] + let ws_msg_tx = webserver.notifier(); - #[cfg(not(feature = "websocket-server"))] - { - let event_handler = EventHandler::new(swarm, db, settings.node()); - let event_sender = event_handler.sender(); - - #[cfg(feature = "ipfs")] - let _event_handler_hdl = runtime.spawn({ - let ipfs = IpfsCli::default(); - event_handler.start(ipfs) - }); - - #[cfg(not(feature = "ipfs"))] - let _event_handler_hdl = runtime.spawn(event_handler.start()); - - Ok(Self { - event_sender, - expiration_queue: Rc::new(AtomicRefCell::new(DelayQueue::new())), - running_tasks: DashMap::new().into(), - running_workers: DashMap::new(), - runtime, - settings: settings.into(), - }) - } + #[cfg(feature = "websocket-notify")] + let event_handler = EventHandler::new(swarm, db, settings.node(), ws_msg_tx); + #[cfg(not(feature = "websocket-notify"))] + let event_handler = EventHandler::new(swarm, db, settings.node()); + + let event_sender = event_handler.sender(); + + #[cfg(feature = "ipfs")] + let _event_handler_hdl = runtime.spawn({ + let ipfs = IpfsCli::default(); + event_handler.start(ipfs) + }); + + #[cfg(not(feature = "ipfs"))] + let _event_handler_hdl = runtime.spawn(event_handler.start()); + + Ok(Self { + event_sender, + expiration_queue: Rc::new(AtomicRefCell::new(DelayQueue::new())), + running_tasks: DashMap::new().into(), + running_workers: DashMap::new(), + runtime, + settings: settings.into(), + webserver: webserver.into(), + }) } /// Listen loop for [Runner] signals and messages. @@ -245,25 +196,29 @@ impl Runner { fn serve(self, db: impl Database + 'static) -> Result<()> { let message_buffer_len = self.settings.node.network.events_buffer_len; - #[cfg(feature = "websocket-server")] - let (ws_sender, mut ws_receiver) = { - let (oneshot_ws_tx, oneshot_ws_rx) = Self::setup_ws_oneshot_channel(); + #[cfg(feature = "monitoring")] + let metrics_hdl: PrometheusHandle = self.runtime.block_on(metrics::start( + self.settings.monitoring(), + self.settings.node.network(), + ))?; + + #[cfg(not(feature = "monitoring"))] + let metrics_hdl: PrometheusHandle = self + .runtime + .block_on(metrics::start(self.settings.node.network()))?; + + let (mut ws_receiver, ws_hdl) = { let (mpsc_ws_tx, mpsc_ws_rx) = Self::setup_ws_mpsc_channel(message_buffer_len); - let _ws_hdl = self.runtime.spawn({ - let ws_server = self.ws_server.clone(); - async move { ws_server.start(oneshot_ws_rx, mpsc_ws_tx).await } - }); - (oneshot_ws_tx, mpsc_ws_rx) + let ws_hdl = self + .runtime + .block_on(self.webserver.start(mpsc_ws_tx, metrics_hdl))?; + (mpsc_ws_rx, ws_hdl) }; let (rpc_tx, mut rpc_rx) = Self::setup_rpc_channel(message_buffer_len); let (runner_worker_tx, mut runner_worker_rx) = Self::setup_worker_channel(message_buffer_len); - #[cfg(feature = "metrics")] - self.runtime - .block_on(metrics::start(&self.settings.monitoring))?; - let shutdown_timeout = self.settings.node.shutdown_timeout; let rpc_server = rpc::Server::new(self.settings.node.network(), rpc_tx.into()); let rpc_sender = rpc_server.sender(); @@ -272,39 +227,22 @@ impl Runner { let shutdown_time_left = self.runtime.block_on(async { let mut gc_interval = tokio::time::interval(self.settings.node.gc_interval); loop { - // Sadness to get around https://github.com/tokio-rs/tokio/issues/3974. - #[cfg(feature = "websocket-server")] - let ws_receiver_wait = ws_receiver.recv(); - #[cfg(not(feature = "websocket-server"))] - let ws_receiver_wait: future::Pending> = std::future::pending(); - select! { biased; // Handle RPC messages. Some((rpc_message, Some(oneshot_tx))) = rpc_rx.recv() => { let now = time::Instant::now(); - #[cfg(feature = "websocket-server")] let handle = self.handle_command_message( rpc_message, Channels { rpc: rpc_sender.clone(), runner: runner_worker_tx.clone(), - ws: ws_sender.clone(), }, + ws_hdl.clone(), db.clone(), now ).await; - #[cfg(not(feature = "websocket-server"))] - let handle = self.handle_command_message( - rpc_message, - Channels { - rpc: rpc_sender.clone(), - runner: runner_worker_tx.clone(), - }, - db.clone(), - now - ).await; match handle { Ok(ControlFlow::Break(())) => break now.elapsed(), @@ -320,9 +258,9 @@ impl Runner { _ => {} } } - Some((ws::Message::RunWorkflow((name, workflow)), Some(oneshot_tx))) = ws_receiver_wait => { - // TODO: Parse this from the workflow data itself. + Some((webserver::Message::RunWorkflow((name, workflow)), Some(oneshot_tx))) = ws_receiver.recv() => { info!("running workflow: {}", name); + // TODO: Parse this from the workflow data itself. let workflow_settings = workflow::Settings::default(); match self.run_worker( workflow, @@ -333,11 +271,11 @@ impl Runner { ).await { Ok(_) => { info!("sending message to rpc server"); - let _ = oneshot_tx.send(ws::Message::RunWorkflowAck); + let _ = oneshot_tx.send(webserver::Message::AckWorkflow); } Err(err) => { error!(err=?err, "error handling ws message"); - let _ = oneshot_tx.send(ws::Message::RunErr(err.into())); + let _ = oneshot_tx.send(webserver::Message::RunErr(err.into())); } } } @@ -369,33 +307,18 @@ impl Runner { let now = time::Instant::now(); let drain_timeout = now + shutdown_timeout; - // Sub-select handling of runner `shutdown`. - #[cfg(feature = "websocket-server")] { - select! { - // Graceful shutdown. - Ok(()) = self.shutdown(rpc_sender, ws_sender) => { - break now.elapsed(); - }, - // Force shutdown upon drain timeout. - _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); - break now.elapsed(); - } - } - } - #[cfg(not(feature = "websocket-server"))] { - select! { - // Graceful shutdown. - Ok(()) = self.shutdown(rpc_sender) => { - break now.elapsed(); - }, - // Force shutdown upon drain timeout. - _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); - break now.elapsed(); - } + select! { + // Graceful shutdown. + Ok(()) = self.shutdown(rpc_sender, ws_hdl) => { + break now.elapsed(); + }, + // Force shutdown upon drain timeout. + _ = time::sleep_until(drain_timeout) => { + info!("shutdown timeout reached, shutting down runner anyway"); + break now.elapsed(); } } + } } } @@ -559,47 +482,18 @@ impl Runner { /// a) RPC and runner-related channels. /// b) Event-handler channels. /// c) Running workers - #[cfg(feature = "websocket-server")] async fn shutdown( &self, rpc_sender: Arc>, - ws_sender: AsyncBoundedChannelSender, + ws_hdl: ServerHandle, ) -> Result<()> { let (shutdown_sender, shutdown_receiver) = oneshot::channel(); let _ = rpc_sender.try_send(rpc::ServerMessage::GracefulShutdown(shutdown_sender)); let _ = shutdown_receiver.await; - let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let _ = self - .event_sender - .send_async(Event::Shutdown(shutdown_sender)) - .await; - let _ = shutdown_receiver.await; - - let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let _ = ws_sender - .send_async(ws::Message::GracefulShutdown(shutdown_sender)) - .await; - let _ = shutdown_receiver.await; - - // abort all workers - self.abort_workers(); - - Ok(()) - } - - /// Sequence for shutting down a [Runner], including: - /// a) RPC and runner-related channels. - /// b) Event-handler channels. - /// c) Running workers - #[cfg(not(feature = "websocket-server"))] - async fn shutdown( - &self, - rpc_sender: Arc>, - ) -> Result<()> { - let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let _ = rpc_sender.try_send(rpc::ServerMessage::GracefulShutdown(shutdown_sender)); - let _ = shutdown_receiver.await; + info!("shutting down webserver"); + let _ = ws_hdl.stop(); + ws_hdl.clone().stopped().await; let (shutdown_sender, shutdown_receiver) = oneshot::channel(); let _ = self @@ -618,6 +512,7 @@ impl Runner { &self, msg: rpc::ServerMessage, channels: Channels, + ws_hdl: ServerHandle, db: impl Database + 'static, now: time::Instant, ) -> Result> { @@ -626,30 +521,15 @@ impl Runner { rpc::ServerMessage::ShutdownCmd => { info!("RPC shutdown signal received, shutting down runner"); let drain_timeout = now + self.settings.node.shutdown_timeout; - #[cfg(feature = "websocket-server")] - { - select! { - // we can unwrap here b/c we know we have a sender based - // on the feature flag. - Ok(()) = self.shutdown(channels.rpc, channels.ws) => { - Ok(ControlFlow::Break(())) - }, - _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); - Ok(ControlFlow::Break(())) - } - } - } - #[cfg(not(feature = "websocket-server"))] - { - select! { - Ok(()) = self.shutdown(rpc_sender) => { - Ok(ControlFlow::Break(())) - }, - _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); - Ok(ControlFlow::Break(())) - } + select! { + // we can unwrap here b/c we know we have a sender based + // on the feature flag. + Ok(()) = self.shutdown(channels.rpc, ws_hdl) => { + Ok(ControlFlow::Break(())) + }, + _ = time::sleep_until(drain_timeout) => { + info!("shutdown timeout reached, shutting down runner anyway"); + Ok(ControlFlow::Break(())) } } } @@ -744,20 +624,10 @@ struct WorkflowData { timestamp: NaiveDateTime, } -#[cfg(feature = "websocket-server")] #[derive(Debug)] struct Channels { rpc: Arc>, runner: mpsc::Sender, - ws: AsyncBoundedChannelSender, -} - -#[cfg(not(feature = "websocket-server"))] -#[derive(Debug)] -struct Channels { - rpc: Arc>, - runner: mpsc::Sender, - runner: Arc>, } #[cfg(test)] @@ -772,11 +642,14 @@ mod test { #[homestar_runtime_proc_macro::runner_test] fn shutdown() { - let TestRunner { runner, settings } = TestRunner::start(); - + let TestRunner { + runner, + mut settings, + } = TestRunner::start(); + settings.node.network.metrics_port = 7001; + settings.node.network.webserver_port = 2001; let (tx, _rx) = Runner::setup_rpc_channel(1); - let (ws_oneshot_tx, ws_oneshot_rx) = Runner::setup_ws_oneshot_channel(); - let (ws_tx, _ws_rx) = Runner::setup_ws_mpsc_channel(1); + let (runner_tx, _runner_rx) = Runner::setup_ws_mpsc_channel(1); let rpc_server = rpc::Server::new(settings.node.network(), Arc::new(tx)); let rpc_sender = rpc_server.sender(); @@ -785,22 +658,28 @@ mod test { settings.node.network.rpc_port, ); - runner.runtime.block_on(async { + let ws_hdl = runner.runtime.block_on(async { rpc_server.spawn().await.unwrap(); - - #[cfg(feature = "websocket-server")] - runner.runtime.spawn({ - let ws_server = runner.ws_server.clone(); - async move { ws_server.start(ws_oneshot_rx, ws_tx).await } - }); - + #[cfg(feature = "monitoring")] + let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + #[cfg(not(feature = "monitoring"))] + let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + + let ws_hdl = runner + .webserver + .start(runner_tx, metrics_hdl) + .await + .unwrap(); let _stream = TcpStream::connect(addr).await.expect("Connection error"); let _another_stream = TcpStream::connect(addr).await.expect("Connection error"); + + ws_hdl }); runner.runtime.block_on(async { - #[cfg(feature = "websocket-server")] - match runner.shutdown(rpc_sender, ws_oneshot_tx).await { + match runner.shutdown(rpc_sender, ws_hdl).await { Ok(()) => { // with shutdown, we should not be able to connect to the server(s) let stream_error = TcpStream::connect(addr).await; @@ -816,19 +695,6 @@ mod test { } _ => panic!("Shutdown failed."), } - #[cfg(not(feature = "websocket-server"))] - match runner.shutdown(rpc_sender).await { - Ok(()) => { - // with shutdown, we should not be able to connect to the server(s) - let stream_error = TcpStream::connect(addr).await; - assert!(stream_error.is_err()); - assert!(matches!( - stream_error.unwrap_err().kind(), - std::io::ErrorKind::ConnectionRefused - )); - } - _ => panic!("Shutdown failed."), - } }); } diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 2b286276..7634293a 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -36,12 +36,10 @@ impl Settings { /// Process monitoring settings. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Monitoring { - /// Metrics port for prometheus scraping. - pub metrics_port: u16, - /// Monitoring collection interval in milliseconds. - pub process_collector_interval: u64, /// Tokio console port. pub console_subscriber_port: u16, + /// Monitoring collection interval in milliseconds. + pub process_collector_interval: u64, } /// Server settings. @@ -65,10 +63,13 @@ pub struct Node { } /// Network-related settings for a homestar node. +/// TODO: Split-up and re-arrange. #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct Network { + /// Metrics port for prometheus scraping. + pub metrics_port: u16, /// Buffer-length for event(s) / command(s) channels. pub(crate) events_buffer_len: usize, /// Address for [Swarm] to listen on. @@ -125,11 +126,14 @@ pub struct Network { /// Transport connection timeout. #[serde_as(as = "DurationSeconds")] pub(crate) transport_connection_timeout: Duration, - /// Websocket-server host address. + /// Webserver host address. #[serde(with = "http_serde::uri")] - pub(crate) websocket_host: Uri, - /// Websocket-server port. - pub(crate) websocket_port: u16, + pub(crate) webserver_host: Uri, + /// Webserver-server port. + pub(crate) webserver_port: u16, + /// TODO + #[serde_as(as = "DurationSeconds")] + pub(crate) webserver_timeout: Duration, /// Number of *bounded* clients to send messages to, used for a /// [tokio::sync::broadcast::channel] pub(crate) websocket_capacity: usize, @@ -178,7 +182,6 @@ pub(crate) struct Database { impl Default for Monitoring { fn default() -> Self { Self { - metrics_port: 4000, process_collector_interval: 5000, console_subscriber_port: 5555, } @@ -197,6 +200,7 @@ impl Default for Database { impl Default for Network { fn default() -> Self { Self { + metrics_port: 4000, events_buffer_len: 1024, listen_address: Uri::from_static("/ip4/0.0.0.0/tcp/0"), enable_rendezvous_client: true, @@ -219,8 +223,9 @@ impl Default for Network { rpc_port: 3030, rpc_server_timeout: Duration::new(120, 0), transport_connection_timeout: Duration::new(20, 0), - websocket_host: Uri::from_static("127.0.0.1"), - websocket_port: 1337, + webserver_host: Uri::from_static("127.0.0.1"), + webserver_port: 1337, + webserver_timeout: Duration::new(60, 0), websocket_capacity: 1024, websocket_receiver_timeout: Duration::from_millis(200), workflow_quorum: 3, @@ -315,7 +320,7 @@ mod test { let mut default_modded_settings = Node::default(); default_modded_settings.network.events_buffer_len = 1000; - default_modded_settings.network.websocket_port = 9999; + default_modded_settings.network.webserver_port = 9999; default_modded_settings.gc_interval = Duration::from_secs(1800); default_modded_settings.shutdown_timeout = Duration::from_secs(20); default_modded_settings.network.node_addresses = diff --git a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs index fb2386fb..f40a5c7e 100644 --- a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs +++ b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs @@ -98,7 +98,7 @@ pub fn runner_test(_attr: TokenStream, item: TokenStream) -> TokenStream { impl TestRunner { fn start() -> TestRunner { let mut settings = crate::Settings::load().unwrap(); - settings.node.network.websocket_port = ::homestar_core::test_utils::ports::get_port() as u16; + settings.node.network.webserver_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.network.rpc_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.db.url = Some(format!("{}.db", #func_name_as_string)); let db = crate::test_utils::db::MemoryDb::setup_connection_pool(&settings.node, None).unwrap(); diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 0a122e36..f7e1b64a 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -4,13 +4,15 @@ //! [Workflow]: homestar_core::Workflow //! [EventHandler]: crate::event_handler::EventHandler +#[cfg(feature = "websocket-notify")] +use crate::event_handler::event::Replay; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ channel::{AsyncBoundedChannel, AsyncBoundedChannelSender}, db::Database, event_handler::{ - event::{Captured, QueryRecord, Replay}, + event::{Captured, QueryRecord}, swarm_event::{FoundEvent, ResponseEvent}, Event, }, @@ -156,8 +158,8 @@ where }; #[cfg(not(feature = "ipfs"))] - let fetch_fn = |rscs: Vec| { - async move { fetch::get_resources(rscs, workflow_settings_fetch).await }.boxed() + let fetch_fn = |rscs: FnvHashSet| { + async move { Fetch::get_resources(rscs, workflow_settings_fetch).await }.boxed() }; let scheduler_ctx = TaskScheduler::init( @@ -170,6 +172,7 @@ where self.run_queue(scheduler_ctx.scheduler, running_tasks).await } + #[allow(unused_mut)] async fn run_queue( mut self, mut scheduler: TaskScheduler<'a>, @@ -257,7 +260,7 @@ where } } - // Always replay previous receipts. + // Replay previous receipts if subscriptions are on. #[cfg(feature = "websocket-notify")] { if scheduler.ran.as_ref().is_some_and(|ran| !ran.is_empty()) { @@ -629,9 +632,11 @@ mod test { assert_eq!(running_tasks.get(&worker_workflow_cid).unwrap().len(), 1); // First receipt is a replay receipt. - let replay_msg = rx.recv_async().await.unwrap(); - - assert!(matches!(replay_msg, Event::ReplayReceipts(_))); + #[cfg(feature = "websocket-notify")] + { + let replay_msg = rx.recv_async().await.unwrap(); + assert!(matches!(replay_msg, Event::ReplayReceipts(_))); + } // we should have received 1 receipt let next_run_receipt = rx.recv_async().await.unwrap(); diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 0687aac5..f08eb33a 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -1,6 +1,8 @@ #[cfg(not(windows))] use crate::utils::kill_homestar_daemon; -use crate::utils::{kill_homestar, startup_ipfs, stop_all_bins, BIN_NAME}; +#[cfg(feature = "ipfs")] +use crate::utils::startup_ipfs; +use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME}; use anyhow::Result; use assert_cmd::prelude::*; use once_cell::sync::Lazy; @@ -390,48 +392,3 @@ fn test_server_v4_serial() -> Result<()> { Ok(()) } - -#[test] -#[file_serial] -#[cfg(not(windows))] -fn test_daemon_v4_serial() -> Result<()> { - let _ = stop_all_bins(); - - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - - Command::new(BIN.as_os_str()) - .arg("start") - .arg("-c") - .arg("tests/fixtures/test_v4.toml") - .arg("-d") - .env("DATABASE_URL", "homestar.db") - .stdout(Stdio::piped()) - .assert() - .success(); - - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9835); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - panic!("Homestar server/runtime failed to start in time"); - } - - Command::new(BIN.as_os_str()) - .arg("ping") - .arg("--host") - .arg("127.0.0.1") - .arg("-p") - .arg("9835") - .assert() - .success() - .stdout(predicate::str::contains("127.0.0.1")) - .stdout(predicate::str::contains("pong")); - - let _ = stop_all_bins(); - let _ = kill_homestar_daemon(); - - Ok(()) -} diff --git a/homestar-runtime/tests/fixtures/test_mdns1.toml b/homestar-runtime/tests/fixtures/test_mdns1.toml index 30e9a742..63bcce0b 100644 --- a/homestar-runtime/tests/fixtures/test_mdns1.toml +++ b/homestar-runtime/tests/fixtures/test_mdns1.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4001 console_subscriber_port = 5560 [node] [node.network] +metrics_port = 4001 rpc_port = 9800 -websocket_port = 8000 +webserver_port = 8000 listen_address = "/ip4/0.0.0.0/tcp/0" enable_rendezvous_client = false diff --git a/homestar-runtime/tests/fixtures/test_mdns2.toml b/homestar-runtime/tests/fixtures/test_mdns2.toml index 1eb3a72b..c5ddf01c 100644 --- a/homestar-runtime/tests/fixtures/test_mdns2.toml +++ b/homestar-runtime/tests/fixtures/test_mdns2.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4002 console_subscriber_port = 5561 [node] [node.network] +metrics_port = 4002 rpc_port = 9801 -websocket_port = 8001 +webserver_port = 8001 listen_address = "/ip4/0.0.0.0/tcp/0" enable_rendezvous_client = false diff --git a/homestar-runtime/tests/fixtures/test_metrics.toml b/homestar-runtime/tests/fixtures/test_metrics.toml index 9c066443..e6c31773 100644 --- a/homestar-runtime/tests/fixtures/test_metrics.toml +++ b/homestar-runtime/tests/fixtures/test_metrics.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4020 console_subscriber_port = 5570 [node] [node.network] +metrics_port = 4020 rpc_port = 9810 -websocket_port = 8010 +webserver_port = 8010 # Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN [node.network.keypair_config] diff --git a/homestar-runtime/tests/fixtures/test_network1.toml b/homestar-runtime/tests/fixtures/test_network1.toml index f3db1601..bd1c09cd 100644 --- a/homestar-runtime/tests/fixtures/test_network1.toml +++ b/homestar-runtime/tests/fixtures/test_network1.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4030 console_subscriber_port = 5580 [node] [node.network] +metrics_port = 4030 rpc_port = 9820 -websocket_port = 8020 +webserver_port = 8020 listen_address = "/ip4/127.0.0.1/tcp/7000" node_addresses = [ "/ip4/127.0.0.1/tcp/7001/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", diff --git a/homestar-runtime/tests/fixtures/test_network2.toml b/homestar-runtime/tests/fixtures/test_network2.toml index f2081fbf..70c167c8 100644 --- a/homestar-runtime/tests/fixtures/test_network2.toml +++ b/homestar-runtime/tests/fixtures/test_network2.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4031 console_subscriber_port = 5581 [node] [node.network] +metrics_port = 4031 rpc_port = 9821 -websocket_port = 8021 +webserver_port = 8021 listen_address = "/ip4/127.0.0.1/tcp/7001" node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", diff --git a/homestar-runtime/tests/fixtures/test_rendezvous1.toml b/homestar-runtime/tests/fixtures/test_rendezvous1.toml index 6070f8dd..46d2bd97 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous1.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous1.toml @@ -1,11 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4035 console_subscriber_port = 5585 [node] [node.network] +metrics_port = 4035 rpc_port = 9825 websocket_port = 8025 listen_address = "/ip4/127.0.0.1/tcp/7000" diff --git a/homestar-runtime/tests/fixtures/test_rendezvous2.toml b/homestar-runtime/tests/fixtures/test_rendezvous2.toml index 94fa3109..b285c6f2 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous2.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous2.toml @@ -1,11 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4036 console_subscriber_port = 5586 [node] [node.network] +metrics_port = 4036 rpc_port = 9826 websocket_port = 8026 listen_address = "/ip4/127.0.0.1/tcp/7001" diff --git a/homestar-runtime/tests/fixtures/test_rendezvous3.toml b/homestar-runtime/tests/fixtures/test_rendezvous3.toml index 31553836..e8f8d444 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous3.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous3.toml @@ -1,11 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4037 console_subscriber_port = 5587 [node] [node.network] +metrics_port = 4037 rpc_port = 9827 websocket_port = 8027 listen_address = "/ip4/127.0.0.1/tcp/7002" diff --git a/homestar-runtime/tests/fixtures/test_rendezvous4.toml b/homestar-runtime/tests/fixtures/test_rendezvous4.toml index c09f5741..07c57f3a 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous4.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous4.toml @@ -1,11 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4038 console_subscriber_port = 5588 [node] [node.network] +metrics_port = 4038 rpc_port = 9828 websocket_port = 8028 listen_address = "/ip4/127.0.0.1/tcp/7003" diff --git a/homestar-runtime/tests/fixtures/test_rendezvous5.toml b/homestar-runtime/tests/fixtures/test_rendezvous5.toml index 876bf37d..f44441a0 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous5.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous5.toml @@ -1,11 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4039 console_subscriber_port = 5589 [node] [node.network] +metrics_port = 4039 rpc_port = 9829 websocket_port = 8029 listen_address = "/ip4/127.0.0.1/tcp/7004" diff --git a/homestar-runtime/tests/fixtures/test_rendezvous6.toml b/homestar-runtime/tests/fixtures/test_rendezvous6.toml index 8b0b5ed2..a1792f00 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous6.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous6.toml @@ -1,13 +1,13 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4038 console_subscriber_port = 5588 [node] [node.network] -rpc_port = 9828 -websocket_port = 8028 +metrics_port = 4040 +rpc_port = 9830 +websocket_port = 8030 listen_address = "/ip4/127.0.0.1/tcp/7005" announce_addresses = [ "/ip4/127.0.0.1/tcp/7005/p2p/12D3KooWPT98FXMfDQYavZm66EeVjTqP9Nnehn1gyaydqV8L8BQw", diff --git a/homestar-runtime/tests/fixtures/test_v4.toml b/homestar-runtime/tests/fixtures/test_v4.toml index 7748c44e..b6e10d03 100644 --- a/homestar-runtime/tests/fixtures/test_v4.toml +++ b/homestar-runtime/tests/fixtures/test_v4.toml @@ -1,11 +1,12 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4045 -console_subscriber_port = 5595 +console_subscriber_port = 5590 [node] [node.network] +metrics_port = 4045 events_buffer_len = 1000 rpc_port = 9835 rpc_host = "127.0.0.1" +webserver_port = 8031 diff --git a/homestar-runtime/tests/fixtures/test_v4_alt.toml b/homestar-runtime/tests/fixtures/test_v4_alt.toml index d9ccc9c6..72b5222b 100644 --- a/homestar-runtime/tests/fixtures/test_v4_alt.toml +++ b/homestar-runtime/tests/fixtures/test_v4_alt.toml @@ -1,11 +1,12 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4046 console_subscriber_port = 5596 [node] [node.network] +metrics_port = 4041 events_buffer_len = 1000 rpc_port = 9836 rpc_host = "127.0.0.1" +webserver_port = 8040 diff --git a/homestar-runtime/tests/fixtures/test_v6.toml b/homestar-runtime/tests/fixtures/test_v6.toml index 7a4a15e4..4f371951 100644 --- a/homestar-runtime/tests/fixtures/test_v6.toml +++ b/homestar-runtime/tests/fixtures/test_v6.toml @@ -1,12 +1,12 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4047 console_subscriber_port = 5597 - [node] [node.network] +metrics_port = 4042 events_buffer_len = 1000 rpc_port = 9837 rpc_host = "::1" +webserver_port = 8050 diff --git a/homestar-runtime/tests/fixtures/test_workflow.toml b/homestar-runtime/tests/fixtures/test_workflow.toml index 549f337a..6a0efb11 100644 --- a/homestar-runtime/tests/fixtures/test_workflow.toml +++ b/homestar-runtime/tests/fixtures/test_workflow.toml @@ -1,10 +1,11 @@ [monitoring] process_collector_interval = 500 -metrics_port = 4050 console_subscriber_port = 5600 [node] [node.network] +metrics_port = 4050 events_buffer_len = 1000 rpc_port = 9840 +webserver_port = 8060 diff --git a/homestar-runtime/tests/main.rs b/homestar-runtime/tests/main.rs index e957b31f..cdaa6596 100644 --- a/homestar-runtime/tests/main.rs +++ b/homestar-runtime/tests/main.rs @@ -1,4 +1,5 @@ pub(crate) mod cli; +#[cfg(feature = "monitoring")] pub(crate) mod metrics; pub(crate) mod network; pub(crate) mod utils; diff --git a/homestar-runtime/tests/metrics.rs b/homestar-runtime/tests/metrics.rs index 3252996c..02fda3bc 100644 --- a/homestar-runtime/tests/metrics.rs +++ b/homestar-runtime/tests/metrics.rs @@ -37,8 +37,8 @@ fn test_metrics_serial() -> Result<()> { metrics .samples .iter() - .find(|sample| sample.metric.as_str() == "system_used_memory_bytes") - .and_then(|sample| Some(sample.value.to_owned())) + .find(|sample| sample.metric.as_str() == "homestar_system_used_memory_bytes") + .map(|sample| sample.value.to_owned()) } let _ = stop_homestar(); diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index 8a4a346c..7bfd507d 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -98,7 +98,6 @@ fn test_rpc_listens_on_address_serial() -> Result<()> { Ok(()) } -#[cfg(feature = "websocket-server")] #[test] #[file_serial] fn test_websocket_listens_on_address_serial() -> Result<()> { @@ -116,8 +115,7 @@ fn test_websocket_listens_on_address_serial() -> Result<()> { let dead_proc = kill_homestar(homestar_proc, None); let stdout = retrieve_output(dead_proc); - let logs_expected = - check_lines_for(stdout, vec!["websocket server listening", "127.0.0.1:8020"]); + let logs_expected = check_lines_for(stdout, vec!["webserver listening", "127.0.0.1:8020"]); assert!(logs_expected); diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 7c754b94..37eb4078 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -10,13 +10,13 @@ use nix::{ use once_cell::sync::Lazy; use predicates::prelude::*; use retry::{delay::Fixed, retry}; +#[cfg(feature = "ipfs")] +use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}; use std::{ - net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, path::PathBuf, process::{Child, Command, Stdio}, time::Duration, }; -use strip_ansi_escapes; #[cfg(not(windows))] use sysinfo::PidExt; use sysinfo::{ProcessExt, SystemExt}; @@ -29,6 +29,7 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) const IPFS: &str = "ipfs"; /// Start-up IPFS daemon for tests with the feature turned-on. +#[cfg(feature = "ipfs")] pub(crate) fn startup_ipfs() -> Result<()> { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".ipfs"); println!("starting ipfs daemon...{}", path.to_str().unwrap()); @@ -95,13 +96,13 @@ pub(crate) fn retrieve_output(proc: Child) -> String { /// Check process output for all predicates in any line pub(crate) fn check_lines_for(output: String, predicates: Vec<&str>) -> bool { output - .split("\n") + .split('\n') .map(|line| line_contains(line, &predicates)) - .fold(false, |acc, curr| acc || curr) + .any(|curr| curr) } pub(crate) fn count_lines_where(output: String, predicates: Vec<&str>) -> i32 { - output.split("\n").fold(0, |count, line| { + output.split('\n').fold(0, |count, line| { if line_contains(line, &predicates) { count + 1 } else { From 16392e7dea6fd295ab9c0ea101d7e8546b346e31 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 31 Oct 2023 14:01:17 -0400 Subject: [PATCH 02/42] refactor: cleanup settings --- examples/websocket-relay/src/main.rs | 1 - homestar-core/src/test_utils/ports.rs | 2 +- homestar-runtime/src/metrics/node.rs | 4 +-- homestar-runtime/src/network/webserver.rs | 24 +++++++++------- homestar-runtime/src/network/webserver/rpc.rs | 4 +-- homestar-runtime/src/settings.rs | 28 +++++++++++++++++-- .../src/test_utils/proc_macro/src/lib.rs | 1 + homestar-runtime/tests/metrics.rs | 1 + 8 files changed, 46 insertions(+), 19 deletions(-) diff --git a/examples/websocket-relay/src/main.rs b/examples/websocket-relay/src/main.rs index e3909584..35b77ec0 100644 --- a/examples/websocket-relay/src/main.rs +++ b/examples/websocket-relay/src/main.rs @@ -36,7 +36,6 @@ fn main() -> Result<()> { Runner::start(settings, db).expect("Failed to start runtime"); // ipfs cleanup after runtime is stopped - if let Some(mut ipfs_daemon) = ipfs_daemon { match ipfs_daemon.try_wait() { Ok(Some(status)) => info!("exited with: {status}"), diff --git a/homestar-core/src/test_utils/ports.rs b/homestar-core/src/test_utils/ports.rs index 362c2868..673fc0fa 100644 --- a/homestar-core/src/test_utils/ports.rs +++ b/homestar-core/src/test_utils/ports.rs @@ -9,6 +9,6 @@ static PORTS: OnceCell = OnceCell::new(); /// Return a unique port(in runtime) for test pub fn get_port() -> usize { PORTS - .get_or_init(|| AtomicUsize::new(rand::thread_rng().gen_range(3000..3800))) + .get_or_init(|| AtomicUsize::new(rand::thread_rng().gen_range(3000..6800))) .fetch_add(1, Ordering::Relaxed) } diff --git a/homestar-runtime/src/metrics/node.rs b/homestar-runtime/src/metrics/node.rs index bb58f389..d25e575d 100644 --- a/homestar-runtime/src/metrics/node.rs +++ b/homestar-runtime/src/metrics/node.rs @@ -108,8 +108,8 @@ pub(crate) fn describe() { } /// Collect node metrics on a settings-defined interval. -pub(crate) async fn collect_metrics(interval: u64) { - let mut interval = tokio::time::interval(Duration::from_millis(interval)); +pub(crate) async fn collect_metrics(interval: Duration) { + let mut interval = tokio::time::interval(interval); // Log static system info log_static_info(); diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index e0332a3e..f18e4460 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -233,11 +233,15 @@ mod test { use notifier::NotifyReceipt; use tokio::sync::mpsc; + fn set_ports(settings: &mut Settings) { + settings.node.network.metrics_port = test_utils::ports::get_port() as u16; + settings.node.network.webserver_port = test_utils::ports::get_port() as u16; + } + #[tokio::test] async fn ws_connect() { let mut settings = Settings::load().unwrap(); - settings.node.network.metrics_port = 6000; - settings.node.network.webserver_port = 1200; + set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); #[cfg(feature = "monitoring")] let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) @@ -272,8 +276,8 @@ mod test { #[tokio::test] async fn ws_metrics_no_prefix() { let mut settings = Settings::load().unwrap(); - settings.node.network.metrics_port = 6001; - settings.node.network.webserver_port = 1201; + set_ports(&mut settings); + settings.monitoring.process_collector_interval = Duration::from_millis(100); let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) @@ -284,6 +288,9 @@ mod test { let ws_url = format!("ws://{}", server.addr); + // wait for interval to pass + std::thread::sleep(Duration::from_millis(100)); + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); let ws_resp1: serde_json::Value = client .request(rpc::METRICS_ENDPOINT, rpc_params![]) @@ -303,8 +310,7 @@ mod test { #[tokio::test] async fn ws_subscribe_unsubscribe_network_events() { let mut settings = Settings::load().unwrap(); - settings.node.network.metrics_port = 6002; - settings.node.network.webserver_port = 1202; + set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); #[cfg(feature = "monitoring")] let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) @@ -341,8 +347,7 @@ mod test { #[tokio::test] async fn ws_subscribe_workflow_incorrect_params() { let mut settings = Settings::load().unwrap(); - settings.node.network.metrics_port = 6003; - settings.node.network.webserver_port = 1203; + set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) .await @@ -375,8 +380,7 @@ mod test { #[tokio::test] async fn ws_subscribe_workflow_runner_timeout() { let mut settings = Settings::load().unwrap(); - settings.node.network.metrics_port = 6004; - settings.node.network.webserver_port = 1204; + set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) .await diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index da449917..6749585d 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -96,7 +96,7 @@ impl Context { } } -/// TODO +/// [RpcModule] wrapper. pub(crate) struct JsonRpc(RpcModule); impl JsonRpc { @@ -106,8 +106,8 @@ impl JsonRpc { Ok(Self(module)) } - #[allow(dead_code)] /// Get a reference to the inner [RpcModule]. + #[allow(dead_code)] pub(crate) fn inner(&self) -> &RpcModule { &self.0 } diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 7634293a..3b2ecdea 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -33,13 +33,25 @@ impl Settings { } } -/// Process monitoring settings. +/// Monitoring settings. +#[cfg(feature = "monitoring")] +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Monitoring { /// Tokio console port. pub console_subscriber_port: u16, /// Monitoring collection interval in milliseconds. - pub process_collector_interval: u64, + #[serde_as(as = "DurationMilliSeconds")] + pub process_collector_interval: Duration, +} + +/// Monitoring settings. +#[cfg(not(feature = "monitoring"))] +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Monitoring { + /// Tokio console port. + pub console_subscriber_port: u16, } /// Server settings. @@ -179,10 +191,20 @@ pub(crate) struct Database { pub(crate) max_pool_size: u32, } +#[cfg(feature = "monitoring")] +impl Default for Monitoring { + fn default() -> Self { + Self { + process_collector_interval: Duration::from_millis(5000), + console_subscriber_port: 5555, + } + } +} + +#[cfg(not(feature = "monitoring"))] impl Default for Monitoring { fn default() -> Self { Self { - process_collector_interval: 5000, console_subscriber_port: 5555, } } diff --git a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs index f40a5c7e..928e3b49 100644 --- a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs +++ b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs @@ -100,6 +100,7 @@ pub fn runner_test(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut settings = crate::Settings::load().unwrap(); settings.node.network.webserver_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.network.rpc_port = ::homestar_core::test_utils::ports::get_port() as u16; + settings.node.network.metrics_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.db.url = Some(format!("{}.db", #func_name_as_string)); let db = crate::test_utils::db::MemoryDb::setup_connection_pool(&settings.node, None).unwrap(); let runner = crate::Runner::start(settings.clone(), db).unwrap(); diff --git a/homestar-runtime/tests/metrics.rs b/homestar-runtime/tests/metrics.rs index 02fda3bc..e764b888 100644 --- a/homestar-runtime/tests/metrics.rs +++ b/homestar-runtime/tests/metrics.rs @@ -13,6 +13,7 @@ use std::{ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); const METRICS_URL: &str = "http://localhost:4020"; +#[cfg(feature = "monitoring")] #[test] #[file_serial] fn test_metrics_serial() -> Result<()> { From 234621df88e319c628b27aac45b743821834f447 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 1 Nov 2023 23:21:37 -0400 Subject: [PATCH 03/42] refactor: testing around e2e websocket/jsonrpc, feature-fl --- Cargo.lock | 14 ++ examples/websocket-relay/README.md | 4 +- examples/websocket-relay/example_test.wasm | Bin 358815 -> 285147 bytes .../relay-app/src/lib/workflow.ts | 34 ++-- homestar-core/src/test_utils/ports.rs | 2 +- homestar-core/src/test_utils/workflow.rs | 16 +- homestar-core/src/workflow/input/parse.rs | 2 +- homestar-core/src/workflow/instruction.rs | 6 +- homestar-core/src/workflow/task.rs | 2 +- homestar-functions/README.md | 2 +- homestar-functions/add/src/lib.rs | 4 + homestar-functions/add/wit/host.wit | 1 + homestar-runtime/Cargo.toml | 1 + homestar-runtime/src/metrics/exporter.rs | 3 +- homestar-runtime/src/network/webserver.rs | 65 +++--- homestar-runtime/src/runner.rs | 23 +-- homestar-runtime/src/tasks/fetch.rs | 69 +++---- homestar-runtime/src/workflow/settings.rs | 2 +- homestar-runtime/tests/cli.rs | 4 +- .../test-workflow-image-pipeline.json | 75 +++++++ ...test_workflow.toml => test_workflow1.toml} | 0 .../tests/fixtures/test_workflow2.toml | 11 + homestar-runtime/tests/main.rs | 2 + homestar-runtime/tests/utils.rs | 4 +- homestar-runtime/tests/webserver.rs | 127 ++++++++++++ homestar-wasm/fixtures/example_add.wasm | Bin 8768 -> 8780 bytes homestar-wasm/fixtures/example_add.wat | 176 ++++++++-------- .../fixtures/example_add_component.wasm | Bin 8765 -> 8803 bytes .../fixtures/example_add_component.wat | 189 +++++++++--------- homestar-wasm/fixtures/example_test.wasm | Bin 286703 -> 285147 bytes .../fixtures/example_test_component.wasm | Bin 287104 -> 285548 bytes homestar-wasm/tests/execute_wasm.rs | 2 - 32 files changed, 526 insertions(+), 314 deletions(-) create mode 100644 homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json rename homestar-runtime/tests/fixtures/{test_workflow.toml => test_workflow1.toml} (100%) create mode 100644 homestar-runtime/tests/fixtures/test_workflow2.toml create mode 100644 homestar-runtime/tests/webserver.rs diff --git a/Cargo.lock b/Cargo.lock index 3916070e..4383bc23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2558,6 +2558,7 @@ dependencies = [ "tokio", "tokio-serde", "tokio-stream", + "tokio-test", "tokio-tungstenite", "tokio-util", "tower", @@ -6665,6 +6666,19 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-test" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-tungstenite" version = "0.20.1" diff --git a/examples/websocket-relay/README.md b/examples/websocket-relay/README.md index 46a41427..dc074bf1 100644 --- a/examples/websocket-relay/README.md +++ b/examples/websocket-relay/README.md @@ -63,9 +63,9 @@ if they've been previously run. On macOS, for example, a simple homebrew install would install everything you need: `brew install rust npm ipfs` -We have packaged homestar binaries via brew, so +We have packaged homestar binaries using brew, so `brew install fission-codes/fission/homestar` will install everything you need, -including `ipfs`. You will still need npm to run this example. From this folder, +including `ipfs`. You will still need `npm` to run this example. From this folder, you can then run the example like this: ``` diff --git a/examples/websocket-relay/example_test.wasm b/examples/websocket-relay/example_test.wasm index 8ac772fdc0957fa82cec223ec893a3e8b9a77e81..b382b252b6466241d86a0e378e0019a8fd910b6c 100755 GIT binary patch literal 285147 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 yeLcRz)_Ft=FO=G#h08{(OPIts3OU zdNZ8R1U7G4!^HA;`X&TLmr7DC31`8aTATK9{(z(`VGISVK7_EW!j%;mkq`rLxOcxA z&^W1#_*S~9kwTHA`bMj0wc3j9d0a|&y560L_WpX=kfW_|Lsrcp9E1iDbjWSq zuK*)8gq{UBuwt|_95uRAtZA~9qw_T?5O`xST_L$tu`)Ctuc@NE#eeCF7-wjc#Zqs2 zb+m$ZvsrITLF8LE{V(c@OhF7qF+&k3w#3+&02Z6yK_%Axpu3;fs#I^YMuX`fb)vEQ z+R#X;n9(?u9$q*1Avyho3@y5Y-uecUo^Dn)`qO@3GI*6~0(YjFX|H^}S2%|w5tH>w zYv9WWJ;>?4bV##tb*KQkN5aYUKDKR2g&cQ5iV+FZ| zzZGe>X^HB~vP5h@-OFN4x4`%rSfBefIgaUt(%=CjQ>roO?h&~~)IL_Q|S@2M|$9T^kk5B-E+DiF=UgkqU&eutY7aTzJ9F7fO+ie6o;2kGV626xD6| zU-J~LJd=f#fIEm^C4&-lF4fdi3OD#YG)V?6p%clVGlWji$lq#C+9X*I*Q1&erJAX7 z^M$HVISuTp4w2ywD2kDgA)nA0W{`gRiN0~tM0gM{Qb72BKA(ok zMMKO~ayt@Yjr`5e*lTZpK{S^oXb~EctSM&O5>@Gy1~KUB>Lv|^G)c87h@U1Y(v0UIt_1Tv&V~ef1^c8F#|(761{8E#LGh)~Un<4ZC-MYalov=Kl%~d>2_*xF*P((N zKIomVJD-AsL6>?;XrkUGz1VpKTl@>E>+Vc}I`iG^g}F7+(;jWK;ajsTL}S*nO#85z zkqrRhn^}(+`^F0pXw_pw9b`VY-Dy^&NmERZroaq&sY(O2kc?b^E-`?H)Szflqv zkFnx(vd=m}p%56&uWjV89*pa%(l;bA&+~!8r zz<4HBpzVJ7kT$iYpsi6=JZ&k537}uuf+rJt$g?0l?YX7f#kY`tbWDrM7J(=U`P zoG7{(GZ{NiNk1(YdmJ6!TOYeKdpeZL>ieV9cFkMHe{xuDw?TjB!ShZdw{!0^?r{BhEP#-6Jd zv{{xhR?>7W=gASAiOY)m(n+UBzCI`kYOH2;;80W4ERaQ-RQ4vVmG=BCgf8F9GE%n zSEz)Ud|69i#cZ`XtZW4-kOMd*GC9REi0KIm50|zfH8fkK!C{icI zt6OT?6-f?T^~Z&ej7SC&R@zXj4(a+S^~&>z;fqfnKB$NhGrDJe9;`!RM28-r9yjaBJKF9Zi+3b=(ZOs)B5|_nle$vXI3# zONB!8Gd^2(mks?!Q7~_>e+R6`1`F8ri7_{se=HcHiq$`_OL;sH}isz9E8nX6+ArH*euv ztZMFwH_nkfuV4F$Dm0=sj?<3P2sc>IPK;J7Xzm!zsrzhJIema75-|E%UHc<7TT%Xp z$OItz@5`3-g{0MKE%9dM0nh3&DLQrxrVC4?R})c8mv~nPpCMs{K@FLHT^007?oeW) zNR~K+>OOAJt+<0)O&S1@?fpGgiUbEPE7Qh0KJc!fM}!4`uGqWF%~(EtfhX5&E}zOR zyRKajzI(_`KO^d`J#Xm^Wwv!@8|F7+XU2&yL|5%odi28? zYs#a71eJ|Q>&xfR({G3`EF!4A^3Il6nOh#4M1^a*mue<}R{#P=-n-r(4?bo(mgHRW zh=^6$8@Kcebib}lB@h8hZ_%V8H4P?`>Zo~8PIKYO)*S5Y8VzFfTu#1qF)DihP}Dei zAaS;_Vl8NyqAT+ct#J#R?Ef?`vJhpz*s4^Hk>MT5s?6t>?X~8jTyrPw+7C^ef4KXW zJ%`vQPwAm^ywIPZm}IMncP+!Xg7)*2C~QBH1+g-#{y|QgoQrGUDn1_7qX{92!MOH6 zB=Rcv$4FWC8cDi3Yfwf1M3WMXQY0a>IK=_^^!o44Uwp!ef^? z_qn~{u}2Q1;yJoW!_h(?#Q$??{AH0}g2T9bzreaO_?vCsL0fa-!Nk*HJ=DIWHZK$n zPgFV;SLFM1eF|NTcpqFBh5ab)=c-)1X6>+-%1BV}20Z;6YD>j^$gVxKiI)6f^8reG z)E*GUlDdb?)7&>Ju5uKW4HKWVES;Rz>VM4Lnk@(`XZzFPlsB4&aQzv zywLcz9oY~D!azGNR)UG{~M*i-iT*X@FcD@f7KbDce7eEGj@7Zw0?p@~ zwVZN?pz=oxIXTECpYwPgW-+J6m|W?n7S9>B!DK`^_BPcMuZH&0!&aCq=dW8%%4yH( zx5G3E{T-GQ&*S!-en(6*=Z7q(_G7fQ-EWF%=KPf9Jik*;X&1S+f5~!Q&?zU%h7rSQ z%d+U99eHT4dAsm)o_o7p1Bva^Lpb@w?`xB`eZaDGCNb$2xjOz)$ZG9AV$cG02)~($ zjr|!NqclJMFvXi;F>;SI@7p9<=Z_!xz1GL4m-tv}z8_(a+6en!7;H84**peMUA)Ua zkb~5O+Fp%pHZu0>uCu$9++AsR zI*;ZuyVLkMVt0~-Y}J7Xw0Ft2c?c6y?nu-Kp?4SY?pE#yMz1*XH>2Boyt`~;SQ~z` zIn+z}j2Y5PxS;jDs3e_AVOBVKPlylnGT*!x?WN#o_HDBev!5xI$jvH!tU|#jvsLJE zQ}mL$%_(&IQq4DLK}h3v(jnMQNMDm&Ck8QE=HDm8^hXcUzZMgbdt_FI-!^!tJ-gx_YC zlmC1AT1tLv{@?X)OBw{~HzZ@3CW%i4i zmD=jG_uI>^lV8=8+X?ek9I%EEn6zep+IWoJtTN$kCtQlymL>@$=(c9mL#J(zuNWgv&ZH(Te zge{i9(`&P*WSfM?ETQ~csuX)=5x+jXzljWF@!MJ=2J;#EnVXnW31$L(yMHMSP5HAU zd?fEvOOqx?=+GQP6F=2&xkYHPL?wp=u2pZTmn*!8fYDxOG-o?}(acn^*$;ieAW?O& zs^d3+UcFBZle(5EjI+PpOlWQY-Q28cb&*<0SBZ^78m7sMSNvg&1PXvE^z7gGT(n_@ z)duZGoj%*ryli#miQF(*9E-x3(;OzPe`&bV@g9(bM57LpQwRySUa(6aR`aoN$_{{I z^P#`Yc$SVoi+t})-cJg`_p>3sHSPH`X?!_yH?w5rZ&$Y_C5w=Ip7g)Jdymi3+01am zYu2ix(9L{M3*BV|TY~&;>fx^2j?6@=ySysezK z0hoMZ*#MaQqeaDoz1dnO4w>EVku^?p`s=G`(|BEH}* z#y^=K56JI>?;q1##-^f8+hSx#SQvQR%w|QxF2a}0<{o_VK@~wtYeMvd6^bPtK+(1j zKXCYqzx&~P-}wX~Y8%VdAw zTOJ#^k-70AClRi_Jxr8o3^qFN$HU!nP9l_>o$QZ%8Uie7@dO)$JZvI=B%!=M;M&?B z3PEKq%GLjixAJP=%HMcw|9MNa^6LMnR(>TeCHW9aIfgR7 zQwUdorL7QvoWN^;ByND3SrcR9jW&aEQc*7l)o^;vw(?Iaq=%g!quH`~je7@G2#a7e zlit-7>j)ls7PuBV=X1`Az%FAxtG3dlLygQ^Md^hf+6^hGnSj4oaSe}tv zcRill=3+WXE>N6_pkHa4TySvYu&Bb>QphNr_Ygp4Xz;c}tabLDxh`FoelSKro?g)|td4{tet)&@NmH+|C3SSh? z=s{QLCDsJ)$!ppALyw_5XzCQx8;w2*9BmUuPT{`iw_BHZ!?vq_sTnw*%BQ#X9qUZK zT}gH>wR)vViE-`TadkD>Q}&C?_=EQ$JAmDB`}Uvxw^()9e#^J)^n;9)-4-R*>JCrC z+I>ciC`u!&MUrJyK@aSuo-S*!BHU->Mmd|JKi~i_i(QSYqn0+6H>usHn&?mkD7f}f z6>NJ(DPb-9IBffCoC1Y?_I1<+)q!tW?yQ+vR+dxxK@4GyARLqc(i_5%tg`qn+RQU@ z4SeNoi_WI#Y4w=yv(Rc_(F<-?mq>JL=so z4Neo4oAEABkZHMgzv|@CzmV@a)5-I_46EMh+}gvA9UVJ?`@eCX}vFGmDU5S|nS)d>2D`Rj>&PK%f!7_dr+71^-p$TM6;*HdxzS z%i|O#GZIQXO>1o*q-uFz10$W3%KQH#Z>4@EHntIoT{(d&QYY5GJWnOuL|f)8jxS@5 zY^`2w;Ni6*bjUH)fT~-J1c0?xu=G%Fzzv=jJ&Gx+i`dvI1lFN=G7)|v?Im6d(v{>* z(W+KwdviCCi4Ot}U{3VfF5uVufkf;*`Q95jg$q-?euKzscI5JoroaGUUx2D4t`Znj z76}3gF&I{$hGt1W9!`8wx$PtEC^Tg;r8ljpo zoZnm-Z~#CUpO`#av5|Bq0CwVK6wtLT1I$>e#weA}pq4 z(jDyLUxZ+;W1^94)t~L^NfSjspn^A;&`ECe>q*pJ30z3fd&ZVm-BI~TKK5g;_?M#c zV{hzN%k-W}1fwWf!g&`Y;cdp~+Dxk4h9Zz`MPJb1PBMBBxpa`6kayD>oNVx?(QAx? z?8~CCu`#*V@Ou-R#&`pH=#XPtydMZY4THI@^AoY;)0UL+ z4A}6(Spxu6puJz(b=wpXztxCWWuxHHWJjZKVcT-8#APeqIoKs1nqJc+r~;l_)A!HO zN?!>~H+_hEhBK*kGxp|wQ>Oad6yca~utU)sY4p3>b~Y-i6WOmy4r(0XMcSy^VJLlQ zZ0mR0*(rT#HLW16rpRK7rImTuAbSCcH+U2BS|IDg0Cz6HU9GYn(SsG-Z>OyEr3`mK zJ|xHr#RTQR8av#)0r#?*fFe!cD;r53_RMcX4*65{HrPo@U)|yX`Sce*#f%i zE6}ULt%o`1AlJRZ1-Y#Loc}l8qU1imQJt5M5L`)_4kiRE;CdkOEtw|plAiY`^(q~d zxKz>yh}RRn6dt{PpqoCz?xd+O@zb{xH^D;X5tPKPp=q>2!wh3&l{U>-9WwrHa!qd_ zEp>IqFug4vfxV|iF{nup%oMj7Q*`G^c20S^1E?82FhwxF=&}!u6#~JR^B)?mm-FP! zblYei2~3Iqo6JQC%Oi8qijM0zMZsIc^XkmJb(du zg$%;|If&qbaNj3713OzK-1q9i%jiA2^}>BOw{~Q&Skz6tMr@b~_tvm+T~W9n3~;ZO za2Lrph5LRzc;POtU??}%H{m{Mz%$|A8ab{k3U}iaD!p04-8du@?#4E7IaF`#GOAgL zboc2(wOhl+WktxwNtM0sX*opfIrPv4KT{ng$j9)4*Z_8J%*%r}Oo0 z|CLpNW@`{obDq?73dtCT7mZ&kY&HxmS$f;B^Ey@34?B}#*xB1Y?0~&&*nu^H3uMPxC zUiQo+9EQmU422iy$`E*A$;6pZ$)>Q|6f;d@o$g9D;OS0?!mZg-3F@Ugl*9}DRn1@3 zyhU+fr;^?a6_uS(Rb?S6S|cXMZMw+^<`5SdK5GPzI+<@T5vf+VGli7%2thkh_^JXD z=L@0^{ly(Fq$tmHAw_v63n_gT*xzd+(^f(zAUQlB2uNc37X{=5v?*-9QUY>J4_-jt z#hsUruZ9s6x1R}uI@kbb^06g(Z!gNneF5w*Nj^%#Hszy9UN0XdkKJ)VXWB2q>afAj zL}W|;zM=>}lfSo}RsL$zjhB)0g{rrt?@NlH=R(zAbXL7xX9pNJx|4-kx5V$QoyG49 zmLq=m1@XH#h~Mu=9Pi1A;~<(%9Gj2~;`q3=R|ibAM6+qbIq6sn!WmYARuIp2x-Pee z5{}>H3UjW|6$RlUU18GmbVXmiSXY>pg8)olT|=j{IZVGyCtf3AsyEMTtd$+3Df9OR znLk;O`FCk9(MjZI5?ynO6KWAH=QIqPh*%3V$v>5s_|xR1l8Z?E=6LYBtdf%k)k}N3 zeVp3)k;eo5Iv@It672xu$(jUQ@$^WSKdeyqqATasbxt!1n zgMg&JjRff+cZH8FoSOyB9SjqnqV$Kik&6AzLEwyWLnah82u$Qlq>>kVA%Bs?JLl!@ zsITI(W=H)3F5uzj#_*2%g%bRwttQ9o{U*Yh3rScrT1tE~`bb(cF|v6Zf=vTJ#Y_LH ziGJ!yVi#mPs2c*d@`1!ZTUUdaCy06TmXPOJDvKQ9wt$M3^k0*5$a20}>BXGjJY7h+ zD80zd6jD~FtKH#33TFnp`9eycJDO}C@e3bO{Rt8YScVioB}u871myVl@F9U!UxC{ zR!`HodA=(Yf|(0}s7$IdKh2kzYi!jJ1cp^kTXQtisIQ0GV0GWoFi&ehgAV>lK^?99 zWzM8G+uEc7cBIc|7M8@CKN`>j(Sl0lz?uqHRFJHcSpF>0A5L2$Q5}2PmJ*a6kUf$| zwm*E!Bb&?{OErJoE%jUt3XB8{gIZ2j5bscytps%Wl>oC#H!(V!ZA_@#ia}WYH{#8! zTDYX;ZnIxKY)FB?I!x8KlFH?3E?2fN4N1z&JytdJPsqBi@;f3z>Vo+Xm#@UG5M0V( zGP3pR-;GEP);;YNCQcb)?WNt{*y!PPH!thG6wC*=q&*S^N@8~?mP`*CKt+()6NOHy z9dcS^?S{rs$vr0dQr1D0F^1 z4kl+ao4SKoX{qS*-o8yb#oCbwoGmjl_cBWWly_U)kzr;j8w|$P>RaHpFF*+@yh+(UtS*Wyl8SxI=225snnU z9cn1q+}d>H*V?EXE7$yF>lR+l2mZEKRE_sou~#takNy9|y#-Vi-Mct^I_Yi>A>AEP zDhMc@DhLNSAbIE#X;4r=8jDZ_F-Q>v1Vlj;ln_A>2|>c56$AP9px&SFd++_P^{xN6 z);FwS=9y>b(=&UYyO-&V-+|F%LD3DBfPlWxcoQ@<1?EnG z6Bp_si13CM1q1DbgVlw#!B~-*qAX2xb}Vt5C)A2i zZ}15e-0&N42SYosKz2@#H&hP=mhJ~LR=^lM3|f{BcqwQxTR`?-y#7<)&N5d!&K0Tw znt#7zEdVQj*?G_LQg@rKo6j)6aWiQV&D$+3&jvT~HYav_M{{2XJTzR}X5yHvi7Q1Hdm_+$jYz5I`j_0g(qR*4P=^{1ub@ zUH}r5jiN)qU_c%b5WA&e41X41{0FcPGz9u1fy$t5m=OQ@H6+xT-#a$`9*_lW1|!3p zkeWea0tyC|lMqA>(3%#gm$Bgikc53f2DXx5SsBFp2smzM#1@c+9um9Xf!YrFy!F6J z1Yqh86r}z#d5{GVzyKN4Lk*_UITI3sdDE{ zJQN~4w7Uf=7%&l#Z6`T!gP`bN1C2nEiN8t*@wxyUv=#VYkmr9+4bp<`T6StDfrjo( zO#rgHBPXEaJ3WPr4b;=$gZKaJDU5g%{hNe4Tms3wNnkr4a0OvHpcfEAF!>2k@V^MW zv-=SWbXEXbs}uzh8SHC_1xa=mqK5K8YeC>Z<Y&Y_t{JL@;>%#?w|1Cle)-+#&(Fxn9B z|BKvEZpaWpU4hgC5-yMl#Fx-SH6R#psFYs<^Pi%i=l>BH7_lA>!hwIXVaH?tuL=(e z1xgRe|33x)w~9k@EC>lYAUgsQ2uM$$@G&kv5$Ch>mJCut%J-L!0^xzg9+)U7*T2>6 zzt;r=b3>4jQQoNqq(|t2X2Afx0D+*s3ZkJ|0)I?1WTAl;L8O5E(9X+0Af)%s{A}W{ zIitXGUQPsN@;_kl4mgcMpkk0G{_E8e6a;Glo`pa3Z`TSD@HY{6CWt^wx%9nk$^{!e zK^k2!+qr>+)@uQ~`^3g}Lkre}#-sMazyZB^fUQ}uP}g_RKfsQ#FlgzN9SXuA3ZnG# zXFH+7c7E;I#%^%gGW1h3`#oZBxz>L}x?O=m7ldF9&Rvirvd(AxKR6~DkOIsY1qXcsu*>j2 zx0q-^7~~$(DM7H?Ml|q;5Dq~2=qN$-9#V3YAOZZm0%Qie@ak!7P8_ng4}{VAj9zEdRnoFxy{v)_>t4 z_-{Phzwi+JH=g}pcnJO*FZnM#1pnQ?D8xVe55a%qlK}j8c@WI|SNoIyg@<4WFKZJ- z2@H~rq)>v6k^Owg-r&Edtc)u;(1R))Nb&Ft3J#2r4GMIX_4IS2gv+=Fl7lHhGM-dv z6?b`6Hw6V37qYyvoSeM7tiPX!w498*s*JqcP6mpfE7gq>C=(b2>bVGKfKkBp2B=r? zFZdCFMgD?o0Sxi|Px^WQ^ZW%50a)-a_$z=(|APN6@7urd5d3%f5Ul)H{_gICihhRAXd-{cveLUSrK@^HN)G0E_kK#cN z_6((v0L7@Glt6bMYM7^=$A6I-WI{2(6u<@2;r|b1)ZLBZ?gJzk2&$sYFEMqc`uYb_ zf`Wea9Raw229|*rrUg@k1;L0gZrBCb5!fr3ChQUH5nL1|3NMG1!#!Xluo1)vd<3Zq zUZ7Rss>lWyJ%S!X52r`dBk8djunbfNA_J2F&p^v!WWgR?vUpjvEUhe3mMDvrrLTfj z;i^zoG*yTyLKUWpt_og-uR>SRRw1j1RoE(eDvXMwqNp@f1eHL=Q0b^}DxQj_(o&IB zA{9%eUxF>+mQYJHONb@H5@v~R3BH71LNC!SA(x0t*d=;?7(b35#ZSYJ;3x27`04oJ z{CIveKP^9!pU98pr!RsP;fhd2G)0IaLJ_8jt_WU)FG3g579op>Mc5*GCzun?3FSoN zgm5A_VVvlk;7)iav=glp(uwGVb)uhyP2wg|lQffvNx~#%l5P?{iJwGI(oP~LiIdn# zdL5V!P6wq!ql3^P=wNi{bl^I89kdRu4pN7xgVmvLgSFw>P;E4Ah&DnSrj4!*-iB{O zx6!sC+lX!0Hu@M?3@!#0Llc9DA;e%}=wje8_!x8yZ45Go7=w+W--cx%Gq7ebGn5&^ z3}Xg2Lz^MZunmYlSOdHP83qeOgu%m*UtnJlU*I0_dDuLh9mWoChULSK!@k3?u#-T` z4*+fRh9QB^VFtbh55vH=aoebEntZq&tPk}C*@x)E(8K!Reds=9A66D7i`quWBDM+J zm~Fak_%?nUy-mA~+$L^gx9RPG^6CPa^#t-72~KTr{(-Z=y}(~^GPs9q&>y@QxQA@T z9~iO;OnNZ18`qxw>U zg2{o>?je3Vwk1dwSoh!^a{vqkPziDn@DDpy$DQoyLvhm}3HXwO13klKJcFDdD-|Io zejfYFcSCK0?7<(H1n%Ykf}uX!{RK;bd#Ejc(nI=w z?i(!YOLYUq$jAWv3`h@>!k~UZ2Ou@{W&!LU@SG%SNHB@&PI94!__+mXkb;j21|Tm3$ZHF(?BMDPt`L9znG?$LJ1@Zf$#eF1-s0bR{^W(qjiQ051B3Mf9MoT^ z?=;|q{Fy8`A)62Jodg}nNmAe+L5gsKbV5vw0MQ0Y52b}pi05+Q|08AnLVSPOWI+N0 zu)=_j;Dq`i2f__G1vy1IB{^j|6**NoH92*8IeB?`1$jkzC3$6e6?s*8HF{gRMpkw)a2C^)D+c})Rfg!)Kt~f)YR2M6V*ZW>Y!+KkWC#t`kM}lpkGSh zgkq6yAV*Mtm4N_3F)x%3xY8YZLG*Sf`vg&f14Ag_{!dzNz*w%oU={!i0yvQD=RqMs zIv)I6y#5H{FN{0zz(GEquD}($L-GiM7g7}Qm&~Pu>wn6i___Tqp&dMjnZ zyHisTi45cul4_7?2Y^k1JW%ZL8C;=0{f==cZcsq*JL^s}Lj0j8f5NMs_=bgsVMnKe zs3C!%p}(s8-BvMik{gv0MDn8sgG|A$$4G9#fO-0X@B&0i|4eEK%7J2jM{tGWEGz@# zj*i0re?CA{{x1*2|M5Wn>p(%rUp4+W?E{gZtPcomBYx>$F)hQ6zi|P1p!YL}a2Tu~ zJcDHM5AF!KhiD2rA|oT?=n@h9$C{CVIfIx(Cb=FX2mUgDAnw#8c>;k90;~`6bfb_+ zVo;VKk~k!W&dxB{Dkz@_irWHL$hQg6G3@AoB)DP#EDx@beRTn^D1M~fT9BlX#Go5- zQrIyN8$+yzLP}@QUXYd!r1b(7A>BoS#zD63zoiZF^MqcDWc>p{SWgCT4!>yp1LTAJBc#JnEbs@0;_n^0V!-D% zI0}IV+o<4hcmx4#s7r@pfHNYP(9Dc1a8?96f|Hg9&5PrQ3&Xuo-iRt>4VbyqhZsN% z(Y(M9BSsKo@G0~(Vji`C_(WPpZ6dxQzr$%nHMLF6G71Zej-SrXyIk9pT!X>lRkXBi z*ZK!gEF3DTwstYsE357)PcfZLNiRgv(lIiL%PVQ<8yFdznz>ON?lf@oU~vQ@3%iQC z#KA2j4Vc~Q`@Jd!!GMTwymtsUyzX%6|=ciTymwf{MxOimWLQ3 z4J)sPj=n`%dGFI=EGL)XE^VFp4@=vfT_}>!E)g+BH4Q`KgXUH?w)Te|jyk(i+`WUs zPsF5_UazWd@2{-#qqb%r6+DhcqNI`TNVu#_Y&C~(q>7}& z5isglLYxXGRujo%3#V70O}OA5LX3TwdeD_NQI3Hv<9hu5lDqNhSP7H^MucEM5JNM^ zS0AAqK&fLHbs>?+-^3*jOW-fh$0^doc`pYDNG=p2PM4oi0}YSsmx!N>{UUx4 zML;1E82262itX5qfun5D+)9WzIw_PJjV&RzQjM2Z3Wdia=rFP86JDYik+jG#loN&q z1*fM$se#Uk;RH?NtZ8^bTU2m#ASoUj+p~+1h=IY8Xfy@`w&TWO@r(o>A}0+OEdw15 zJ&FOz#KeqegR`SJ;G9Sa<}A-^I{E_#zyFYt)p2lidXt!zo|#oq)70GA_4vue?AvV^YDb_| zH8i!1j2#ox0rGZJbJvq+eY0<2|0V94kh~vtqan_nQ8!9O_kY}?fA@DVJIoVI|Q91Su4d4Wv9#3dMyX zB4cj?l&ri7GPV@A8%dAcja9=*qT{z2IdHO!(ntY%0s7c8sQ7$NBI~Ifv@BW^NE!z| zwpB2gCU%&M1|7SNj-8}gy@XW7$2qXXHsWG?&;$-mBmtv_Gr-Ycf{A>{!>B{}*aQw9 z0vrAyDmE2!qm+gnC0~My8xz6OpwY2q3~?J+I7tEnuxY5+RwOr)p7wvW;lFLUlN&jh z49s{t3&YO)mN~H5(0kV(*a6%_{zFb)K~YIrMO96m?BWW2i1_^p=D*%2DA4N&O;qCfXV1$XpC#Vkhy_kevCK zq>3daT4@(t%hAi>}*fYBfUoCU!KJTFKM%ErO5 zNCJWn>=XtEQ$_G_F;F}x6^RCtj|C%8P^nOpK&1!>=0T`~_Wh}a7j6g#+e;wfa2(tM zj=<93T;K>i5o?6t2Iaxws&sHrBbo>o#>3rFa13Y^f)jy4GN8crY6uLR9u8=acqkHuz{4@fSAYnhwOFV`1P(($z~%VmQE~uA!^QA4U|0bp zK&k;G$cWUyArJ*fI4v9tRft4%>B8U-1YpQCxHAcc@kGE-a4_@N9DEc5U4(NY(C~Z& z7ZWX91jk8~LCS%?ArPYQy@2uvFmC_{mxU{WQV|F==rCteUe0G5ITOhv=+ zaKr}SJdg#A61(Xtr0Tlq618IY*g2MoJmC$JD z4h}vM0aFBn>!2*abC?W*9R_HCLgR1gMy{y!FNuDosShRn8bW9%b-6IIHLUA~3+?^1v)|_x>T&fvqIk zLGOCP)o<78m3<0o_Y6O7ex>`h<>rjfON9`JN&P;;! zeqSGc?zQN-_s6K8lEEpDTm6mBV?olWeQ$Y@icSX!*B?jGzTO>xHovZi5M^K>wkDHk z9`C9@W>=R)CE)z!gEhhG`(M1P3LCt{D)I`Iz&0MlM0A}=)O5?y_VT5@cGxSD*he>e zX&av{S_vc66k<>8%cfr*yn;Jg3(8Uzb|b_qH~$zeulkUbr$m9GyK-^04Ejn&s-G*@{Q=fQMa6*=tO_ z&Hn=|%1*Hs57K2+E3}ZCW*(I>m_hYO@aCmPC2gIh1dYbjbJh*PkvNmEx_OI8xo= zc0pv-^~1$0ULk~RUql54e^Ez;?tPL6Hjvp4hK@IG%Vmjdd{Ze|b6k!44$X!1!7SR= z7jGMu#-ju#UlE4}pC|8mGqkc<_xhM$!WjQkw$`{KB6mMehgDO<_BU|M9mbvV_oCl^ zqKR>LAi^293n>|%l`fmlx4p^weLigAZwSSo25CpE6Huldbx3uxh)4MsHs^km{hmgz z!u+(2y#^}B4-b#J6DM_-HD9V)Rkl=g7B)}B^wsWP+^(17S-p2JwWH~YTy~vR*_GSE zXqEn^Y|O}0ak~lYHjhyunMn7#Mq~0r1ott=97(E#YAf+}Njl8~?n(6Fv?Z{H3pn)T zxjExqR1_XqNlT(Secbfd!%ifZAGUpzr)P#s9ql%1?ifg2So+p8x;{f=R)5nZy0Si9 zI-#(Sr{JUoQ-?8w(5TgO|Ml=Jy(O~ENTm$RT)pZ@eF0BiVgmY50e3t{1?97Pz3XdiFs_`(bsTfSwVja5zto=XAQM}i`tVuNA z>5#u1lF5^zRE$0Qy`_k~DJ_vMVxnS~+EVTA?TEFYR>K9a6WpD8yCo*<(Pk0L`=VVI zPfDA0bn#fff~T_P=r+@(B`gS)b&RUD(RLQA9b9h~$SBVvJj+XC#>09Y4CP1kPrH2c zdtx;mX6@MA#OhOgWmqTeR6bo^rg_201Y}AF_MCZ8(g$1oF;Bcu&+@>lP)N z9kOSlW8)NJr62QG6TYA)q*d;{MVUK17qtuDW9O#UZ+fLN6aJo!in0OhrDm_~aYHhL7K|TJ?(?M1MaPMtSo&LPxg2P*^3H8_%mO!Gv0Eu{uzf zW@za@L5YxGigy0FHaFkavGp@5v;T!waZm3zmDGf0Z07lJ>y{dIO8K<|1qqsjiUPsM z-|F$FdMX*r9G9HveAW$~>WuoHq3a0V%XD}RA*A>2zQ51?SiP{9`$tsrF>})Co%OJ* ziHY1TT&a2iN16{GD_98IJ38ufn9od_)hSwq=AtyW=xZLBHWRGiu9bX3@mZJ3(*suZ zS!|u_ukF^C9_EyHEWOAZ{k+(Mt-O}T)8IWJe^FvdBI#p<_mi83{?Do0j+-(P2Gi3j z6Yj;>&KOtg1#3adn#tR>T106_1-D0LqMS%^k$7We=j_*_i7k!Zwc+Di3w{S7zzy)9G{3?jp79bHXFzV*=0O%L|g z_kK-F9GNm^%Bvy@rQPZFFTRwb*PLkJD1^lNsBt~iVUl{M{W1-GWQ(mr7F3utDzJDVFKc0(Bt?gDHljT5 zs$olpJ9lQOs6^_$sg|DAlC=K!E)!erLQ8X__oiD|e)N~q>LjF!MAv5O+!gNk-(>Ca z)HIme6B)L3q~4n{zJ55mQx$LZDOAnyOHC!dak+@8M6p*VHE>Hf8`6JOuMW81P$KeB zs{vyC8Xo(;qX$zPm4-@UGeb98Fw&$nPD9e3;{E@3;@&?C7y(ee)sxs4d!58K-ks8L#gFy@|hj+6I z$^B%BOmAc}nT=$hGB8mak;+g{&w8wRcax~G`_n!hVq&tkrAGU1iaA2dp8co_^~6P0 zl?P+W@Cpv4#5Ozmx)-@}@4TNYyoNI>o_*^rT%J*}>!UjB2>9l3-D8Gat^6wSH#Fj``)%Se~wQLn9C$M@a2 z4=%?)zn{Cn*nX|bq^GDe190kb_X;ENaWHmY?_S^Jr(Dn5`=sw8p6TXQWrqFmJu96q6xBS(2g1McsQ#qTDT8jE^MHl8Vt6eOK_;yK{T3^774m)SuEPvs| zz}k5^<&-q6(}6Qbr)kqYPxobfxD=cE>dT(f@kXeWdZDgUr`*mbw9(ne&sFoq4vdY* zS@&iqx|UfaX>hQgqz!zLT-^VrBEN}r?HS7b`d7cw8&To!D-SSn2Cs zO1bQN#j;SFkWxLJhN}<7H;dQyYLr~xin?+szx8r|(8Kz9PR#Ara@{*APvaYCUOlbV ztf8-Sp*O0rp`5NhCOFdUWXRft`E1oFe=+m!?bL~ugejhTV@D6SeN-xFh4i1|(b9Oj z8-M*%RXvkkzDtI}@`UQRllQK>Y9p7H#BO({n&D2kP2HoyPr^qU+lGx_zw}yF44a&? zWpK4Gvg0)WCUK}6}mz9H2`g=pEPGLXC zNzsKqu00t+mv54~svqx<#A#P46d&nM=cxD4B=o~h)_SU$%hEV~$bYkZDCuPWTdjeU z$&}ku8m|wVW%TKYDTXC8%)Xlmv$v#u`BC>+o5g0yrsVR&qI^RMIl;qbc7<;~Q;lM> zaT||LlG?T=Rcy&-9K@wj@g=s-lY@MMc?UKsxG~9 ziiU@)`c+)ah=+P+_@LxEzh5@;i-@NC+4BjfbYwF>+GmSfJMX(KkKLr1yk^4J)VEUh zVKVDyN~4T{2m1wUgum#AX2a!x3E6{>FNGMg&)OgzFM4hvYn0Bkhj-Jz-N@t`6=?_ z?kCgwmTAW!SpPk-rWLq6)pv-z&R^eR&_=rs8+|mK7eydfqgUcU%0~RmSAw4Z+io=%0=< zWL;~$DPh3KsU{S9H8#Y(>m>s`()nj)U*LtuK~Dr%O6~UNaV%A+-`eUu%BYX|WD@F{ zvhplVWZ(FL-lm&HgzQ%(zHGg*8WF3u;q&hoyS~<)DbXjo&@FRfJr9<&T3p!uN<}nL z`CL|>`A->{b9b#peoQPBG6guE31gd9dM2Y%al0$6fWVXn5?;ii>a0u?iDaidFn9c+$(++><*3#-gp&iOce%uIL_l=iF5e*fl|y;-#zu?Sp&A&zi4oEc*&&m`h}r z$+0IyGF(-(@-LVX@1L}SRlh;W-oJkvQ}V+t3f??qLr#8}qNfyGEPyW^i!)_en-1!9 zt62##8R)&L8gQDGkMRz=LmpNC-a?&{xf1=NF>`NnqdLbi&7jM;#97=rt?i6#vF*S`Dn+4syO!l5avWE4@OPd#6i^b+1 z%op@s8SCj|Ry#fyguZd{`5x~2x)`Eo_S0tFwPkM&tAP z?A}wcn^iF912z7+gTb3m2hVpn{HVK3mU@g3xj5y*-8VJ(HRf^c!>T%_1pf1Zt@H+# zG;LG`c`9pG=;ZFz19UeoKW>fhSohm>shb!;q(7q-+#M~v2VQyfjR9X+{PC>nxf;Q9 z$!At)M0)l-G5BoB=DzQZ4kkiDzf3#crweKIJuhx%@Nj~!J$TL zbi;IeMeiswh++nc>D)i~2Y20QcPz{6k4qMIzBb2MJ!o(g_Fcqvz$bK?tj{)HqjTp1 zoNc&_@yTbFl9@d5fO9H-_p5EK@`^P3$-8|u!)~Z@ z|68|{qAzc6oh(8P&0l`8w}C|Q8sV*H2s?~cXZK)ztNP+k4J`dMbK0a{20&C!?K(W9YY1UQ$Vn zO}4vi2{HQKhhjET7HxBHt4k@brJ!w|of{2_5?^N3`fbgms7(a$O-!B8UFc z=iB$BCx>FUt_D9cIm7bpBKN*M_2g{GVj< z_>&15FKTU9E+B@H+5@TGXRq*?D*NwAO4Hq6AePN-l{}sCLP+5`-{;t*B`d2@;Vl7^ z%l4~`o>Ltx!TOAbZSP&j)lI8oS5?qAvDw4Ry;`Mi#sy(&aislP7n5?xc9L@&1L?{Y zTt`lBzyE1{g=8+GTDGy_?Q7EM=fA+F&}`sVrZv;>?ei^cBnmeq(pOeiYlIB>6yL1> zzKAcc!Mb`@XpmJkhO<3Me1T#cBGe;1ebw*FJ-^{cjX^;yvyHFa{k6mEr8ayB_VkZO zQ4c+CoZZtA-kTC>_&8_sLatN5B7Ag6!;uMoia1kx#7>ML{-CnzTM>o*$@%bF&aOHh zZ%gI7jdVZ1;L>R#-JfHe{O6O3VWTqtR zEu~E(_cPrGkBf6NRG;)rR1s`?xpkO+9k)Gx?SVz*ZvFKW&glnDDA|jJ>h}9T+?sf! zv%;#HRz90`^tK9!h69yh<&Gph{xZR)M6l5O0xVZm310zENU znakHgnuKY~=9c!dx%19-Y784TRGNQuTfJao{Uv$%*E-YiiJ^AXJ}mHY6Ck%#j@hwk9p>LF`g*HcBCR%!8$u>(eHtNMMm z0-UIXY_km!U1sf53MU(-r(Xr-4ovvTA>ywV`m+gXUNK$>VSd8Z-<~%RU5l`LMLv4b z)#6B8S3K7E*m8F7Q=vXAN9MWD_x5yZG0Ryr-LkG5#ic2oOzs43X9`BowaQC7O zz4b8WjU@SI>y#{yHdAV!%c$_Z&rWfE*K^d5R;64_Tic_s?eQ>QN|Sz1q3_Rm6M1DL zovU>pK70+g9{)jEp)amhr@Xl$A{r9DN9mRRVZI3p6KK3@LqO_q7}J%yjT{PXVgvqMofoibzHlU?tlhH6Z5OTRV?RHgH_)VoXJS{YN5 zWAB(LoW8$Ul_~y``~1=9-J#t}r%tpPv3l5rm-vt-WGzo}UpTaU53`r!OsyeR;)t5) z_>!V|WT1HCfiA(mkA^(Q#D1{zr;};Fw7tN@a{4{Lv48C4`1Jk`rT(?YdqYornXP;L zBb(KF?zr&ON1Ay2rG3Ho@)QS2XI{%29Fu+Aod=OeG~l?fPp`v`uXIf(GF{u2W_@i2Fw;LxrY^UgAt^|q9Z z{f%_AhEEsq_Uhz#d0kGAKf;!@u~pD)sU}sutFY$!(<*dH6aK@%>bCt@(xJ|F-(vwi zT+g;Q?hz0Cd~$1eR`8<&R^SJ_Sm;Zv9+`}2QyhC66%RjD`!(Dq}4@Vw_xfs7Lvido^uraC;?dB_w z?^u?cUfV~?vw6ZNe>R%cKxKx$5^IMVEolwTv?IjKt>jba+J*cg+;5prT;V)Yflk!Z zQ<5`Q$%6F15O%%y1a{Y+8Ei)SH#*})H@YdqlXz+uC%%(V&;9nfB)1uVH&1(}EsvL@ z8Eci@04w@}0#hpMO(xQ)qRMna&ZVn`%Q%3Tn}zg%U@r zU=r)DGt$RDo{%08a1&;c{3hHw&nYDDc~a=YRa=+PuiY-=%90eT=6cH4u$w-+Pbm20 z)eLxZ>X>=gt<6MaOPq*MpDT>Ma|{;EQ_2}OT6r=o$i>b7$A@qJwxknQPm^Y>Jd9x0 z3y%t|_YhAyo`iEcNFqx| zp!dQ>lz(Y*)`$V*gWwi z`0|KC^zM;@+R1+Q-Fp4^mb=zOZ0y(0oNHc3vhP~2c(r@WXW;Ty7q8woqwL9VGfqJZ z50XDFcpB3!%{@H3bP$&^mx5rQ6MD8jeN)$K8gr((_7)-uitmltNzo0lv;8pduV`4 z-Q5ZPA!A9JR{N4Ni^}8q7}eu%zb2gy7->EIirevwb?$>RKMzq0Ct_C$gAX#D``&Tp zoCRI(*&Gz{takT8evLtBK6mQM?O0jrZHbvPbyt0v>gY>|O;2v*HaUBQ-dkE+xTn8s zUlrxYSXG~#`Ze9w@@ww`TT5iaNhL|QACwU^9LtJ7pP4tg&NM&ozA|-efjZS89J+{= zSy;UFhWJU)C->8-V)Y-_BFcXt>i2z5(;WLQ`sKmun=QvxL;2Q?M{T4H-=Om0&%^4& z4qRgc&vW(-MA$pNY_WXslC6+5d5)oVQgNK=DKq-a)4PwUPgM4=Jh^y!;lZJ}&R`571Qnrt{ zzb7B*6%DtIyDz=7atzJWqKhdupJ%q zlBU9U^M$IfCFhXW>wIIcup?HkwH8lZIWEb#fyYns>_FsS-iK0 zSneyTGMnD3V4e-A<`OtA!*#mhDKARhinnrOn?7BNOfNhYkGtW=gM(j2!yjI|0QYkX z$4tMQ!yJI70l@aM9A{4!`z+JBJ58O&;)A1^e!g*P1Xs{wU$Ps=<(JJz?p(Q9m@1R| zy3i(`Wld?$Zr*Gq>s3ehK;PZTfIaOu6>OWEzj&#{-;OrPJ;5xsqK&ni`>r`Q*(h7m z?K#E44%8pE)cr~Q=6J3QrDok&Xw2VCf7I<}mm<15Ni=#=mwRmv zNq=eui_DtX$G-SDPSS0;Lx+E!Xc?VgXn@X1i#&DfncrFye~B^0pP}1^h-7G$RY!HI{ZKQ6w@E3o`OTz0 zmj1FbvCtDWe`6B6>{9EVBT&YhFnj)NQ+fLPTcvTX4)eJj2Lvl}1IhP3QLgo8z~4Vy zXOr2CkoEn5P-_utp5In&U)CU+JdWU3nWzr5Yg%)cx}|csBqzjlEaAn2E6aH!FXye^ z+e|QQ%RC;23l~Wk=3a^6>-dAuxvnMrjnB^~*NtATv)`jR))bf{@&mAl*xQ;JcfZK4SBgxT z1W?vh+>5)Loo2?{4JB$yq&%;VY2G|%hgeFJV*L`0J_XhIg+# zQml0U-kQH_sWWz+6y?;Qgf$Grr3T3JthjFmoJBNK-m^v@a+lIF;nlpJ+|3PqhgTBpYAu`K28FEBG% zmO7 zVU14P$E)(PxO!eBlgAmFKD@Q6%wLTs%Shi3vC%*oy+rPJyuy+$*Wv$E_^tn_n0s32yta0u2OXPolo}*iz7nsHO}>B3Fyn*G(LI1YNc8t^L$_@&;1A` z!RyVh1Q^fA@kg3Yx|6fC>`*GM^ zW}0w@C$>?+0r_-EQ zZCX|@Xh-wD*>!n)+|-$2a>MTXbYU)wPiNs<%Ss1=1*`WoHM)Y1jS})fjIIGGSs%N= zIRY~y zMfD#w@!y$i)q6M3SkkdCp1N^=WX4lw$o^>OJH?9<**CN>=BBU?pQ8ZDgOxz&;5${buYG8-H$tnAfKe@@3vDUne} zkf|DS_bGcNc~73uojex(eIY^jtc1_g9)U}1#WR7++(YM{`Ll&Kct6QWf#Hs)HtU?d z-MmDc}?TDtzN?WNwsA+E{*+Bd3Z?xdFoU$ZL)-gz-jyoJ|R z?&=v(Iq^dS$EN)v$wXPWS>#Jk6oUiv&K2Q9xDt|1_lk?UU*E@89*Yhr7Qy7aufQz2~M~z}_eI3kj^6;`-AMnv%3ljwjkzz1eqC zKZ;|M|&f7FDALS-zBGC6lM<_8J%tJUyAd zOPR&=Tk{LEi-!(o>>Wv?vHeimo8m%wx)l3^R*p_fFHVEk##Oa)T)5F^ND@$0phDse?e}{&lxUf zp2)GbtmY+7!(rL3E_Rt>;q*^2*UfyrGTm?0^OxUzzNPI$6SRGO^R$nbDEgMS-n`O{ zLeCAc)0X24*5ka33fhU{AFK6+U$=(OBINGAbsHq@YIc||tMur04_L%|uoKUgcPCkp zcc17enOQ>?Co!IPK3sHU#;i0@=&VGY;OB0MH{u5G)$)eSH;oQwewA&+sV}QMZhA{Q z6{(RRaNL31*qpEwaUzWU-Mwq)BwL;xuZ?2d2--$HJFN3c_k#Al%ZQ&YQV-TF1%E20 z-PBQdZ(6ph)b0@=@ZGfLXGlhMK|Y)Re$J6Qb<_JoeW*!c1Te|w1@g60yW09X{~hIx z2`rIAN__lQs4{7Y^*pT2;F5*oZZeP>f zSGDcd_T}GC6oiMgoa4*ib?83ltn~`}XpYP|j$FgzaW?rIy2J6$JlPy|ud!=hmLHjw zT4rPzavkV9e7V1(QIjE5GssM%o_aW0JQIS6< z$!3dIKQ*Y=U#g;{dQH^1Cb#ozfbjvfRVRN&h1j3{igP?hYwB62l%INWZ!pb{tp{WF zD&060-_TljBaE!&6^fl)yYtdQp?SfH{U8W~zQ}$LUc`0zSSUee~xOgaibwS6+TWsph^&11-LQ$dShvmV-uoz7Yk|FF$!PHf)rHsXb;Gdy1m`sg|OXLOB zcXXv$0dXb-^e1>#A>VWP#ekxmz#ktuXtGQ0<+}z6P0{EiA5R$OqyKQeIfut^*Gv#9 zHXLz*h2T3$#dlA=OlRO6V`F~D(cB8 zRqlnGS+N|^G+)9LDts`vwD>%y3zE($Vpc!LT=9&^)8u6vD@>ll!B$fCUG+k#h_iS{ zp2@gyjcIdzw!!r{tt>l>Uh0|IY?kk*Cu^L)>F#36rHM_UOz{0AWHnQgjteoqQ%z3g zddu#aetefJ2Q9g+?g|H&dj~Tr^Mwuqn>Y$^qu?R z;0k@2_E%(@Pff;;(Nw;%d-pQWY+bJp?D`?VV~@U?YV6Tf6T`zUea`-(Vb2t8)k2$& zuYRg-ebH3aIq31$JZwo4Q8&B2cDBk;6znI@b+}<@Vs@Z;P>lE#PlPM)Gv`IYPph&(! zceXuj-S5QELf6{f&>!kHzF5SEfiFIe@!Z)V_uQ~OkIgbiY3=Tges$XP-K+eK|P(rCqD1L*oiIFO+A)+djRM zLvIZ)dt&nasFRHLNQ`Xq zVCXLw=REw>-%*p}c2BqV4IieAx;}f#!M;wO7Z!yKoGd>!zVX~0H{5EK(%)JTDqG`x z`1ybtyXID`Q~c@bWo=D2vz+MZJ84^?{?&?1oU*#j=Zo$qJ=~{kRWNzP7-M*(>)rKe~y- z44*5no3C9l%Q>g-!|5i!q34QUOmy0|I$yKGRi`W*-Z14^(MxW1cdXL%%uBBe$%bB> zdbRn+&fQMN9^E;$`_|pr*H-ZT zR;x=-o|S!7i!3LnPap80V8cgM&UzOa+4lA3Fprn}%Zx79`gNlw(*t+U@t9oUNs{x= zRi0f^w=WU>r)mARt;2U8s?)V?zL%ALTH5=OTVKC=eY+gG_jr)&=#q8Ev|fATT-d(Z zt;V)#F!#^7pYqjkRj!ygcBfC%{M~L8KXVo8FRS~Z%786J7tHGw98>Vsw9mT_`i;Bi zKPykIvG0Bx?&1^BF{E_$Qs-aHPsu;KN0T`LpJw)~KFam=(*oyenSa;r>eOJvy)KoH zL=0J+W95DQ@-h|QClt6nGIU1W!sFVtI$!y>5&lm{jq{z9wcV27J+)(B1>8*W>3Zpv z$7OrX*_`?{x(S+`Tpr=v#p*|D<9Crzyfiz5Rk zE4N(_eENIN*FCZhE;FS6>uvJI5z(iPEW6aqvz~LdTrK)eJ9T+!hlO7GuJj##=)szI zH)4CK>MqE)Vo<4lb-N5a5UfloaN$#x_-qY+Sw3WT?h@VS{rK_YfX)-D?Yr z*=`O~8|{Sp%UTx8d$sRdZAxl|)KV4wtIpgob77OmKb@|nZPfVQ(lTQ@7JZ&?_=&-B zIhuC-aAa7uiV615mM-t~;M}=uzJ9Krokwh#GPIC(UWhKZ#K`1HkM30R(bZY>s#>i- z#;w2AFm%B9(NB}x7OZ?zmm_=hX7kDAmzx^P+g)3o;IQiKi>tLg>i_a${kE8B-T0lk z1Krf7$@^WJmuYzB>g&9JtRDH3M~$%75icM7v0YuI#^LNscX|!&YaGz>(Az~9^Q~U+ zb9A|Wb++Bf{nokL$!?e0;14dI-aAZLXm!x+S-mec*w=G;-Nwu6-x#s~O_wYMBM0r+ zaIav^xSqS;$+r(MT=1S(_FC7gP=A@KhuzNG>n3+Q6Mef$*sq3}Gk@Dss<69HN|P*I zhVMOhtfOL!{N6~_v|3TwZY;lbtXG2f(j7;Z{ZKZ?^FAH4iStiw*nYy%wO)=%=iK+) zdNnRjqECZ_U6T&1Dib_=MaRl{49B1TUdSml^w5NHfvGcAA1|5JXxHq($>EbivtOU0 z)O8Hnw0^R(zlYcRhi8kIpZR+J8$}uYjDwROHV6q^*SJ`f>T@?fc>ii(SpTvQMo#K5 zCtt2XE{85W-rKiJ)v%?Gl}lY3^zaww;9>o?9SeUF{dv^v2K_?aXK$>(N9lXF-KUrv z#Rom9xvopGHhpd^8S}I*>3^?3v(8?%U-#$b5~}|B$FIyAr&~B=Q}eyayMI2RsaB{% z9_>|3q+RQrkcEjYhv31r(<-5A`!=B_T!#C$Wo3Ho! zLIdTky>4FW9Y1H~)PeRLi#}>oe&6lG@9qxWU~(Q+rs1k4LDhqro_VY-vufsulD-%E zs86h((IX^rhg*63E{?zKdw2NU#$1O2b(NP?&GXyxWly8(ex5$OQl$Ta(85*yJDcA; zn0Rln%bdRLy;Gfhjz6EZA>j8Fdlz45&41F| zas2ex+Xp#TnDb{+hk;LT&p2Pq^sGwx0#&?Pbn|=e_vyvEtqq6g+H|FJe6CjNQl zn6({0XF0h2#e{-$COqyjaBj~s;=`+}Y<)sPqy;YN^`GFEws*y>{TGM(gztE4S{}`+je^`<$sB zQ@hsL_p0>O!D4 zu;b=4IbGjYkG%i>=Y@V%PMu#^yS2x#jy`>&+pMoOEq8EGuRLd`g(j`} zb6u}WMe=NK*Y5p>qhqd|-;`4KQaQ!Q-~oMO8s3=SLiy7lD=WxzJKa>P?mc1yz-i@;8_}ny%41q|+xRhmuCQ0bPW1dS zW&4EL(fw+L*k!pKKKJ*}^={28T=9qOd5-SYygv~B>y<^t4%c1rtIM>g%RlteHeMX+ zQtRwG=cOf_CcOx~_fzbMy`MZk7kfFj-pKm*o5uJiPHNH0_0Gz!lLr>>v&i*8@0!2e z$kk|(JnsedigP=&HM^`lqTkke*SL*)0&5kSR{ye(+wIRSd;M6r!K2n2$Bru=X3n14 z^Ok<(gvT$Bbz7Zvr)mC=QHypz^=x#?c>0-Lt|cB>Mh_?#`_BDnWcN=$ysI;MZu0z) zZ8NTx>@>B(-4~roOz;fb(!6Tb{dLdP>aetP0qvQc*o5F3%rxZH5zsQ~5JDX3BYBM47`r*(=qm?5(D|NUT zSL~OXQ)jNIHcjsTBz8-!;a-^AjPQ3#odflm4G3$7L<-M>iGd^|NYL|16-}cyt z^?x2xaewx_FN2!AD6{$V2>+wcx=pXxw(m6G6F&wWOL$arlg9p2iz{d5FFjtfZ|B4Y zo<-Elj$OLCZvW-R9SbW{izRk>dTjNQc5UN)?0?;Ke%qld%T`oy4$W58rS-_GXP5gf z+Ip@?P(+341Nwb9I%S^B`1t662nVlWmkSjd-r((l{_n@Hk>8I_NNJU_+2EKhcI1v8 zPOn!DzM3`1jNd%Q_jG+z%_VX~sT*62b1L1QG44|Ri0TX56fW6(-0iuoOm`f%b}Y1W zw{PUi(vPO++*sPPVV75rl8eun`rx=4R`S9S-a{b?xZ79FHcVU`8stxOy};ssj+J8h%$}Z8YhHzFt~3vk6d^1{_BM$D`(C3T7So_z=lHW zHP<2+jCGuzf12BjK?Bc3)O6Zjxb)L*wVN!4o;R4%{$szpnG5`hPTDQ7>q9{DC)r3+#@$HoNG% zw(|NG^DoD2%CjeW^WZW&f@+2QUa{PkEJuIr-b!gUkGfp@!;K|nv)Ar2$+b#nmp`8^ z{bl;lOY`Ep7Rmc$_VN~vja$F&U%tYf$?wi}9+!OnP*gFW9|{MC>fimi?DvDC`w#I6 z8D6(tt(5Y+{*+Hy{8Z-AqUwjuRlwHwidFS|J>g4ttw<^}^{5BqOsK2Pl_5B5FpFS|` ztb4usKdbzG{I+eGc0I{=U-bpY`UW4~)MH8Z7QgK%5EK}8X!CRLRY(6k(5qtm++{B} zO?{z!F>F?s8@dKdIwZ7Qy7#Bao%`MST%>!J5|_?SxZ8SopSsKI$=jW)@L)`Zsjmk6 zWXa$D{Dz21%NCD%5K3EY$|g>XSshto-td)Z+BPra*`Qr%-;k0nuYGqSA?a2b5?VWr#U)e92?j<(t}=GN|z+buRO zE4a3J&BQ{#kKI|lNrCe-ZZ*pD!f-A=d{DP7y}VwWzMyt3Q{v6^hNGR!k6bWy=bws| zm)y;Dn@5&EmAb;Qt8U+la#L%y-R9gnx#{|uXD$|fIWXnz4?aDja}Ant_S$k6zfH~q zwzW)oT&`4Dp}EfnjQ1FRcB3|~OYvmKSHB0HxYXq8;)F^iI*(kqp-QP%*r{jt#L4s2 zhdUg8)1hLN^ON^2r}m%t=dB?z1q?Z5>jup~Jj=^>Ue$8#nr+{9uxQSvhKF@7-ZKBV zGJijP-BH>1oT_*Ee%CeY8xJ4bux73TWv1O;_UEsOHAi~N_QXG#7?Q_7^7iTOFB7-y zKb);~vkrr6-Cj}s<%wELW=6K`8ae0MDR zemf_%oqJYY{l%Xyy&DtTd~VUUB`!dZt@Ip9U|=|Guu#?bKrD z#rRi(HtEUSN4owVgLjYH*8D-? za>bgI2s(0P!`1oGjkVg~Ka%A)7RZtdR-QOv{hku-XNGL>tuk`z-sX9nf4=i@eZ=v8 zMcpDiF1?yNxB0A!ajBtsKI&?3I_P?)M!V@-r!Fqvb61s}dA%Aoy7Bn*xeTjd}?Bdd1#+T+_VM$ad?Nu{vs;wSRfRr+f`PAIWxvV6nwTCZdd z*&`;#?#!*7(qLHfr>6-)RS(^{+C_VP+va=0<*o<%5k?5zhYo9H- z;b*_#e8z4&G&f(IJLcJ_z)jP+grL{u$Nr+(d8hC+SMO?0(;H2%le#9?D&3(i;g_;b zJlQsQ;M@WarbyJ!2k-G{+@-@8_K zJ3W0##19MhjCfu-q$*jnDepx-OQg;+|BaWZe|aeor$j33Fl6ItuQTS>3R9x z%qd7G!@05J4=ev$vHZBMcU1n5F6K{il>f84=>kjdYPPed^1GQo;@-&|T+G;g2j!<7 zh^hRp=Fj$SX1S}I8GmrXEPLu^cC>Rdr@AUkLwWgAsr+u{50w9Bce8zprTjU0`Ed{B zDA#+se{SyPPbnz>CxxjIORq57y|$F!g|5%h-Ryw-LFK>A@>iHYzqU8g`Cqc~yP2^m zjww~9FdeWdzpFU~*Dcon8?pR$?q*lq8`%e5|L&#|EIsP~GnL=f{1Nv>tp67*f7Czj zo&2Ml*`3PoQz9=vtAAJX2g?7mo7vvpQvU3`{8axA?&f5)BU!Q=HXuX! z9TcXjEWNwg&XvmVZg!^o|H;klfcl{F7h?Iln?JiM%yQiK=dAp$W+%DAocdH@+GtaL zg*gTHjnzNugW4aq0!nZHsF!z?zccDT)l&a|vh=9`PgH(|`6G0L)qg70zrvi0<&XOR zg!bp=X2zd}G0XBP%#LV}scs6>6kdK-{|fU5%Kx*Y$)bPm=EuDJXhU+;>wB~tnTMn4 z6X}nm!qkSPcQDyOZ>an(R6kCRCZ>NV|9zIfgXy!(Qva`5`4wiT6ifY|w<*6={}txf z(8F~7cQd=99@6#S-Bg67|FZr+WBFV4|AWHpM%Vw=s{bDprZ`@Hss4Yq-2ZI6{7nDd z%*kjsvUkw`&!qnflQ&Bb{r^bicQZRv`y>5Fd!+g=!19Owe?nx3UJBDH zoASGxQ_ybG^fhb`5q+Pi|J1KB{da)=Co9YfmcP5% z5&c1Gvcfc$m!H+YyZHm<|5;(SceRxN5idW}e>d}c^f5Ap!u-kAqW>*edIxL$yO~|+ z`WzKz2j~Np|1K*(uYZO4r8R#idrSSFuqi+EAMG|>{}tvpV)^ZyO|1VH^uG*CFX%t? zB3=JoVFjuDr2i6-K17xi`j5Iwbyk=L@bYv0r~FC(KUvD3gO{J`-@)0$`v3RP z|4$bEuglV-|8t}IcQ#r2f9OB-k;;FQ<&XZ)joQD${G62^`k!hk|4y6oJDDu@5B;Ds8e$sy@XA|rHUt0CwNnz^3(mPn|-`T|ae?k8rvi#HQ z{}n60lgSDF0s4RZ-J!49e|Ph1YQJB!|L&&3Ed7`C-_>Mm|J}`Q)PCQj+kY)Dzf}L3 z{dYBe!~WYBW9gy)AE^E_+W-9g{(r#zf9r1ckaPXV{eP=4Ew?E@>_7UAbp3ZXzZJ`$ ziT%IC(hK(gv#tKy*#CF#X8aLav+M=zKiYWeJB4W^FF&h)*ni5O?Egn<|E{J??Y}$h zKl&iD|A8z$uYXr_n*B$ANbUcQP5rx@U$XMU{(rW#|06c#m)d`K^Bb}JXuH@o)W-go zV(A6_x3T}OCR_XOZg!{gXHU2PNxb~5{ssH*U@5nJ z{%6$x+$?{q{a3h~vtUdhD`eGwg~Bw0mp@becQx7Ce}(xy?1t=ly8Z9K()0RPm|duT z1pR*?ssC53{Av1cXVG6i{*~H)7nAMyANC)8xoH0_`uAo1x3&K+CN~=YWlgvLJ$d=1 z`p@jYt0{y1hmMQ(zamSI`gf-Khy5q}BiR3fy#8T(EnOc|C|cbFDyMTKkPs3 zM!NpHm~6-Yu>Vwk!Tw)k=>_}$$yWbu?EiZglY+*7xnTd%MpNG_OcQzeS^dNQQ~qh= zKUY%*`;RipvQYh7^uHBL&+8xdpRUg_ef)RNrv6<_mhr#4+3Ay|{hzTZztsM_m~6-Y z8SVe?EWM!rU$g%%E+#hqe{8k?E((*0m!IoDv;XM-94-2vx&24~=V;OYhAchozX#Pn zxBu{eV2?@vZ?W>j{(F%AyO`3(|5p3I$EN%l_5WM;-;1S({c%G6nb`llEPvR4C)l5t zE+#hqzi-w5mkQH-Uj8rWf2Q{TRl5CGvGlzDrS{(qGZQra;`ZOgWEuab>A&M(oAOKb z|9ky^h50Gj4;ue{!T*ou<(KL|v;PXS7o~?y`I7&ipQXqB&(!~SGuin6ZzTTz8-;19 zP5J-6|9_FC7wrFE@&BKt`~Smv`MLe){{IJR{|a+XUVhv|8~^`6HKjkmje`l7S+kg1~AFcgA8~?kRZ2W(BtN&jB z{{KgeKiw6kX}tWb{^9>q{%QTc!u*(*pT_^>|6|-J`v2`%danQQ|EYe`?7y4oi~j#d z*8gKx@@wP&Ozc1Tmud5V?xvzF{g>>&!u*V`pX>hzvj4b#!T;Cu@*^J2Kc>z9G5fDD zXXE9k`p4Ku>i<_@>7oCb`u}bw8~;DM#QtYjnAX^oANC*P#;@9cD!*X=ud?)l{df3^ z{ZA(QPyPq&Kjs6-{~yoG&+R|uPyTKPB>Cyf&_5a;WHva!}tNp)gwg1l* zrn$WQnc9Da`3WyS>3=5ve>h9e>pz42H`)6C8SKA9q)qvy_8M}SOe<{4pNao3 zmOm5!|1wK2*#B?&|1AG>|9>9R|Nodx`K9(B{=Zm$^8YdKCffhfEWM!rHugUg{~tD5mLuK%_v7Vf^)J}} zFZuuQ?L_~-HcJot|DN<8{y+I&Y5u>PDb4?XZ}tCK`P2RX?Kb6?>VGEwKjkmje>aw% z+kdJ5?`E>`|FgjV$2H1sTJ=AR!ZeGQpVhx~{x1{%A7vB$|4uADuYdUeR6m0K&*1;F z@~4ge?JL`qU#kC^*nij|(f$`_>A$T1nfm{p>HfcwmtU&?f6@Q9TtDjHh3a4G|GSye z{C^kd|6_^$f2=UAw<$lyf9S`*YX7PHg8jeF(sTQd@t@;Y>_7Ma^I-hvXz~9i@$$3! z$M}!(PaFSz+y4(`>3RK2{eL%8=Kep{e;h6L|Ex{<|GxeIgQXYr|7-T2`~OeU{eO%9 z>1?*+`p^A;r*HWGP8R>)pXCqx|B~uo@c-eDQ~#Id|G&)K{_nFXe@6ZPhW~Guou!BU z$<+UMGuin6udMd}p;iB1DNGAldX)bQ`u{EeKa!>A^`F81f7AcBEx%O%zt{i&_WFMm zFTYg(nf?FX^?!-~pUL|F5|%%o|NHy?|M#x{598%$_5b(R{~NRPX#a1h{d4=z*Z&3o z|4rup|3RDbOYMIq{{KI`{@;t2pX>kMT>r1e(xd;+bp2oO|7ran^DZ|2|1*VYlTG>2 z|6?4P?*IP>>;G6AO<(_)`2T$UKR^0^tVhxM|5RRn-v3knY5o7V*Z;GTz z|39$)zX6_4`pWY^SpSC|P9OjO1MB}ZeR0_wN3dmS^pQyk8A(#^?#Os`uhJkUVc{p!utO^vVZR84Di}`Tz9w z|9UJv`agHk{%7L=|4aOTTK~t~j_i)r{$u@r z4ljSE_TSx{sr?t$|GTpEy#6!Te~JIEFu!8uPaFR`SjLas{z>iszrX(9otIy#|9{E< zcmKxvzv%yGzW)ESP5o!$|KqyS`oB#4|L6g|8Hhf|APHz>;IYf|C06p zqc-K2+W$<}|FP~a`v0X^dSU$kE&pG#{@<6EpX)!j|6lU|9c=yoS}Z;6KVSc+`Pa<+ z|1YfnZ?h@CRR1&a|0#dL|Nr{>zr_FN>;E^b`j7ShnY{d&>VGEwKjw`^|Gy(k&-EYv zKh@9I{D0f^fBSMa<(KOJxBdTOEdAf~|AqB`124Z+|C#;&_WFM%mLBz=>H5Fm|I_;a zBZ>e2NMTxMQ~v+b`hV{9_5UAv`MLf7o9q9rS$baof7Sm_U;qEjru*|M&X;SpP@4($@bndHz3!mtU&?eEpx&|BLJY z%WUfZ@B9DXyZ%3dm!H+Y;Q#aWe_npJ{wF;DZ(aXyYE%D${kK{F=g)s+eE$EiP5Gtv zKNJ7|@2>yz`p4KQ?fHLU{l5<{ztsNo^?zP|w*HrK{l5lFkN*Fg>;IPdk97avGXJ^7 zruOZspSpVncr~1!i{oj|R|BL>A z4vGEGp)jqrDgS?e{eJ>4KdXQE|9t)by>Swv;{S)R^t}EB{~zmN8U6o^ z>;I>1%K!K6e_57Z82^85{y&rT|3SR`T>owSf2{xW@_%9dzX3}R`=3R$|C#vzSu(f( zyKTy!QU5(z{#^h6q4ocJR{h8N|2$rPR{t6G{|PVu7uNr~vGlzDGuVHL|DSRF-)K{Q z>G=QOU;mHg<(KOJU-JJIrt&QPUtIt9l&t@IDoiVF%Abkc7|5=wkf|<|1+`wwEivF|Li=K#|EsX{sDHHgZ?6A8k=Xwy3eyIg@?-r6>$u;w{_pe`*8lTj{m03&{y&+Q zpVdFs|0(~p^`CFA|A(>ky#D{H|Bv;5C(HQvoK5-vzWx8+_5UHf{9OP4=K6mCOAq`1 zO0@s*|E25yuQIp)`)$gfQUAZ;|NjT;|Fr&O)qkx2FXH9@g8qNY|5vm0y#6!j|2O^r zTiAyH8wud^zwiIuhyS&M|LMX1`hoxT1OMv>{%`6BsJ8#pPwIdCv1r+s@=bNY)A?Tt zOxpvgJx)>(vG|@yJs{^5WEJE>f*cPymmoKWEbg&16>@PQd>Z6tkiYfe`RBKj$!Om- zQKmhjw`sPy#Dk=|7}-#y5*?-f7)4T*db zGUZSET+yCawEq?Dg+=>f(f(L;F4`B1_Qb+vv%Rlq53THg_CU)I3WH*x9H2;{77I zkr3Vwa&(y0S5HFA-7!`h(Nv>SYYc%pV`5a4Hd3q6 zB?Rhlwm@AkRjgL+7psr#5tbCMvC3_9e6vA~B`ipzi%y8KhBl4W@`O#*S|k7MuUBg< z@mg!*HL3*UoPMY&@{LpJ!VDS>jt6NIGzL|yCAMu)08XgVMQVhj)@*GO(V4Bsc?!!R z6e=`9Wk@i#(hD2k6kHJ`em8UEQ`Ar7Y?;A8d z6SW47QK?d@wUN}w(BrE$QFIPv`i7Kb7&Hch-mn$vNmsrSrIr%mR}hc-ke~;itWTyt zG(tKdmb(e$CK??zlQ?6vZCF!;1#2{FV?f$f1*C0{*@&*hHhv)D`|IQ4Q52&!nAv2q zMRbpw1G3Rv4zeB_wAxbFEg(}}2Lr0>qHjHAT6|wZh0(Qh*#mtQ*=kLncui!2My=H9 z(9UC3ksADp(i`GbbPrK}oR`KFJe@PrQJ)p8>64(0GN|G-%1Bi#46U+v3=FF()&T8I zQmPV^+Bj9TMyb)MWjWS(vVMr4qX^DXL^$Va$kZS3@F2+4PiE{_A`+u~w8n5vTzo=O z=`uY243v#*ESKLSeQ`nl1ew|`4=1DTBax|{^Kg5F)4dVpiV~UH1P^bI^NVs6Wa=k* zxDK*-4@stb$ivA_iQyzuIMp$aL$-x%9hZkf7V{uk3?BhmDa1d5dn8_C0A#YYOrIhv zL`GJtrczg_R-yB#cIA4RaK&*Dp#(6{@DP{s$fh| zGnLfpD?}z5jCuof^2%CI8MVhpfb#lJ&mE~ZXxPc)G;tN;R7nw<@I;+frzO3w@r$P{ z4CgKdWuW~*OP7(!TH;#)NIybAE6^HxgvLmP=q z{e>9bRwB29{15h{5lIOey#Xc_U1=4c%Iwaf8gVm`Pr^D+nFik*oh{>0QTE00j5=6} zM$e>(%)0aVn_Vn6JX9a25zE#d=Md!%kX!aZ&n7wE5%ND+X4d(5!*Epbsb0ay(o(5E z(#IPss9;8P8WcZfy{9Z5`I7GE{qN-;sgKnga3QP$?&BKhUQqj?K8(xMhm-tYx+g|e zTzo9ull~hl_oO8H2f8m@E+vsmOXM;VxjbYV%kuPjA(OwuWk>W+6fU-nPLMlW`Ty(j zKxP%SI<=;cl7_`feUvgnpQuwCm9n5so~$2hk8j#5`QNSkNDNCOjTPc`(bN$8AkQAi z(-rlU4c}b;K*vB>3=BOrTqW$i4*enP77f&a8Zkm=0|;eEa1*kt4x2rh@9TkYs`G!h z9O#OoG-Q5c4Uiv(U$P@;Thw;Bd=zqaL7ojmLHE~>rBtFHgTsJfkFp!99n0ZbwRD=^ z-IV%X8beg9zPAwu0tO-NoXJ-E^Y508-EkD{-_3<}mdM4t#Z!hU5!o=@FB*Svc?@Lg zkGOmmWv8(Q4P@0?U9_mHU2wb+Tr%nLt~f5n3x^z}jngK;2#LqLN#dy_as=cya1G<* z^#-&$Hbk}zS1ofUG-jf4fLfA{Y@9!}U&(PQub4lzA2ycPMmC0Hm}o#kF+2w0ynHNk zs4m9MNw^I*@o3E59q{WR-3rjgX>>-6K3Zsv7+*)mG-gv&4J~74Yp%ii78w2ns1j8C z_Ofi)y<)#?uP0VKFAEnP=N6hcy&);qdY|JER|#OMWf_ z!|>i(bwUig9=cAdOB$!7hsS8N(J=`m!|%{XQW&87O!1}HOnn)vcbYN?su85B(K~m6`(fy#l;6k z>YJ(!E#o!1MtuyJ1Bi&y#Ht(i(nht?YcYC-Q)RR@8nR(KJee+j_gLE)tBurDV1|kM z(nUBgjjO3It%Pss`1T*{k>W74?;Eb~gA!uIR^6u}8?IR$^9tk);DU1Ow2Vt;;$}MH zdPJGV<6JI^>!rS*%XJ`=4dQZL$W!5Ci43`=krE5=?stWBatgeWM7F~39_${-Vbt7K@OD2VUR@~AUQ;cPqJ7)Z6)#B zK^E(uWb&=~^+!Oaah<1EHkpgFtCO3fyMw|$i=Br&Yg(KM);K)P49GOUz~u#yMLk>y zneK5$J)&``vW~Jv!}j4#+ca(*9@?RWU(2A-@JeatnuWBagZ$jLA&c#mJrw{ers9v_NLt{oN1D$&yNF@*<6%kV5Kc^x>Dk7vJLMkGpA_Big z2Pey%2w(Ve#L|~D7QbBa54(iRu~Re_!W8l>)_)#y%fGC0%Rf&nlF>ge$-iuJ+ke^R zw*PX-ZU5zzfBj!B`PcvDmVf4zuo2lX-VKC|b@kuzs5d~)NH$NI@@ z{p6E;@=HDizVRvejSsJ%Jh&cOyThoG{qZpV*S0KwO)LHX!e7W*$c>N##Pzx0M1!-UK zE#3cK1;$^K`#<{0^beW+Uv!Z7M@7*eRYqIjav7`r;Bp6R|Cw<-rS^$eVV%zC6BV0i zj0rbr;#6?BbkWMXcnBu#e6dI`j)7i4CZC$0pJej8GM=B!WQ41+7OqrTXUWJ73hEdh z5a=Hk9vaxOk#L@Cf8{)QVn7^^Uj0TI_*wLjL5slP@D>fj{F{acH465@dZr$WL;U)$ zSkJ-bk{GXvK12?Q90Hl_QO5Gd>h(QTF<9jcXDdX=82N`M=)>bMSD2RHWTY4GXDLih zh-3J&ki{`8$)eA68tL;1_l@MH5?Q=Q=MXNI=R9O_%ullDBMwG>v_{A8#}LS(&q1>2 z7bin*ium-QF)?nZObMus(Dxv~0^q;WfcXj<%YSW-z?gtl=Qx_SkkvS0Sx=$o6=;0L z<+}J?1pMpQgw=C;=77&z9LM#W9leL8kiV@@$Db zM4290E`S2UISX{9Rp~J z;5aA`R)SdY7?5lJ6X*=CfI46Y=np;u+zFW(_<_BkC|Cd@z)e8S{zuRn{01t4b-(~# z0yi)UGy_LLS@1LH4juwh+nJyvxCCl}ZJ;km0i-&^Kx1$almtsa6u1jWO{apk;CE0B zYy!Q&TR^VqI1mg@0w1s%=)hAz-M}2s6VlnM0QdyRogWPR z!9Gw7ECi9@7NF_3Ngx!Q1(m^iU<9v#I~Wa`gJYl^SOI!~M}WGWS)dcR3~Gbzpda`E z&=bpsgC^h*C3 zFdei97eEcL1@r;QzyS;a0boBU4i*75xD9fE$si1z169BVkN{o-1sDT@z;RF>tOT*( zF(?3j0-eDXPzUS){lQ0I2h6|^>;*-^0uTXif^6VN&>H*(DuH#t0A2z&FbXsSM?qQe zGw2Q;f_z{m=m;)>T3{RK3sQg+7zP@HgP0;0fOkPA!&ZNcxL8rTGSfw#Z|j03^o zB=7;Nfet(cg}@xp6sKB4V3rqy9z!^{x`~rG{7r+&a z1cBfPC3m;%~>^Pno&2ok{? zkOhndEx-xj4OW3T@B|bDvq2Yd71RYg!2s|H*n`2qAM69gz(NoSZh`Dz5(ounL1nNW z7{M#x4n~9K;20-?Wa0*laYk(d+1BJm{5Du<``d}9r2vRL(zri4UHvqeV63heLzzyID zCV&ud8u)^>ARasiE?@*`3J!zPU>S%3_dy;o9kd4*Kn<`3^a07h0So~FU_U4h76CQ5 z4RV0VAPk%XRlo+20A2$H7z2X9aZnzt1hL>TC;)x}oxv4Q2kZd-!AD>R%)k%q1x3LE z5CLw2Y~V-G8vF(-fpx$DUII5T3N!;pL0RxK=nfu&d|)Q%2rhwIU>oQQQh*Z}1{#Bd zpd?rVqQG5{3rq!V!SA3N*aUikx4;981Hs@V@ByoV4m<^gz#PyOTm$vMuiyvp8OVVN zGz5D<5ilR9z@NYiOa!gK8Bh`Y0(ycMz!i)Hf#3)z1C|3VcmVQ(8K48W2x@|@APKw& zj$kNg1P*`_U@_2uJ0K^R0@{G{peoo162Tjg1&jqPzzN_DR)IM11QZ0bK^Jfp)CD`i z0PqRegTcTb>;uKXLJ$dVf$U%s2nA6t&QY}ON-GlJm0PF@zFb{MCH-IOY07AfN;0xA*c<>y!fDxc6I1EaIWgrIJ z2YJAB&>ma>HNY0o2P6XrFa!jE{h&Bl1k~U*$N?sUFmMi30UJO9cnuU_3} zWx>y&J9r54ftjEqxCCl}ZJ;km0Zw2TXbcX5l3)pl0(U_!Fcq`~zk_OE6X*rr0uL|_ z1cQ^n2doA<@DvmRb3j*c4b%g_f*-(VAO|MU5bOa(zn3#Xtk@fSh0oXamlJs$e5X z1aCkVFc!1`CxACt1>(RHP!P-pUBFdP7wiNBz$ai21_OVv4-^9nK_s{ZvV%z=6r2T> z!Fpf>uYfxk4Vr^vpd45MdVohDKbQqNfy*jXG^F4MbTC`j7; z!g0t21)1IjQwnk_tqHiVo_2=4&7m_+svx56NP9xFkKvVt6-6dXmL( zl_Z>GF`V8ND-6xLO#7wDzWjjE*Kv?hY9puZ^5fko5>h$B-5tFNsgG7(da;GSR{FKMuJC(*M`y zBdxFCr9Ul;39_4>Y*I#v>!7;jG7K8(%T|UF|L5L!$5ryjP$8&_0LuiKt%GB3s1GWn)TNT4^IlnHvg%hn5z#VI3_lngWPJedK>y zJmH-nYYk$;80QVdIla-|i~lp{*5XwK_VPr~9Zxn1S43`^bmMX<$hnXgm+9REFUaZT zkJ84*+7w@Q80X55I3<91PeUMM;K3$?Rs01ZyxWGiCGaqB0((nNY0%(7VVy>;R9W9H zR3_^9i*>O{Y=Ri4kl`}W>;32Vmp$*w9*)F<2UhT??S$O5+=HK>z+|6l0%{8c28?Xd z5Cd6kOwJILpM!XHD zi4TuRLWP{gHFUr=6amzyQhm4ps*fZPX?a<)TWgImF*c!AtwddKgDSps8N6wLw{=O1$ZusIh4`)c>(pGcOMb74a;!FY7PxihP`2NB1WBp(k5tYk=#Z@qI8}mLQVlc7UYIPe;9eZ^I35!3rq-&FA5a^X?x&`c>MQHHy z3SPP9?@IdwAU=(0z7W4Rb_&3YN~*LAOF;aRlz-EJ)|P|HI4vp@ucOEoA&lxMDl#m{ zHVk*2uFJ@=ZHtXg*8kSk1Z6aAF`e_ z{ZbI8zk)0;7KNX9vObaNjFR4CNV7;$Pz5E+(|Wgq5EosztTn!=Eo6)x5){@b{knPl zK|DTtYu9pFxTu8Q)Wld46>fccPDYf4r(KJ*H14&$@oP=1N>Ik>@mz}@n>+|{A0sY} z?L)EWfCfQyee^;cDu%shD=0xRrUk`(WO<%?vOcp8zNu_}_@@4V^iQMHCq~C8<5jU5 zjQpruH)u6Reio&yGvXFQT&_brEnXv{icU*NdPOf5qADz}bRo@jq%DGP-agqIWfkz^ zGv0VZuMnX{OU1C3uAQD~(HLqeE9^%avNzAH*BE9{>5O=Y2xU#O)_D|l5lXZn>q~*u z11e?DENz!xAI}rd2BCw|DE0B!B7xXPwWDK?^Wa5u^jdiE0cq&INz)ixH}OMvMK68PttXSL zqXZ7mEjq#DTi=1Ad(8H7pnGf?swibu5SPZ9(zqCvk}B|MvTle+eU3C<`c@5TQDq|# zmuwx68=4ql-JZen%8<7{%G>gusgG=fHGlt1?V^wJ0>bm)T>qj$Vm&NIP-#!@WZvhH zfgsJYY6$BxsnJ=QxB7)AdpM*G#y{5gP}vxWMMHy5Xw*J|_Y(6EmvpGPCaE`+RLQoG zpiV9p_6s*XAqIo5>NS}Gq<-Nz;#0pGtXJw1;Vc+6^b}nJq^*K;v|-wg z>!bl+q&^z2O;N8)CL8xjJfWpsP`)FOwqkl(B?e8=R9N;Z8Qof6x(Su4R;5$GtO!@d zqO!XkmVO`i<9tnUKHQfKap}(BHDRp~U6$jO2$(JBQ?RbJ=W_*-OCa zS%k5;Pn~z3Z0@}yHdL{=7p#*fm$NrH0qqe^c8`TyUiY<$&?QxFUI0x-Z!3-@-9;Rd9 zhN2ef2ICSjUd3z(+uBM`Q!1E%3Jh(jtWl$CO>gSce!@9w;+wal9@en|4Fqpkud$7e zWr_>o=FT>|kBFzlIsU~Sz%nd^^Ikl~;=9w{E7VVkawS2g`A)K#JiH=g>gPn6%FpFW zl5mp6@G6q?)se&}S&Uy-5>B!hUQZHEvKZa~vUvSXCGklX;|EH@NfyJKNy14M!<$RO zNfyIfKo+m3jU+zFV*Iv}aFWIFc9L+C#cV`{eTWPO=yt2U)xy z{UOtsiN_xRIZ%*)fb1{GgCL9gHxe@0MxK5YWLoRv@@Pr;7)kgz$m01YKo-wGLz15M zL=ewEOA`K*Bz(3ce2yf19%M291(3!3S4h&Yl%!uJ312MpeQ3q&|3wnMP7=NW zvUq))AdAf&q_+Cl)K1uj~N%#Rt_(4hdAxZdQN%#>-_)$stamZr%Xs=qn#?m~zxFkNwVtkUt_?01x*Ixy)c>Pr&i`Q2TGR@!d{K6oM{SnDkCE;Fz?2mA< zJOPl!@dnA>lKA5Fc0jmzy&WNo*V_p)jl=o%(OQBSzl%iX`!e(RBsUV$lPq3;6lC%G zNf!HelEw5SHy6%FGL?s3z5W-+bu?@Z=fhSE?}TMMyr^XF#a<}cit8Z%`Cq(_L=5iP z`sok{FFccjdI98rak&J3Q~TjE`Tyj{h%))rT&{rMu7X?xzs2|@D~0g7_?<q+EV z5}C&RV*C)uVt&DpY5c^mhh&QXSJzK@x*5;C<@ zp8gSj({(j5sFIABht^;(sX)sFe%hgivGFk~+GDDvP7~H!-&mh$Xhi#8g=(<@mqr&t zOLS@biW&RF$0ypZxemqolW>&gwQJ(HgOy?U#y)D;{8t;L)u>x&F!M}1PDiuFzYwcm z7K-d5H7((K8Lev_;kv}Q2#tX)3R+zAKH>VPC?nQDt>0=5wos*LmLF=&m0RZj!}alO zRvzED4redcCbHw(5!aFj-%j{0gl`vom$ZKS;G62Uy7jvOzTF@TF{1ilLc zm;=U0e2Zls1-UV8S?sUJ5{o{zA$P#7GOjt?;^WdU+6mTZlfE6&V{##!O)juWg>bg= zE9Oc4hA5LAV$TfG4QBhZX?55s+UC5Y5hsvV04+NYhQj^itEe{ds4lT9TcQdRsbaaB zP#${Pgt;NDr8Uy2TSl?-;kX!&<{@z{30hSwTZRkP(_&(DQ?~9Si8}@|i{z(?*6P?V zf6NVIg-XLt9ROE5?MGvJvs|NQGI9^pvx3^>9f9*TX4^{JUej31HK9^)<&tX{2iY=z z)X2JwYdM4E?6&6_kGTGZNWQaPq#-gK9zT6y=XAENX!Y_#`QmK)3M;!|tccbXwZIs) z!TPgtWI`-IN>xPVw%!*^4{9hTyXk?Ldtg68u!e(4ej^^+qMvj;ATb`Bt6|oHC2WYr z`IfXDuXV{HGzL2vhFI4AuxMrd)!MS3x!QWw{xpT7;gG^q_b6l*}JAv&Tp!q}2nAX^c$ zE`L#zp_x-ma-oO8b2`@JjoC_<%^^z1_8+$#MNzPX#kAFrZI5bQtZGazSBJ7^^E9-H zuvk{I`IhYy(^@9o5tv!NW20^S35YLQJF=F@x=KWvXs!HUECPk{HK0KDjyDR$S|Vz@ zWfX1Jm{!(MTFoEqz`{*^QpT;m-^4wRWsX zY7>&!lqp-M#j=E0|reLcD}Yfg3dgKmc4}mi4LpidP5KK zkZ=-J1lx?0rroSZHENYw&F0K8U5*U6#HK!bVcScZRpTdBW3$KzgT4n=>EpHWuxjk` z`J|<$54M&trmr3M)+fed)r!ed);wESg6YUnmrUe(&fI3WysJ;YN4%m%Ye=#H^9Ib~^$PqdHHm=qVQ#a`hQ zuM8$8@$3jM6rBZAusU_DMr(ccPD_8r0&~b@g3#FcVe}A~21eerwWH<9OnTT;r%$^s zwmxXoXnN2?Vu?EX#bzZb58-NiCh9T&$sc)P7eG_cmbFBUk=b%Q?rBWIBLb0$?62Xl z8^MmLac}6JMjG%e5a!Q&VLM2a6@^3jvEWVDY@N?$hp`rEnNp>EC=@G}iS$S$LeX|u z##mZSuQxXOqUW$QY7~KQ;F|uRB&KWGb1`&2nrp{QKK6e{wZckaDHPhSHzew)8;#Z* z*vcn6)Y3?kylhR1k8LJGuX~`%e#AE)3rxcAIP^rQX12M20y4#)0-7T((`5Yi0W|)g zu&F>7MxUnAwlS1<(VUO2nZ)xHSeT)ISM(*ZN~G zRP2^+`5O?FFTeh2_zl~Pv7SyHlBT->Om#6-v8rxsi{^?4ptwz;xYVavp9c)K=&f)h zJ$^%r)*>hCF^nIew1K)dMh*QyKtryo*7F2FQ~j8xhCorR8`m~s3fhj;lKpWON*BO& zGSHa*JMAE1G@?J@&>C&7CDgGos@$TD>A!-YQuymAu^MaE6w;KbleGg46#gg-S>-TN zA@s%V=-Fmk+6c91bz}70$S~BZH0e3CB6Ud{S>W_xdVLVBySKLLiXT*^QJShyLOK#) zRTO_x6pb-}X^IhPTVSat2nu7gULYkP#b{_%j3DR*WdW^V#?GP(!Z1W1#Y~B1)83ZU z>hZ^I(%XBwDj5?aBhi+l5@XrA5&0C^fTtCjyC9i-3lBhJQflwiX6ZLU{uqT(T=Fl; zC(8@SzoED^=B9Bros*7H7{#Y^(wLTXgvK6pjN*`=L+7PD=r_fq-*o=9cm`}YI0J5h zSHM1tmn;`34XOfv&>pCP9t;69z%sB290AWjDGx8a0qZ4e2O>c%=mmy>*LB;5qmRJhI{bf#Sdy)Ca9W5c2AR@9rQG z^abR@5maXWvBwXB%0<2!{ieQ-$|e5hX`ONW$aYWJF>n(62L1q-z%_6SJOIxCPoLR0 z9TTDR(*35q>39(BaSTF1ThI}70V<#d(V#ns1M#3Y7zhRf{-JZxhss4CvNx0mhxh3e zPJHP&rK3+~=cD6zC(7PIioFAl(Lp;0Cl`fh7LTmhqBVPt+;H@G`z2ka_C@WE{6A`& z#1(fh*(2}?ctIZvgK8icr~$P%YSUCwvRhPl)JFMvNcZVHbWYyi@IHt1B4&lBED;O? zQ^5wX3mgMCK?=yZ63-a|f6x}hfWBZjp!P#hJyE?;8Azu4)6L0?p~ZJBFj{33zA2pY zr~8}-&v)O)v6lcJngZMk`;A0ksmau{K^?qc6O+5SImY=sea=-Vpy~ zlQ)pdRefcB9c0cJ!eZ=*wJ&zeC^I_9ve0v)I<+^R2Z6Q6>q!ml$N&- zkP(0GFutEW-X3Y4u;_2l_HmFoILKV_7rihEgNGyKj`H{cNbf+SK_^E%LL8^+C38Zo zh}c8}wu)`wXbF<{Ns=ceDe!m0qBRNLY#t^li&e5r$Rx337{|q9f7u3dOENmfrj!G?9)M&Ay#Tpb9Ew@G+ZL}!=@9$Y_znqiF z8z5KxT%vRKUVH7ep7pHfeLZXK;H>lhG7N$s+!l4u4A%!|M(ZyOFXZp~aQ&I_di!bc zv)&%@CkU)KIJ0S`lqBwHs5kg?(V4D3cWqZi@3#Dw3D7AK#`cxQNcU z8Y&u5`~ zwA$^VWGG1*E&UrFZgpCXpw(zZ;IWmol4PjW;z6r56g7q$(Qu=|MT=+r7w}4>5j2K| z8p)7d5AnFw7z&009tJ~0!~CD8LlOVc1#J%e*9Zau(du;QsF5V}#fubKDZM7Ha~c`p zdbBY{m(*;GsbT-$=unG}c{7-!r(C7&Mx!|#1#_d|u*2!QH8*PT6AUv1{ZhLVMR7A| z4b6*!))7ajwD?aTMzMwl8lpIkhlb)I{t6NPztF~2^k4j@<$s|cf~vQ{TS`mCyMnOM za-Gpp?LYcewIGS2BZiM?A9*B@+H1qA6hz?E!1u2(37sfrrT~)Z>H9Aj9*U!3rnA8x z8dVGGS3L)a|v!$n-&{U90A%eJsrW|qDU}AO%0SeF)SsbDyj?* zAHn}MMFwSLx0DP=!>y6I8chR&Wf%gp)rwo-Fg`p|Uqh{tD2ZBXBN|ijaLj;XV2DN% zy*V`h=miVs4~@)!0?&ryNW|NsB}gusFJPajKmI>v{7L*jj^sIBweaSgz zFF9}B+P^sG&x4)eQ7=C0&;H^?=f3Q`bua3FbYD0!^|HS>$JabB_(nLiZtYp;oJZA{ z1m6q~uimSQ?+=qdTm4sSgKve&i`TyF+~C{c(4Vh8>#xsy@mZ^15_~6YuYK9Nv(~-j z=}UtL!XvBq`?|R=9IKvKV|^vo?0xm#s}Al95361+yJ8$xy7nb!tzP}I7YAQThF|oe zv(H+0){DSH9ClvxqL;qptaD%VXJ?)F5@_Qc;583BUkI1KDc%_W)vKc`qK)Ad;pO2g z!*w?|KGb+c`0DVc#;d{$-xaNTW%DnmUi`!4U&9}S)A3Kj>FDR-!{NixW%1?luJ9Gn zmS}7AmS|7-#_09U*F{%HuZdn8ZI0d$y(zjXdVTcc@XF}l!=FcQj;@I|MVCiE3jZ_w z-_eHn*65?rSEEbg&GDP#{|bK={xrNQeki;o{#kT!{803Y`1<%`@!!QCjd#SKjc`#9xkIAO9fv&*YQwkCGoJpNKcbH^;Zc{}g{Q{(St}_n z$MN6BuZw3I?}-07{z!aB;{)*r zSCW58{=xhzc=K3t0h}!xS^rAdmH`R@GlXR0T;6b;cpQIb=C+Ie#$!Id3%)+dB%4nE{ zd3#DPwz7tPg6ED#8L#JwN;BwS-PCbma6%YRHZnycaUQ7yI-2T6BSEK=C0VpA3g&dO zAZz8jZmWK5LYT>&7EdhiTTJddv`*rVqvy@m3>i@*d?`<9VY&Cl9)@=C7e@K*nG@CxLRB z1DexD0VGSF>oA`+(%anh(m3y@ANO~v6t40uWFd%uj2bxtLZmMv5U4ZVD13B29)?jt zT!b0H^-AQ%z+NUu31gr#q}oT^7>O!^d*$>a9v zqoMEzPL2zPP6$HgEuK;%o#O++qqp5yn-r)jpfRy}1?mKid7_%Y4RvM74HqnrgHG09 zLOlsRHwrg@vrL^`^F&ecDCC6H*@+HVJu=H4cYr{zpACz=V^ z6H;KLx4PLn#OjxY5qfqX^qw-Vepv^-iTR-s}%V7RVAAPr6@r=b=eHw0d~ z4+NUzGs2UUO|O0|@kU!vLR1mnUm$C;FuhXcFk+NmWmjHU&U3VlM&b=9u2e0MpF}wt zh}P^1*5$FyC<55CviK!SPFujtbxxxv2-8%WXd2FSx9whVvS>I9&OvNTIs~g%<-xP2 z=5+Fz*UwA``TYF;>-kMT=4kR!wY0n5(mzKi9IxwkB&&L!?~HTu@hKQS~M|K|}mm zc+pBIXh|@gq(9Wt8&*CXUb-?!-z@zlbf#Ts!xCe6*%DXM%Dn z(~E^}fCG=h&kBsCY667*6dB;rFL*p~#S6$xRT@b*xOU3=%o#S66sf};*3RsR>wh4; zp+yDJOi-ZfpT_G-Xu4Thl#JN}B+V`c^;S_qKnL@ig~IFD@KC$eY~Z63Q9$134}@ar ziYB6bh7v1J|JG)R&SpYKokKj>6q-dB<#&ZLqBROE(Z$j2;qrJ>=vx%WTh;W_MH{=r zlSdDy(AdXSV>*f!RBVC+c)0Mit%l3Rqwv_$h!~e9+y4BRC3yN7M-2-^`1?oe^>9Ec*-Lkdqn3gb-r}p zoiFX{eCbsB$$ICFN8Wi%=S_9KZ9nG9wtjPks_Dn;{X5sxj7Hf>hhnY}kQ`~K|6PrJ zkA9a1CJbuNgZ>lszjHr?cV=Jr)J#87?_L4MN7#Ln?(ruW{(ibY zx37C@rZe^KH93#4`xf0toxI&u8`Jxtfa!h;plbTDIt)n1M+gH>Q$E~PxF(v!z#g`^ zoMNL7gw9!TCidIpP~>72Iax9xu_WnIQn{@_lDAF`5#Cr197R&(4CN@m3n^a52V=<1 z!m0ReT1Y=_zF?4F7f+=h_18Dou4jtt&eI6m35+aS)eF-%ySj=dDq_v6W*WT6f>xxx z^PIlGXAxD`JP0zvZtqfi^rtvxFILnb0wdnJj4e_h77?r$gpC5U9-xi%kmq{WfvKOg zhiBLB5qW1oit`bL$J@>#xq)w#<9W2IjR?aGn1>p2=cK;yW>~nksv1~N3gL%#t9ZEF z-1O~+PsI&f_0BOB;tViVSF~Ydb)KZVRJ+r8Nnb~0Yy#l!Bj6w4n2oRFmFb_yYx4HG zwd(17yW?v7{IzRsDFF<9=aLV-3OR$@Lfh{!NNRDs>7?pLj&s8g2s|hP2Ts&YQguTh z*LTAc>n0=my8@)~gStu5JJpBCU;h+ZUeSwOe7dYRUe>EUR|D3L;qQ>V+K30+wbL@*GeYT(HM!9TfLDp6bhR#mAt5ly5!Q~}TbKto$Wi%Z?mR+J#S zK`rHV3%bLE^8XOJ0xNjxvfxB_^*hUgr*lQPd_@M#3O>ABGbaIQRZmykZo%{P&KaxdEAGDM>QKH2{TLjG8`MsBj;hZS3^+F`7 zLKFJQ#5`8PM+`8`3Bm0--2Vd^`E`@+;= zO;yNNSeV63Uhri5QQHDU&F7=}6_-|Xh#r{Nr4JN%x8c;mB=*69=Hg6EBkk=Bqz|@Q zgEc99(m^$0;COxD8oIu}q2s~_!V^MPR1`FS5Ia?Zl$!iSe&4z2^;ZJ2jmaUX5IF8v zgE|vHw4M+l2pM7?nRzLC)}*BGH8QWYxns*0-e=hG3k}{tD;px~%v!=%u$GXid$qCq znvc90xMYR%r!?})He%XPD@%fZ3jt3SZ+i6x=3MiD^iJQP8!k0c>5PR_>3akyFV%+R zE9f06Y;z;=ot!d?r4lcW&h9qx{T;|FWTCr8!~8@`yqV?AXN}@6gR0vx(1qMRz!2s; z!;)I%N>Xx9t>ghK*-@47{0@J2CT(D{RVYrcEg)U0xjBVi0BdpdR2RIL>!EOky&!(l z>td;5*-wOGq)c&b;ZMb8@K9W?Tfk6@fvY}p1(&A1Dji+2~e@pOw3847Y}DIZ_0MYsGe zyXz*gT2*2}nBVIvEOZs_*}DQ0q!w1V3irDTVcCK=nJq$fD*09gjQm1b?=zS}?By=D zgb0i!I%u|k{8~nHk)E@w7Cddf-&&B!c9Q1IUSpOP$OrH zOXDjlw@}kbk?`DCh+A)^^=92<}#h}#p=5Mx>Y>`@U*S3EDs~qJ)rlTxp5{P_uOb~R3UdA0 zThN;b!35wLJT>7*mGsR5IQ?fQGUP$JhfGv@j|w%{BC1JKF|LhCTO4*>MCrATEVY)L ze$%xx5uG9ae%7eMEM90b{G~;-DOP3oB>l4MMMS!ZBoazZ0&guBqy=`gnMGd{4dtCi z0k~SCuVB!KJPZ}$ov`bbXpc_n4IAPuHfO~_HcWg@;dKB&ggY*GZ%D;|ML?QHDR5k1 z-A@BTyM}U`>O$H0i59$CZr=-D7sPXev7xJ$ThgvT-W&?%Nf%D6gKQckag(5U1=*DT z#$;HOfA_lk-f5mXaugfe**Go>BycqW-BFvyu&tD6-y=c45>MOv#1jMx0`?)ErbEXw z>Td%}?4@A36yuBelVrL$EJf4Ca2DBQWLL>2kwqz=iYq6EAWyOBeptka&_UeKL8U+) zfY_;6QN18Oyn=YHE+--&MvRVgLx^`CKS)!>O6X%BEGI5Udb&CTJh#($j)%Cdj_1_# znc_E-4U6M5m&fP&<||U)RgwBcG#x6ZLoXQpZU`L#1D-I4JKzYok64f#2C`MC^Cm63 zCyqBY;&V;r8FfBniocL2(K_d&z#~S-VxTDh>8vy-9uar(is6HS_j3dIE4dd=cDQ`d z`s=x&==HOEt>y6!c;JPpX>iXgs=mFX^`C}S))<+Ol{+Re zZ(&@qPDw9mMd0&l^nG|q-g8Up9-oD{bI%}v zTKR3}dwoFSax`RoEnY)K2hURY2uYxf_4Se+n`54;lB+PjJ;AJ!5=(qAMm-Aa>7V-+!CiuY(NR&+X zJ%P^Uryz6r^?uRT}>`Oe)LD3e%hPa3Y%Q zj?%2%$eTTRG?ZE7FnF_dM$;!VBmA61>%>(iyJPw}u{%dUD@HM$L`j-Wg3EchM$>|+ zl8t6_vN3`y)H*dA%7)W>g{sx*M%f5{|AZgpvS=w7BW3DXItRjE6oQjZHM|dLc*3{F zwu1clo`^~#>ov)Mmw%PFnA0V}32re;^LJeE^b<6@iy1$=6f`TmagyYhssGg0+7nb{H0^Se9lD=Nf(5Tr`DNu}qmAezmr)VnX+>k&K+_FNG@a_uGzKMGus9zj zRn_4ZW`yrN6347wHpeY^8WMeO`|fyZNg(CyN%R(nz>>+;z`-+lu>$MpKmC@F)oUp8 z7z7~(SqI{(WG<(0aSjHw#+ts-xBQN;g1= zq(j3mi#EkskaZlYXh1$A!qBH5=5wdAVTV^Jb*RKO`!_tUwWmTLqaYW7OjZ@2it9=H z5%X^gmIw~;0B@jYM7p8a#4Sdm%1*JZ0iDk@>&~a7ez7=9bRcGH5quYkn%I%FQvKko zDC^Sflt`NPBkDYhmIS|n=fTfYkT(208QH_nqHe1ajX*}x(|hfFOaU*}ddCs(?R@lE zk~}#im1Pvuc=`s-spgVOSr<~&;`zjKd{=1b_@KoPbl2pEw;KGkjB_ntQ>*2r3BZ)! za{0_O!aTo6zlB05prZSWrhur)g`{EUv<3Ei(bQN&J4-{vKr6o!6&3RPZSV=RT8;bd z+R4yQ*mYv%ck9}A*BF*Ok^Wo*Lbz&l`LTru#L~G@Lg+N8c7$za-hf4`@LOV8Fs`@T zX;o_$f>FK6|JAj6gLbPCLq{{FrqMD41cg&QQN*F9U>v1It>eOxa)VKnkBPaBMzbL|MT|pn4kpF+8Oc^KaF~`` zjaW6t^gF`I@xdO46$)lY62TLp=dL)t&1jA5Yh-KyVWL*W?MkTXGS&!N!%;dDYP-NR zZ0l2qfx+7N4S(m_$+wq!Re2OtxZCt|StH1M8i6b;wGP8Y6~kGEV@YSM>i<7|Ne-c7 zOBzLa1fB}+H!5HNBd{baFLTnVd+~pfLm&*V?z}c)=a8ywi>d6DeK#x#H#(X5BS!N8 z`{sa4`djXWh$6-1rYK$0W#z-~s+S~ox+4V@6k_b zvt3tbQK8}ZAQYa2`%|j55;o5jVu}F@z0SabSdC(bK-a&jgDy?f(2C93SrSB(OsC@! zV}i2x>t8uj2?*+>Q=RX2;s#c6wU?wWA#G}edm5w1Pl&379x=^HSm?%KXeB4K#b}>_nzWo4w&u*l(0W*i;u-ZmVJ;G3a!l_w zv!w2za{Wa3MDc`rSsWd^JYG@2nl;mp84m#Iq)aHWx0y{8JQFCnK_4Rva+yuMeG=We zB&CVyIc`88ULIFwGPpFGOc)tLicSiYOyZoHt=8XMaE$EkKt=^4&fLvB?1}CL(xdo~~Ql4Xsfre$?qMj#QO<{xlBRAEKb(+v|Scy-89A@)-SAVtQ}z?N_em|2i+2GaB`KQkB^4=1Fv8_ z`S0dijT=7i4MW)f4)gF>&WXEiQ_V0)PAeF(k`A|?P8uEuR$1Op>MGw00&(6UqCqNmCb0yB zgK%1XU}o4rW78vWbhp3>x`f+2Jilvuu0`U}WIUNclXA}STG79-px3`thHeb``=xPR6- zZ2>1F$nM|$8lI;PgH7_Ta5{-U0AIf0wR-X{*EnyrY%6@o47IR68DiFQ{)sIU)pl;r z;~CxS4}he5!~vb}HR2|F1Ir!ayXvYk-|5d_fRBjw6{{(+5Q<5%(WiW(ASZ<+v77e+ z;0L=UeD@EI3J6Pp1~7b*w4*L2B~UUvK&sBt1SWhWq`fpNeV8+SqZ%x2k1V{KoXIC(IXJx3n2(ig$jpUAtnBb7kouTuWLs^ z341+by4=I#!awZ}d#K#HK&biNg9Bi@R41Z`BAtJcwX_45;-;J!7DBy);GGyshEE@b zrL)Gm{56d=#;25tPf59yE90oxEgvBjH^#=I^yBrEq6r$7=EH&#g<~V~)hHnY7nH>z zDaTSqz2ld1IbJ4>mhmS++VBbC zAM=A!{U0<%R-APmsNpz7txH-7`2+w65i9jRab6NNmLXzoK-ko#TV~^6sn_F2~MfGloa88N5`=ma4(X1>z_KJ4LzYYZRfNRs?=HA6KYF@+RUW` zHEKpw z9ZRZ-svR1OP@AC<5pAfT9SyuBZse&)O9m7Ky*zQ_?uv)>E)6PKSPkqfV=@^kMSWRA zGP#_@%)=rR{OCZB8BhL1=~-ETZtbJf`EkfWImZ~ifpTsVsUy z`Rv5cm_5o*SwJK$#G6ARnD?g|T9MEWjYgM^Iz~Sduu=N8f*NH7fq7JrQ!p%+!*61i z!ujp|W{ER)0%ZPl5b)i0y8yCy6%B4CvKe%4Zbt2DJNk5OZ8FSnCVG`Pz7zksf5Ode zF=+|>ZjF0Qno^J;#)ksVFu%q8f9C#y%V$uHuNJcl+GEAfWS0xO?p^j0Sa2RgnjtcQ z=~j&q&7m8t6zKH$fYn}+?jLZsRHR9?&hxEBa+tD)LOEIw3xC&^_pGK!I1HAD=ZkZH zhc@|H>Y^~Q(6=}~cX>zv!RUQ)c>eMb(osMx91HzRi&dv>+48tr}VD*VSI5=DaQ_QI?^= z!t_#QwSJ3LyUc(f{X=!AAyelLd-5&^oL^SBi2>YcUckipo zGnPfq_lTUaQZvi8merNVVWH7zQnU9L2)!|JxCp_fO>>}mj)Pvuq^qX&jNZudc&VG% zjuU{g7GW{bFY@vHsWCV>3h=VyoC$RV&BDUqyi&nr-|pC!3}UTHI|bD_zb{Tz$Q9#O zW*3wZy4ZNNrt`JpGDDyuP*R7)uXp;=-pADVDyH%WAJDaIPWno2HC)NZ!95_@3bO3 z6?(zWz-k3F#aQnZLluEPWRi3;S#XdwCmC%JM>8mF&sXT$CINfXjx|x}luHY~wWX4} zW|St95zYnYweOlxP#P2J4!Oc#Vsq*wkT%zXCEX3gAzMWN(p&~t&zuLM2STppa2dZh zXhzN~g(x-iB1$y9Bdj0zH62$f#CT?UI!Zs~=sDk`jZuym9VzMnsO9l42CjoSoOn*( zXx!=MG~X4u7lnB>FuraPT<; z4}yb(y}A&cMHNl`3%pP%fco-9lTvH4_Kcz<$!3O8EoGe6i?u`{ z8ewh1(8^@Nm%M^xbc+){>Mmwhspn+|DQSdpx>To1z#*!TI9}Ee|)tWqAK2Su;ON8E`a18j{&ToO_ox0|YTlm#<&!`Uc zxkbI1ou5tlNcaPSCGRSjp!pqlL3>RtRm)oU8^k=9#OEjNhCDWmQ^yB=<*T*zM`2%Q zX;4*s$d4j(^uk-+F8C{p@^ZJg3f{841-!Q_l9b|Nw_OybCk0!gYU}9s z$m{Vvq@ATdFvf#Xh?8Hf*jM^J(*!|&bM2j(;yRk7wHY5ElXEGQf6C$I$dgsV@_4|{LWW>w z{-eG1SA8{t1)=#kgn!@T1-tFH z6j|ju1V`_b%H%)i$3w`X@+i9Y?G=< zKdYe#WSp5ufeXYIw;~7rEYhE+Su@coy~YC2){}KU53|#L_=D`S2kH9+JPWw%r+P!H zvFxqmL_Qdi+|cU~5{;Kd=Q2lhc(zDrwYxr3L7`UqUbTU#8nW&#MUGBZV$O(x^l3K) zTRz9cB)QKfJt>492M5{SrtA2%;U)QX@X+TqL}iFN8or zvAj5#3%JnaduS^gMPI5-)*`eZTn+`-X_KBamgn?Hwz$IIoVtvZM0y&sJ~ZO%kiuGW zxJJJSkZnY_yJ<05u}Efy#trOrM>Qvty_SKg$#TG!IlDGE|Hc>G5GSIWExl8n0MjjD zk3^7d0t!TL^}ub1b+&|9{7oh)=b*lGvDzD&wKjzf&R(SKoP5J{vU)1-Ol4!I5${j5 zWyF{(-(pFX#^fMSc-?}Qdie-vkI?5y%573MOGxfgn;f{A>B4&($vPHl2%d$r(a2Kb zBbEn6dJHK;={B2sToGkI%>g1vRg98oR|)ptMuW8q2`i9-+()_GL`6jSZqg z>FXN$6wU90&=TcUcH08O2n`#{jjcv!m`F7`6MPee#Zus3*tol(v-`pLD2|?{|DM?} zhL_8bpQ_Bde22}S8r}SA$N>iWl51y4umcj+%mIB>4dNK;tHR`>%>0br%`D#QFc5r! z>#yjV8D$bYvG_@oloJWe`T~KX!50XaRC%d})ImC+L6N_&h1qH2e<1vQ@_hxwvgiWq zGTI4`y}Yk-+j;jJgn;6RV=8PcTFa0}EC%Eo3stV%_pu_M3}x?0FMmKf$O)9fOWO{= z>oMRu(z)Nbt_YXTIuz~dVP^N1&7x^d-JeKycsxfhSQZ(L`;J#Keab(w4vjC3`VrNq zr2i&va{CvYcsr03gfNXP&9yh4tI_2d2Pgk*&w`-7F2tMAtVGvKDCsi4+3#G?97&e= z=^NJc4ag+zhNqS1V+M^ugC>%k9NMUtnS1`a0W)(Dvfq-SoWd=o5UN*HGghfy1yw-6 zq)$$5t;wl39fX{+@eR^v-}s=PB)=ZQF;fXN92yPXJwQV+(!s*0jzN;kaqojkQbB6a z)FJF5b?7O#5V)2NY`LSF#cQH!(ZYp9v9tTht~8nuMY~*{y;)5r(JKb)cxr@5pC+f% z_m?rR6JlHC?5AHX<+u?A0{Rd=E-pS&H}_;qBm{nVF=i>3oVEu3z{33wMJN4 zK)EeiX725Gtct(Pit+=$YWi|uwiH=Wd%OMK2d3Ln(3JD)&<*_m#Yw>|pSfQW{y0K= zz*aN`$Lt&{dE_;DGyw^@iGz<4_qMmy=w>&MGHkP5p$Qh#sG`T3T*-QEqX3jrO`1DL zPdUT{ViSpge2WQe7bXRYg)Vh_oLCdnBy;${M`SRM5+`n|F*&Toce!bV66lDAHL?k` zd47vMcJXe%j%-Qesh-dCk4t#m`myY`{EohMWzs2!SuOX>%-S9T>pfQKGT*r-b~yj` z9hbe+x?sBS)}5ykP3op}36P5#taR=DHf!|HP`H2 zt>L|TB+*em)6I^kaNIr-_W4^;zt01E9A2^__9wuKGYa~5Kv;ypog^bB<{j_bNE}2t z@p?J5=C2l>n5}J=o|x8LeN&9^VBS!z(sz49I9tHiNfMVG3~CLo_0R4pF~}Z80Owb5 zl3C6fhj3;&XI!NRea`ql*5K0lL>{+>2|4foxB;Ec9jDVMoM^Ff7mb3I6*==3Y@~=I ztGBX=Ei_=|BrID#ms+~pg#j2Nfq)f6a@^@YuBXDaG2dUlJ}35s;9I#^69EVL;8IIl zDC&Y|4ptlu3CDO(jvzYg`;K|9k%S*`OzQK2#J)Cx;pR`@oN6du@`x5>=Djm%Sp7dga4&^O5w61r{{F4j1FS*x|yZ zf~8+_y(?N`4 zXLQ@TZEXTAeo~uYr`DKi>0U-@;g@Pk6qvCz1c#P*JpcTaSf}}K(H}B&!@G5JjsB*e zMdk#Cw%HmHS23|8^x@)Yyk5<18N0b-$Z|l-*vHZ`^kKLGMREFBqt!TXtx?Pl9KC!E zA8K%`u<;~QZ}z4`ydl4tw@!z{+AIc`$;31%jia4!xIutBGC&qCRGx-O=|u!IDab6t zp9`jFG3)n3Xj<9<>ty9&>6jyH8&bk-=zm_@8_whNdlcg!+T>z0j`v115hPCJWW34E z#ysF?y=>^OuT|~#Dsg_k=dFUoOKpWxPIa#K?uNo_@_*BQceN^vS+Nh;d17sUTuSxCZoZ*8?Ii1Y-m|;TGg0l z>hIU5IUBC3wred6bF!NgJS72Ghl;;LmQf`KIjZKtkE(@SJD5?$vy7@frhQIT z$nG`dZ37*UbVnq*{LHm9O9P$Pdf5wcG8hyyL&mVn0r9dQ&Ji>tE|RshP*OjtXk1c9HX{YZF8XgtToZ4UZ5$AGX!k=a1#*15Kld%9z_!sF91h>w#xvd{bIhgM86xGHE$Mf!~( zWcBV-O6=o_@s_2JD5)4o&ST|(OR`~`rkA(bxG&RC3oevWlLVhvP<|btEVMyp%_(S< zH(&tQ&~|I+Rjwfxs0%Hi@9^vkaFr7?RL{jcb$-9{FF<|l%t`5A3^Y|eo&$5oi z9iSVHtR8kiejDVsqA;i|g5pRf;wXz;B+M-cCgeFdl(CUE zyeJF!OiO^bwKBW_Yd<_=0g|6-<8&x_!VX>TbTG!r_sKNjPjlO#I-Of%qnMI1Ot zY`d1mZl+(q6cV+TY0^Suqzo#^!BK&>kSoK=f4fbbtE>hVTCX!{q0F@&S(LKCVQdn? z{Gx{_mrT>+gTo}Ujt@9zM9Dvg200Q?r;|u?>P!-@;9{LvLW9cJG8Ee46O$p_iTgND zLnrqQAX8+8^F&gG;HFk3c228E6HX5uQGj$(S+xco!aSd`!7_=oD(!&Rz1HWpa$WFV zXKf|F>iIjox65s8WGh&hUMGZ0I@p50DB@Jaqj}|#{C+*-mEA<0HArsKdtLu74}r== z`aSKQ?YR5LR>5)=ic6A8ic3~qdtv&YzL7n??%h`U6JPpMU%HdhApI|Qmp-K5&Z!W) z08YOt(9^f*Z#PcgYQM4rejMUjxc&4$BEru^>kZqGd3-=H5sWFA*m(>k>njA=|53@B zTlWTnvYyA1AdK{pLF7Y{z7n-;dqS1zi&LW?%HgEysAZ$XLL-h+Guoi*&NCSvpmhU{BYi;J-f5He+v9)8V$D||__2z=k>7Z8D(*rfp z_HP`azSN|3mzq%PbcT7w`?QFmCsN1P8jHgql<3r+5$E)FCQY-^WH(exNtf#JWM;(V zTcJKl)mFbCEbXgdYA}5F`SpVF+Sd#A+X>U=8xPV;Bx0*hII?s9oh+<%^56G2l3FJ^ zRn!e8AktS{4tC9XqxbXSx(G97J1MO zUEO@#tFFq-05->fBjaG=Z0-Bu~j>X=)i8LaM)yOXrhr%$Ezxbg?qruk!pMEe9d2 zeRS)OtJweuuLuA_qSgTs)tipgFWB}@S=qG8ebXecN}UC4r70&G_qE~t3lzHPAv{J| z9NI{zp~>;`s>X`Ulq&TQMu0ukDq|zR;wp_mYw6`B)syr~uG_1IZD&AvDoNG1F=}Mg z&4kd{N|B*XH#4VLkBfgF1 z4I*qEvc*yp(sSRW{iL(aN#Uzs7R7za&lXnv*h_|x`>+>%0|m8@_TDSKSa z?S5FO0-1`k>Y_|7*J`uaqfU4w5?Is_UlY|UIKfx;+)#qWG1@v;L&r+& z1=cWl0xX?x-w&3=`T$tWx`8!309GQfNVj}sU@@K?uJ6Z|z#>MFb7;o^SQqXMmf658 z8a}3A4FMLDvNu?Wk-TqOG>O_hJcuprTwoF(Td-IQJ$V+O5Q?1THi#>wp7&E0mijLM zC__>LWqN`qP7XZ5AUqC~gT)D9a&g1H={w&lVU#~DzurjX^G*cK&I`Y&(hPZ{bFvGy z!1PE>X^TP`NS`Z(CT7KVd6Ps`=dam$qB8%{GiN=8r!OGFX-9|4_U_5ILm0?f)(MN| zeL-p*w$0xV0jCW@;0K!tPesvci?ErK=Z_$VCJM*QZO%K;*VEo1lZjOVvPzO&Q>>yf| z)xHwQCkXrv7yB7IGr%~h%ugGLk7!u%- zS(3q2dTjwzU?)r3N=@V7lfj;D?1n)BSX!+RuD`1$4OY8-(A{eGoz3au(+`JL1Q{-A zxOvcEX)3Z7-N@bGgm{3HZ4>#SV0dV^AU|3R)3T#XHW{>Vz2Mxq|L$s#fN# z{5-5qEi-G%Q(S*6zK>?>umYMpbx;_f4>~Y2wMM(Y6?jYm`t-}qA5j*mUAeDF8omRE}b>~KY*%6a*d&i7G5F`_2R zEn6}LTDH^-f?!d*CFGCut9H1e;DC5%ltX!7;jFnZnU*uT7(@&HK z7%PXoty9Eab8ItNjWt|nW0hJ+PNSMjY|Nl9;g;F7vF>-EIs@SsHmUtecNWF()b@T9 z{~K#W->*enqJpG~3n^vj3a38FDL_WT7&>L8blMz}A>2}AN`AoT}`n9MszBYCG^e_;Rg<<-Wo|OdT zA-9l#$fCTbXDu=n_e$EZ)!jzEr)fJuYiYuWK6|=H=x3Wdf2jl_ZxnFRq_p!)0|CV$ zkJ8;FJy<=m`-@$MjeKKkD!<#_r3GEFajWmCy;y%m`s1D<(4kZFc|F|>SD05&>!OGr zshTie+)LCE896@SYz@Lh#|J;a%*ElSkp9zm*!lwn2rX6I)G5r$e;?wci*iwH(-CDf zExp*@iSu1LKovQ@@rbGPQk6xM#Xo7`U85)lx6c~Xh<21XoUiZNPIe`j_Zru-+|_Uq zqv!K=CWYB(Ris@*I~I+heb*?DMfKKml73QX;BH&2GQya*t3V(q8Dd-P&ZYno4sW(k zWAoyWHE!=zvra&QUDGkUpE(=I8n>G1_gx1z6h3oe2Pd)QKWYaG`{AQeCZ$$RN;gy} z@WZ#13qi=|g|{f`v5QmI1!=yeJSd~=Z!X_$FZ!F|TYpNzwl~Tl4*MUpgtbdOK6ZvObEp!1i=;vV_2%84>+*QlI8x8=p0>_Dgcg4B?Qlr!RL( zgr&`ksAlQX)!;pmm@_@W_8x#ISSUf@-YHqqpSdP7DA_g;E4g~*c3}X~TsR9$R*kG^ zIE7O$j2J#GWQzLh!>Ab1k$@P{4N#1DeaVRS)V*2bL=+VX5;7O;xjzmBS__pTe{laq zn!#8rm>VY1T)a|*dFaFyUjJBoWHcR1)ATT+;8EX$R(sCSoH=vm4&!t7J&5A5WNd6K zZFF+Tya1xsWDw4c=FXjaxLbokT5S}ej?5LlFrEBus@swBmi}8XjV+SQM}N3?2sMJ^ zvJzY2iD#F$%0kSC%Ud9A&;&^wKZf8EAuUKCT@^3NB)+>!8ip@n|6a7^7p0C zA_;=!tqlZD^-m)^4GyaBqX2|L{lpVD-36t~d7m~4-*iMF?{HyDU!JIadlB?A?W74? z!t^P$wy2epmP`}*TabZ<;taO*W)+Lx_#U0s(A%5~Y1r+93l;=POA#Tm&q1TxzQtWl1@9P~un{bc7NA8CF>q2-0l^2Qk6)Eheb6Q;sNj zkZ+ovk>4_|zGadzcj9&YT29ZfMTQ9;wHU7Sg~!)z1-`UR6bOssCZG)+m}wjF=IQ$c z3*9v`beuNm-~|*==lT3_cZMtER1FoL%@6OK>3VA^2aC&{Y{Pqf0rOLdodP-^l2>G# zZ*dG$gG4(FaARbPyBObV_w9=M7 zi|Qx=zAyGR8QT0#mJM43uwR49Z@1koS$@9_!>>v#tJtUVsbSJfjA~?sB0kd(8uP@+ zbJt(z^y;<2h@(H$EWQ6yDBQwl-7vEYA5DPDWNem^u-6PEctiH3Paw=UmU@%5a?Y}} zKJ4g=2p;v-uo=@^T$`es;%jMAmZ~+No{e8MBDctmy3Du9>CeY#<|ArG>?mW3&@^_w z+jHs`QkhUFSB!@cidJIIutib4rGC=4+cZQu-%q8$X;uiHbGod+sZ}f3Z3T!XcOoj| zw%-1P-psW%(PTKN42#goTCHpCOp2+!vO&IWR2?TKfm1pY%Aq_Hii}C8S{}cY;o02a z+1ODdr}C)D=hh0TRQ=+>HkAA0j;RSHQPLZXxAE3bmHQ*}mz8C!z=dDU#gAAVJ!g5e zNOH-X|A}0!ARAm&{_^7Hw}9^S^&ZvsSR_(!Eao-Wm`1=${Gk zd+VhpEVu}?2K zTdQWL?$$_=uw++nL-u4XJ~MYh@D8|K`d8KYBTu1jVoQA@sT*C+6(J3dgbb^w*fS!v zk?u6+YUMv8o&HJL(D{78LiRqUX&xfWbWSw}JtyS;5aGUZl=6q9BoySZ@$D&{ zRIFRvS2uuv6;O~3CgVnqnRXERem#t|ZkqZo!HJL8bg=Fq zM50)k4Z=+aW8%VDGMci*@ea-e)AZ$A*d*qL`}bxN@*bzMNuA+o%4f7CE~?O*+d`tb ze6(#tEDHErLyf|=c`3V*7(#f%%J?F(x7|se$+aXnk=0b)D@NhAA<0#d)-?+cm0>t+ z)3!N3#emJ0q@V~I4^$1h^q9}nmsubFVn@v*2n0eolOjgia>)9;vbg;6`wwcROg zxiOSKSd=GN|2z()Q_=@iidZhJdxa}|Ltf(H5lJ+VzG!*Z-mBzY?M!q&Pvk>TGK+l? zJ`sl_A#`0B3dzJ9ne#R=`K9g5i0%&tn=BF(>BScVk_#LwOTa+$g-B5Q=%+?H2;>_$2tSR$#ty`pId;;wG|wjs)uctke1ks> z!$x7p6K*KnN{L%dff@$Url~bRb2#*eJ-w32i9TjSkWYkH7VZZ#C{qDlWyhzW_|tzkK(zL%&*@m2 ztUDbyw51J9gA3%=;UG8&HE2#Kl^)Qv`7vw75T#T7Q$!*di-Sj5iO(G#}-}HWaaulqR-@l9w0yeJVr{K%Cr>d%kSC3;Gl(^;I>hQ&ew)~ z^0`nGSEuLia<^=At7$5OX(5VF?8vs=Cd}TNo~(+n7(@hXcePqEC9y# zRh7uTORv=xQ)+dx)`hAj@pniA+jK^w zv%n&M7SjZvmE3O&V`PC5Y5tt! z7cnzoLy1*PC$VwFA6pC5>@ZaVuDBOb+tO_yPhF%-oe5^pu4H&E%23aw0R*^%67`1|^fS|od z5KHSCw)4~R(kw@jFwe!-Q0s+wC+JAuXzXAPoI9pVh*<>I5WI&mZX}%kw9`vX7C*ah zDneH=3p2AcRoUe}lB2kaynOc(17ds4+iWIkyQl9RL|e(}@|GqnyR`?8Owc@ob%y{+ z@NDSuWzaxK^)A~plzYnQf|4q*yr$QpO6~fo7<^{iodg5EX+EDsein@*A)qVuuyG|= zH$QMDI8A2)$nK}{iGU+ULm}B1IdP{;=s*msV7Tw&&etWgCU_;{u{a@zQLvEb zX+i98fzHT@Fwyesbr2XKEJs0(im2Yu{>+PQ3yUsAMW>7TeAA3QT<2P^wH%W=ndrPO zU+v3LK3srMt0n**`d{=(FF(IfUu}0sg2Z)-;>HfQ!BOIK%F3g9E6~7S27!aUI!`OA z&K}weDs^-Fdo{`>tWHGv+~=`+Q8vR~sN3JKho%MBV6w6p;@Z$?9N5gNeLfTJuART* zLChKq+78Z`?Wq=28UXW+c6dthxuMzX8p%kw)uhDt8+`;( z2v*1OEBC;5#dI*mCLNeEMLd0tkR^rUG^$x1M-@fQ_VyF<#{S+$LGADH9QLHm(l^V* zY#aex)-?09&c~8+C7_--*Ot!>~T%6^} zkk*E!V~u~=^5%MQkg!4K6b_?iu4Z{P<17dko4bt-6l! zG2zUo+?Wh!O9IYb$4m)O?T&W4w<2lMd=Ig`_S6e&@`Y;0qmx)sTtStZDu0I40`=}JxaB3%V;T8E*t|E zPZo!vVFl}K7k9S2hkzBHy}Fh26QFOr!NHtRkeF2|t;<6fpOQK0rU>mhx2`?sIqeBR zrakxlx-@EuuKLb^nq?a0B&B&fPxB`xl^w#u+(fYX2Btv0p(C;6ICESD&g>*xTG2QV zqRc*bg&}SRSZPY4R^5`VTLd>Ar%E2>Z&FLh8rifNzNSptvwRS$c%cfFtAs}Mwx%jAx>sf`q=eR=F|Ln8VO5T%w?zTRHA{oUr zFMC$!Trf~khckCsV86l{Z0QagRhYir?kfhzTz|9SU{QX*ehVk#>tQwO`gaX&Ot^e3 zUx|M*hO4&Tnjm3)d4Ifa8uk)pt_x31Czh`lfKu|IlD*?%Nh=L|cUg}yt zlgixbxL4l+S33ziv%1FO>Oc5KSFl8Dy=*FbNguW`8cN+|=(pv^dG!&xZw(Bv>F1Sg znTxeSXd-L60XEftvpzuk`nihpYia{j73wi|xlnSLzP*oOHr+6-{WE#RhFNw)frinRAJOn| zJIMNIF@#vp!D!S7bxqf|}z zMR(tLJAFhfFgiZSt)O$1Igjex9Wlo-l>(;o>Ka+qlEOVCxNj~a-ypF&6rp6(1V5;h zMeZ&`qA!)3XTBaV-D5CXOh`{k7+n;_!Dych>I3ovN!Ttz3yQ{i8h60tE~p|kiG+HM z6Ms@J^W3MmY!TLy$#||h&t8D%MoPW&)Wpmz(Q$--y=#*J(}W~$B*^cRF6&T5g??n{>9HX1fYi{`>{8p?6*1C@1qb&_d1$z z1Xt^Czar^T^!K$#(%<3WRg9nhOp=dDkLK7RRawYP{aj^pI!F0BVx#n7tA}kqr&GQ= zEy{E6Ix&*IN+qr((O8&%U%+*?H>zo@f{=xiaA?1F;?CI70UA6Ow4 zkZrJMBM92xe*{Uo}Py!WP! z$#TbuKT`GDL3De>&~=GkcU)y{$>YMQGaI8kW5uPPN%Gk zTI18xoIGO#B@U0BzV`TlnV{F^`DMYF6^v8`qgHU(`Gk@9fEKTCexqA|)0;&*;aS^ED^uJ~uzc_%0Z@HXVIT}PzQ&_08 zK^}BoUJ+=GJcQk@o8v^q@>wIF)6QPm=IxjTJ0tCVmsccyPY21uG+U}GG)+=4Pono!C zzZ9LcWOZdY5vEccz|zSF?>o=2@R}nuLV;(vFWJxstQB(@CS^VN@A<4;Sh$Q3o2{b> zG;556w4!l$uJ$p;Upg4gI|`7Jh?zF;HQCY3zu{$w78cC+Dc;JzG8$kum7o2_Vw zqHT6Hi{$zD*(47*`)whZl{~Fux&_Icoui7SQGl)OcupJibwY0^K`!f0qjO1-HuOE{5n?)Hk8TQ`LF+Y@abRKo;NS9cKAdm(uzn?9(L#GvIJ+?afA<9#41t8# zN6t0Bo2=QfvRP)%N`7SB47+Zot(ynS@z(&C>Jp2E=?_gO{7qO7{#Gavxf;o;cFztd zz375oyb|+pWyDFbWs*kzxmz^Au~G}0YQD`14=W3uYW5_0vtH=r8?6o&eZI-Aw%K2H zFbPxF>n8O>s0%9Qkb@%X3-O)@`XOd5Gtj|ckR2T;p|&{hF~*vNzvN;{3gX-6I}CYR z?L=DLqnl#ZZM9AQGkGHUEmjS`QL1vLubPdIwIn}FTdXGXUPHOAUUOh5)pT4X$=$Jo zGxuWeVrWPxV#n2y&v1P?h7BJPR{a>R&`mi8EGX3!a2m$6)$|&_4>&hkS5w6HO`8!^ zkcP3#W~6oqEyBj0mj>9t*bb7SA23{v<_~^I0}wgvwyT4s=>2!&NzPhKYkf#6T45i5 zZnUYIm2vjpP062MH*S37qk%M!si{Mbos~Sb&=nIB7N_T)W_1b{6|MIlOKNt6-m|0~ z#Zc_n57w)^Ra>^Sr0PxS93~zTeakM9u^r()?jVF zd*NCX&kMi8?mMY<;LLO^hp=;9RHZ}it(83ClR#4whVGMIH&redS1_OP1UMQqwR$*f)q$v&=|Z__~Y<&1%C z+xb0qceCuJV*UOj5z=5k27*ntq|jK?Ma|6AsZ{#yru0~6l}f)$H>H_CNTx6or2Mb1 z*Sxu}rlR_csjzl7Sxs-aZSAY+O%H7iRGWLu^pgce@6&xX;mLZN_c|ld)qKD(P%}O1 zHE*|??3}j|)oY6X3*NzO+!pR1b(-HwoP_!3OqI_qZCDFLeZuMYvTy+GkJ@7oOuuaf z84!fC=flP80}$R+OI4}wC{-l$xh))uPyNicR=9^>IE^&|xN5GlsE*#Tn;asREWR1dTt{qQF!h6 zeCVUlk(}&UoJO)h(ZV8$N?XR!(uUh6nn4R%I?$?kv58dMNi$05?TTAPlm1A!{351s zhgwfRF6K94CVAgV{0Z5Q7HI_h#M6ef3-*gN&b|1RK-vWdM%v#$N=l1YgEUTk6{TIE zoXdSu8Xj1rtjaEa*eq1X`{}y1zl^k(F{lI4-kk$zEK`TYu&j;hpMpy4axXYF7Q?a_ zxH@B7Ahai+)658?J?Rq1unl8aZXL^FORVESL}j>cYH=7W4}UEX_bBBbe9W@6_;~&Pgde(P>bUS%L;kJ*#WODQ51ZaIuH~E` zBI60OR^h0kf7LQ=>&BIPoh&f(K!@Y=3lkJ4n7Y)%&Yp>HU&1 zEwt7Pp|)ix33)oOcOn6(uMPehFzr7R_P;10tmG_|**3Dr?hF8{p!1{%jbPKb(1^b6 z#+m2DIb_0cy#I6TTAp%r8b!t7wwO9C_f9iwrS9}`+)NU~aHc?{oCQ381xxTsoV$(( z@7I$EtSB=Y7kR6kg8cXL9p)VP?fQ7QEv%=MHE!*0}@L+lIfRtg;t8Sr9@)TV-#4nRcABi@+^ zs~{)HK=DmJ5;L3yT{0B9Lr~T*;)ssO>kQW{V48#U1EMnRWgN?)BVCL*^5QBL$$8R3 z)?|<<#{xJCgJM=^I&wi2#y6cDg^iRHCd(=7*oSh}8x=)jyZ}QKv{jEt?qA#rCq7pSo^I;siBukisXIyhX?$8o;C%MGZtzvk*mD^IAz7v|TBJ zIJMJ@AhsSBdO>Qgl0A|*PWCWjT|qWp3bDp;a!+Ift+b()5LpnU@9|Qok~RG@C_TS7 zY*J_zQfc&&ij={SLWQChLWRwYb3rAK_&65w$j-0JIy#sW5;c-hZnKk1lB38a6kS0( z%taZ^-aqXKD;4b=tXzUbL>JJ`0p*f~DITd@x+vS5ayUTMB$by-_r!4<$|=7aS5sS{ z#X>!p8K$e!8#P^pGINThAxYq4vZRFjfGla}7UBi7XuIx^S3Q!P%shxr-|Hj;Sh%jg{2hI3#u))pWDPGp|yazd*JAm*r6 z6cW4FxtF-$bXNr91#8w~0Yd|I-PDn8YH)-sVdzqR)Mx;vJlL6Kx(-(3F#Fm!Kz{uS zlLd#QaW2Zn24n)m?Vs68olGbXVX4h*F^8rd+#LVX$b@%`GP($KrxEsM^I^?$2c9-I zM~gW&AI50eo4tn@bj9AozozWHx7jhk-dDw8r!YBc1n`)%w&UvSZSFW@dA}SwQgEm# z^om1=e@!@ZZ<#y5nV)Mk_2Gu*`tZW=f^zlq02blkF?J@CrL_H7cE-#2e)1nU%u3!8 z=js%GE$0ogovCghM&{QJ*=q+f5Rq#An!ex5K$unrVyWqong=ux-!FoB=ED!KU*A<0 zaP*te$gL*@`fuyD$#?2L_4m7OoqXpu70mMmn=Yro)!q21$#?ZebiMQ8hrj=JvH*-E^f!{f`tI+kOH8n7cBra(HDh31L|a9c2?kV*^fTB$sI~$(E2Pb zh+iV>lgWD&)+a+99ndsjreD+CoFv2jw7j!&e)keJAJm+S?2PoxJv}%p0)jF>52vHi zMTd7g7?RHEP)35B4w)9$RWwC5e4S(pni?OZsfGKXsnNY@iWA+7LZhkC4|my(GP}To zDXqw#m-QJ_RW=SU>$P z%S7E}Ik8AURv_p3m>O80XYiVLS!97yfE|6DC`uDRxcbGsNA_f zblC5+TyS9f9)_H*kc3v2tQ0Ua>@sN! z2y>=x%A%XD?saU`Z|re|=x;{t&H8QuhqYk1azO36E*}-Rp_i^)e?fO{u>{jSj4N9< z?H+C$NW1J&lwo}YOmHMjVC$PHBCC@fc@wP%&$x1P@2D$Z(;L^#*U#(*Hq7x?>-I%2 z6fEN(urEy+kOFjiJ?FEo4D4JsX^n?j)U7?tf(6+)g8Jb<{ZlTo@jqG6o1di_|Np}U z-J`RkveBzvd#Jrdlk|5eJvOts8UO#o1v&`e4;S>9wguVI+=LeZNz=}vdZHo{!wNKwXGdL0hhY~lISXkA zvsK7{q6^Kg={~{Q=*5~KEZTUDOH}1v5M}{fk&Rz%sz!nYhm5?~UTER7Cy)V7TQTne zK{uZ54Y5zX4MHRXB_n&4@S!kT6|hpt7D~2ZJxSD=Wrt@Apw_8wmtPK&0;(Q9)&l*X zFT@>_Em$SQa>8==i8i?IA}8tVf8$9v7dAsdccHs`vio^TRWo~%u+_+(_}tOZKg_yd zp~u8!AdfGD^u6jEIx1Ua=ol8VKsdP9mz%@vu+uqB$dY41NY=0m3kJRM%~y7h%AR6; zZ0O?tDcO^an~VD=XA8Mcriy#rrJ53D$rFlyg0l0UAgmckMt$p_`36UY!3!6p@01KP z9-035pXT9t@DtMSnJ?sWEzQ{7^ImAbL2%|8$)EA}GEfqo;}Nz&Hm~g-qg;rk{nB3F zPLudIk(87Hsw8{jvgjm8@)$|zB;Wms8HGACCo4GerjYi^moO6nLtJvb$a~Pa0Ct+G z7L$_KoI9;qb^z$@nHS+b(y%l%AxE zehGB3aAZ1S|u3n?TmVK5PQHQ-0+oYj7&K zxl_$*IcCsAU}Z)oLR6RtQjr#VCz!a7D5b3!@|_I+b8Bk|PZ)0)`PFgn3}+o5dT0M} zVNc*l$+If$35|a6mzgCjw&sRaV(1Y;<(bHm|wFf=6}G}=xRnow=rN*xDN>mN7HQJ zUy#i&EQh|%=M|x_|R!457!&h4a68U zIx$8kibXwXYE4vIlg3yGR&&_*H#i5lg@1fm8A8XT_P-~U}}pS{ml)v4}uC(#a6 z?|s%)>oe`E+Jkc7~FL>a$&Yw;?R8MAOn%kiU@uPGB4nTC=k$+iqIo7nv0nwSO$ zm}tS-lB*e!pp`5kIV{F&v`{xmLBH4H-Tbb$fA5ZW^4m!g%GWQGB)nuYE=dBykx3E^ z^Pi7h_TyeMXtPJ*29g9w#PhP(9bpvqiVR_B<7J4YpOXdDi4c{$T44URxl3yZ?gAmZ z5;5WAMF={1Nn3_s>TSqw2{swB5!v2hGZlzVD02Xi^tQIB!O~jAZi!E_Vueajv7BI> z2xMRvulb63@?Uqwl1ThI>D8doOC$^HDYmCTwx@9b0oxNdbi{Q<39NX@ME(oOfS&id(YP}U_!U`u2Wx`6eOIQJLpM7CvVR&#lNnwTQ8SfHS#vwFL zeA!%BS&*=@uzg`g+_SZ^Q8Fl_zC#zs)Fl-pTD?z%z zrP(%PjJ3@eXme)9=oULNKQU)$kbgFI!3#M z4leP@{xhND)XW&Yt@R2WTeLWe8G}EBj!_djSTLC}Mx7ZWI~xD286!7*bQ>+UZ^m%f z(n|u{#Eelz^y6wykg9KoeZ!W`%o&5t8khrn>g z?H@&HW8MNKkbj-DfeT&I#+U?+icBDES$ks~+6A_K4I#)DW6yU(h=+!glr7l8*DG6$ zb;=g;$(lUGsfUsx-q)xVC5RGtxQ5?5(%0PKqyttLhl;auG|dIaIoRTh9Uh8`W9_5D zY3vC^NSTLXj0j>_JAOB@cH*7yPU^BJG}`tAZol;&ib?08VDF?n6jR8_)1|gOft&i` z>9###27AJ+unT)aXtK*}!JaVt3$3{>);`z8+UJU_JzS1wGkd~pJc~VHo**#(uqP~_ z=Z|4e*k)_JtbL0X=dhRX2Wy`*);@Pvf)9Wn*c0Y?5g%SLH% z?kt+_Z5x?N2n*qR;vGL4SAQ&C&g=;@P*t-hm`a;_D8izL0!hzoYzh)zk7o+@gcS+7 zJ7rIpjc0C?HwkXC_QlfXz)H!2&Eo(6Y4%T07jYmU|=spcAVZ{Ybo`XgAF%CK%0Xwqn?c znZQ-7%8Qvb!dKv{n_=iXWkXk z>QYgemrfN`?qLp}vn5kFAFbov_hYww;IV67*D`5Cs>b2Ti3K_M00wf#NPO*^gik;Oq7mOqQudT5EP z^)T79)_T*{`}qgXTI&JjXTw^b+2~i17o6R`pS6Ak?%HLoFLK%-md*g~YJ`$@-7M){)*`TFzH4O9K&A&aB)b*gng&WwP8{If6aN*jqIo z|J=3hU(bSc`pkRG(A!|*KCI!47?CsocINR`@JNRdIX>;?;vaIp?aDUH#dhQMN%*#h z2!HFb8=r);T45qmE}!l2H*F3r=#N`Et53~t+}m2Oa@p;y?rO1uD#%~aSuMMVQh1V1ZO7y{4~ILAcH_;f z9*6oNuW>(ueAtxV#bQfL0L)2o-GMXMBlLq za9{x5S-W!_U_t5(z++y!#F3M=J1fwAD7*8|H|xE2=TYp=9ov}7YtrE`(Z~ecVR^pi zElNLYdEUy--76fNwLCwcEzd1ydCqLf?wLsfou+3gj6u-aot2eV+;sZ_%mVGB*g7rH zyW1A%R%U_r_4KhoV|>1ZfM~No69Fx4xrBqH+XB5ClI)_`WO3eY7U|pqoeX7x&ZF2E zq(xq3CP*I-FOLjSY?=Q@zYq${Ph$aWcm{rsOU8V_RfHK;?g{(qveylECO>B_-nO_h z(b{BWrrPaH#*&*1T?!>Xf}e_Zgd59lRlDVH2ciu%&&#grWvjZJSM6im2%75`Jm;&H z0jAxViphBPvv)ZcT{GNZik;t1@AKTI3#oqdYKx1D|3;FtT&&&J_<(?(Z} z{r2PVZD(I*&)+-_zmH{K2${(*T?Tz)ak=~g&o&9hqQKl4qp;XF%Ca@*5dHl%GcVC# z_+iC<^V?-=Xn(P}RMGyTBXYjDmv6!E`mCAZ z`D|wJ0dASJ?4B8$?ld#F0HRisl^x2=u&qG1A9g^f3f1cM1a>b#O8fe?L1l;LM(}frbDVe$2JVY)UhoXdfp=oW>A_&EpItY~! zBT{mwNYVJfMmi$N6=Y=RHxSw{*F=7n&~m!?Mi=5>0T0~TdF}oni2H7;!xE8sA^x?V zF4WHpamIriezHDp!3}n*pxaWXa|U}|6(){YV1ptYbQjvmY^l3D8P_H{9mV4g<xE=$Qb(Pb&NiEg1|6WvHWe+`3TJ7R_1iu|#4 zZY7?#t#djUYDr|W#9w>jvXFjT1I|`-bMFoIy8t{JIMD^g-EzC)9n92x%S#WLdEb4@ zqPSKFt!Ask0tZ&e&&}4=xPArp=3Ta3Fj=Mav9G;7nZ6An1<$cg(OJ}E5g=V<7bdrd z9@AY>?}7QVgEAh`<~SefkZq1DTj2b*_m3^P{bT-i!Puc?{}c5HARN*u+j+mB46~j0 z3o`u9`vu$E;+*#!OT60@*VLmFH?N783j5Ef4;IDNL!>0f16(bG=mTg5XY)MU15NYuI%?!U;jJiRa0#>p6B^^9RG2A7c0)1X>uzg6+Cq(5~xweuUT4lZAr!`0TE0;8B6`T|26ChLP>V&KX9Q z(r4FoEnd6^`a6k;uy~Djkq+E)J%DfYH%YhoMrVwhwt|JkxeeZB-R2$HUTcwjLXe?* zoUf$~*V=c@e%BJN;3eB@t*zBQ^v&(NZZSc5lv|&D*4j4Pv(I|Ftc0!AU&sDp!h9sw zokmu|cs2GeZsMk63x;C5-8pf_F_h_RVqKS=ZD{UgC~Rl$WhjYbC@sfOb~uJ&M-i+d z>H;ylbnw|Ob`?U8ZTsR%)~QjAhYNL{J~J>5VfU&Y}(p%-_Y4aC7{C zq0I8g!;u!HftW&s2C@`SN(0FvZeuUsOL9UWlLoS68pw`J1DS3M(nD(?(*+G=*^ovW z$c#i^&W49DaTUEC3%Vi=WI5A7b~p`$gGFd^rLBSZgGOBF@5}%VWQWs07Wq!ccr4~R zOa4X1P_mSqp=2pJL&;K%p)7W2Amj1EH9DJUC!AzKe`p}P;sw({7J$KJryJ;B4T!Ml zOarksKt*v{gw%r|XvJ+#;x!O&VvOUR%+x}rulUlL=Gf*Ui_$PTY??y>CDktxSLrml&XH{~9S{kVtSjlsbxj(aRQ?qL|fi86u` z!vNw0x37*&Ikw>^?@Y6e#V&PZN|-#gU3KK6{nQbif_-|~hVBwNtB%+y6&z#Td+gL% zbp(+U%2_(ohd@aS5nYf*BGsh7Mxw1aoX0(sM&ct-cX#st+eRmN#ShFESaR z%}a8P#9OmW8S#u@w__=ULJ(7Nd$Q(s+wjmPo7s4Yd%KerwnvJ4?0}UhYF8H}>h}y> z2aSf)yWBpWlQnL6jb!oJ;U*JXaFa#HO>$j?@P+N_BDSRWrEC z31SfA#gkzwUKjCmc&3@kGoy?ANk67C_+-Inz{aJ+Ua)a-_R(1zmp}XHEK_kRNoIQj zFJ;Pk)|d+IZ_ZTOftP*VBc`wRJ6q7XN4m{iGI7b&HRv$GK1_uToigrw3l|ouej7Ts z9cd}X^87J%nc=_FYofyhU5Y>Ty~k^EdW~GW#8Eh75AzC)o@utxwJX(^lB?- zja={)Q&YOkYG;jHMUHX?ja(x#a$ytPiLFQmFHwlx+_icV=C0V8yIS39iq}F~X71XB zZ+s!UYtMhtWAa)NkHr?%cDkF%%e&88PEA3@F*SvggE4*S0O44cFK6dU;>8z*=5?RO z6bU239_bX59Wrz6*uI%-wZl?#8eJu6Kd<|lFmvG@!2};;?UG{BY34FG@%qWXZfniuI!g>`M)3r}F&Suea!E3197AA`}^W~WU-@s_XBS<9F6rkr)Bw8Md)HT?2ULj8GdFEcx71f@- ztb66b{#LNl$Yh8eJ{#(l`&G_b!MOV+3&8>}JNznV& zuR~0jw~NI)#C6OzMxas_3igb~e)(GvPNPu_O-r-U%~5QEws5U1bVq=3bH*k<`@Rui zo0-N49Nw8-z;N;rV8dJ_aT+$HJTnnsAL_?VDyLm#IqOq7>r)B)g#kaS+(d4MXFPCt zQFf;{W-;Aa;IQW}g93;9dr`QBFy|#WL-xQzO6j6Sb;TAUT*{frDq&tFLcX#T7hKHQ z`Rx><2AmO$n-Aq?Ia&Y8nF}NCrgXUBnIre-!}m&P@-tf1VGk>&*|pMQy1TGAb_Zx0Dtg(AJaK9eiF&*VfqyZ@5^z zRZv~|th&O7`_^5@Cu`P2f1SZ85ANpQJo&->exJd8=(#kwJ5OP7zuRYU8_%V|-Fpgy z`-47%``~kFa3@Y-aKF=Ma36Rs4eqg17~CKB8QgC_mj-uy_0dWSp!O2~GLI8)5tVbk_Ak$7+X^SX@sc+RQr>>nEpPx~@bjNA4`6*OqniTveyk zv<_Msrs-&trn#>>jo8<@oguS$L$yn(fkS2jG0PFbmQDC#HlgauiheLt*@O!$Y=x3Y zk!fU*$rep^yRvOIJTehQ0SDMbsxNw#ZW`Zw1t)8@pDCA0$HE&L1!+2RLaB#AgQb!k zXNmND_YH}^rqlXTxholI1ZmV7RUF@l+q$d63$$H3;pX2f?OlLbKBOJoWA<5W35mn4(ONtTe9-5ZGiW%|U6MJFt{crY8O?R2>#e>XbH82SI%pL-sa{Z8cKgIj zYM%`#M|4xqdrtwnpNK)@VG{;sl#k6{Eq3;C4jjRSuWY zF>JP^&1Cutu6?P-qXd1tJfz#IV}v`g+0Zpg8LWMwF8I~WzGI3~9AkSKyQ)#(nhK;` zZ%xEgq;uL&-Sv~b>$bU=CA2vZzBWH$q$(D%8=e)`h%gr3bm{65wps5O7dJ?Z*bcE#GCmBX0~L{0>U6AvS-j)M<90FS1f zVxBDwx|B$P(28{g#du3cIAI2o#FbF`rzG?0h^;9l*MVwg&ejl}OjZnCP3$)Mj|1!O zO~;S9E&p{^QHm6c-Dph!hm&fX6$`W!tSv9cj%tD@Sc*~hF;7yy&JMJ4Yt|Lir3^t_ z)A_rzK8RGbY+P&29%%Nl=>*;|ZfG&5btuy`m9oOPBxJ>4YnzsKR!cjfRh$3-Y#5U< z&cjKLmKhTWHu-=KUDgQe6;2`2ax#c?1Jpnf4M?b30te&khYqf=H(|loK`+_;dw}%& zwW8WTAL(SZDi2@7S5th$h1-<`lZEO^;neA@!AQU`dw$$=Me zCGaAMWjOExMg?A+E%8i)7v6r|^wbSrz?HxY_~`^M;7Z_S!+73-muzXL0lco?8oYkE zQYypb#`{pP446PJN69iCCbGi1mp$hz-0!4MVlH(Wg_?z}syN?hdhF|yF>Pq#GIht# z60UnOX68?C&bgR)7k!3{`Kzd_o%~GXF^KK1o2n=Yu5eMp{Lsa*F8Wt)HGs{Dk|%pG z5vmv2YsL+T)&#IrF+Rr8t^j;Ic12@cx6wf7bm}>T@d|hv^mQF#WkeV|@BTnw2_S}4 zX7C6*Ur%7&HCPSZYQVx^Z4*XPH)UJ}VgQvGmhLfcqI(>|wk6%8=&8`XetD`W5zjwVr2n(mrA_z`M80<#`YY;%me~t(ee|!aGP$2@GU&0BUo)+58&(f?3q!>6D`nxe67(5@8tVT9ibYrZ0?a;Pr$1(@M&O*QlWD}(VPAG zvAU7m^q&BYhwWKn+Z;MoL&>CHs3Wd>4Ka6^xR677Nnlz@uiANX+}mp{lS6RtQ`B0H z*_3p_q-F_yd^_510VdO4{Tl4<GAi3Wgty zKw6S61zt(r5!yFZN)_abv0)G*4K6q3Np5#+Hx(~nj6kkOozxK>h)3^|I1z}OdgV&} zGGn3K_?e}!(?sZ1DwHe8is&G8Kxv(V1qMBt3bEFVGd0*H2P;<587aDVGP0OG zO>CYR5AZpbDFc^N%Kc$EylBUtxIz$w5>bja?P7=hn= z4_|_nKMpAjL9SC+-jniSQh8C~Sx_2QJ_5uvpK@MVnYM%a*com5^S)^Ur-OR+j5hs6 z-?R+=b=+WQaKgXfo0fI5K5|Bz{s-T*_%tq&Gdb!n`KF=3LA`uNoBmthw2U2fM3^(U z)_?Dtmi@UNozbSB^i4|=Gf(lEBPoC9n;xTS#+=69F;%;_8ihAR9RU{^72NvSk4}a! z)7>MGv6n@`4M}8j?06jA#X-}d50ogs5*3G#)AYt)!;RO>PWbYk2ZZ8r6NnLqx2mSv zuNpcaO*3zLh>Ax0RU{fuWHXhxNK#9dgxuU`4RjvXsveONuoIZnZ^i+Wj#Z>`jb@pMR zUmuj%*#}%Gk{wIJkIp{0oStD!_EaI=TU98A0*Wrp$SdgpKUU(~1$F5Q(+z{Z-|%kV z)0El^4E@`G{*@a=uSB@5y}ul?ap}OI|MjI2K7=9CM{TmevBPQD(lrt+Aj&SjKaei2 zgmXr!?IgRXFU-KK-qG1`_?1Cl-0L&M%Q~uhpU)7d znarbTw2qmrTO;)E?~6h%&*%d@1~y5lO9*eTA47l0HDn-A>;YLH8=Upg!C8MiIO`*W zvpzmJ>rVz}ofw?;M}xB-E@ahyX7j0w22EY_uY4^Mw2Kqh{M4X~zZ{hD7lSe$8IUjUKxP%)-Z6ox8*}jlbR2WB@o8u3p1}k-BdhZ)itWrRu@22B22D4d%*Erf zMPrt3SQrMEJ}St5cvcVj!soA9mjQhY6D!w|jmh>s&edQ|mF5P#8rCy2 zhnuB*-r2;t#D%a9=qv#mGLOso1ii2FPun~`Ci6IaMt1Q~c5z*3SdHgoc$|~rQ8%Sy z5FeE#d=$4Q18p#*N@fh-$YZ)7)j9}rw)qPNa0>lmUVOw_9=nFHtz;jeMp#=svp*tG zO-xACZQ%Es(`po=w?T#Nrup3!^o2QCyT?Z@aEi^#BBA3HAUFcGNJA~n z>j4(9ygKjxm4t3`VV@qlLz81kMa1R-`qlz7!!cu3uH+GJB<$2D^cXY?fo5E+3c{4d zp25TwMezz5`6dE`=4HoR0x_^-&hw9tQkjdFWXJTuP;(l{96}mF*tl1PBgFF3@hMOk zj}noh!lRLRmKm6h75Zxa)lP3JEi@R+O!HmBBMrH4WGFWWDRDkSux!SYu zFzCb8@Ei1UwJ||6eq5pDWGlNmZ7QA=AXj2;L?Qs$L3as6?PO*zhU8*?g*Hcn zzJ#$ny))uyGeSsRnF^QgN$z54)lQbSMbK|C zN@94~5F`>qhH0AwJcMirE)6FE7g31u&32(sj#OUuePG1G;wLlKw*?A$gAU@L#H=#* zNC}Q-GBCcIoo$IqsVG~0rNhx}p;94!9#S!^nsug7i5pb6LZvTtMFVb*O53uH3=yo# z>WPw@N!To=@-9NQ{YFXNP&Aa5NwjWvNzRm9cy()hON4TF>JAhP$I#bm7Po4=shaU7 zvvE{&p-{XDp->7ryvYn2od{(?+VZ9m4LIxL{-pC^LV?>U1h%S(>}nw*yXrU=5!ux& zB3n2Ypn-+GGGR?h8YnKZrHms$s1VrRP zqBgC(9WtStmotY{V9J(Ah3KduVnsO#vGAB|U|Llc zyo;&j8Ia2k8_7Rc!i7FUA}q&QMVutqwfnoxYjPYI+Js0ffJ}(m^$4_YZbFQnVG|-c z6LvsnLPTd8-h}9k8tAE*;W5HHbGrri&Pi}Q31i_#!Q$@hgzq-gVNOCB3WpVxFccPb z4ng=Jdo+eZ0T+hC;ee}Xp6IJn^_mHX?X~k2$V|8;N?;~buQ3x2*Xu4#X-gyF@sZ&W zpksgfsUU#2p_hh1fai=2y@&vN-NyE|wVbW&r-%TNH{3yG?dB|we}i@2tsPUgWF+5Fs_A*vKn@FG>H zG!jNgMA73er$+tG4JLvdA{xRICE)Cr%dRM{O_QAT)Oj zj+hZOBKlagnL4znsIxLIt_bz3`LysrN0yaaCC00>%hU@$P8+ypNXBgSEJ&G~<8Qi@ zDaMvA#VE+4yV_?cMF?qY>L8ajh54bi+Z?iX@m6if$Bw3}*R{%pacVvn83oJ2Sef7c zgVg+;fkA#c?AUBla$|Sv8b=%t$EJ^LWpSNrWo_FCgJW7Xwf0sVrhx)bpkbnWYM7#T z#bK8GS_0<`lS8PpwFJabl37c^x~-)k(=BOPYpD}``>iEQ+Ow9Wd@aA0I{2&=P<7aR zOz}}dMcyBG?`op?eK(QrFr)cj3hzIx_u6oEY^Xk8#}5_ic*tV1u7MQzV?>&rSY)`P ze;9R`{_^i`9B0a0Bv;E~@Xe8@6BO=rdCYx%J$atsfutRKUh3uE|xn+K+<-PxFVcPoo;VG49S9t1VW#T)EsM4f#Xq`Tm9db6Vt zdGjkM1B=9uGS5>z3b9sutB9VVi`Y`J@IWq{|7+{%FOOiZ~51y;TL9 zpHMO91S5~5y?Tssx&o{ad?$m9j4VCwupShu@p`jl;ep504&xZ9c_Pn7TEr z8t(0WHExR{%Frb5gkWiZbUv#UrF-abY;SdoB@0P|X8d(~5werHS%gUko}lv&yp<|y zpB&G-)43|5awqpc1MG=i`L?_9TD>tb*_@i5nVp+oSX|n%ys}g1$n2=cTH1R)$q>_8 z`hI(ISP6@s-tuQk{H=D90^=;{0ZS5}1Iaf&L8<27xY5>k!sydGEx*|&5GzXjQA=#J z6PK0v&wr&RX4{E7l=ze-*4l|nN_@O45oQ~vAGgH3p#?Xp(2wf(EPFEj8GDLlwfp-y z-{1MpFFt4`$2R+7uJem~xBgqxuU;J15Vq&viO}j5L=jaDxlQk6W~QAtw^bHYkZwwDlkEpW>g z<_^oAP2X+LadM*d^aBQ4@3o{czgGIu%(^$xS;?0k)j))f^xqh9n!D-xw@VMk<65Z6?nvDyIwq@AK9u>t|)4JLq8g; zs^Acg{C&Wxs-?ecPfyq$v?_g%I%~|y_z%7HIAr{N%egc@z+1b0TD~9|<5 zgkQ-ojvG1JzDB=YuncO8xEeoZahJLud+f(Yjx&UI0`T-ldBXjF!nAgncYS8g`lK$1 zTNcfwD7vJnQ+wdeYSpvDk{Cpkes!C-zGB@}Inr)+O-V<7T0Gn8Q>Lq8PhJJ=q<y#QOaPUD5 zJpIvA89{;J4{G28r__L#4}_AERUMsBQ_uiV-V+2?+^@YO8XwIm!Hqr5`^;_-(AEl= zGFA5l1}|f5vm+1X9ohJ!?uqu9-5pW$t|RJF4^$$j^v9fA^9HscL|&?i+LJ2zCZE(C zjXEavkMprS@{#Vbe9UETb1VhPXb||^xt*qZCaWM&4Qk-A6WwF^lfh%@X`mp74Qk*6 zr_?|}d>hoj9jDYlLFyaSz*B#CDpOPt76&zO;*=UF$e4o~c>5_eP!LTAHSokAoXQjx zB-%j@Y@AX91wnXF19zNK1IW+)snSy)Zcoe>R7uiyUy-60LJ0 zrIMCU{T`ljeh*Kr_QjHKWKXm&jV^7t@k;ldD1|`?Cc8%6woG$ipnHe@S{|Dm3N+h= z(I5HN`_wQ?%gowaKF-Up70d7GDbMnFq6V%KCh7g>??k&Z4sY`++J)+=K2^Nwi- zx_b47LUu|7S4Rn&Tj|%US7*I)&8b-4D<X48-Yy?;p(jLr>t)jz9=tTOEi%; zJ3LX8sdjLuJj~^BDF>xtvnb`xwcHY(Q++c(q~E!oFf@jzn}N#yW}vdzK*92Wv|2Ei zpWjv+J))v;2`4(lO$+lk049y03(iSr{-ww2bj>teWZ0&!#joL-*m?P;S zvW4c>{>{4oiOm|&ki7l1b0D%?bvD4LWQ@7rEs*BN^YlHu+EjJUg(@g`5oWZyyDW}Y z$1ia1eYv(mn*X6(^DJfCjcXr??6ifp6G}F!(7Qf_BF(o6tRm3ug5VV(A^3c8@W0Ey zPUNAcsUkB?pbShoe`GKSW;R=RVE2})a&V`(&B09b@aybR|2sRZM13nQUKqWL+TzlM zrQ@pAE;Gfuc_|*G(1@6~zD!JX% z8N_zaihKJJwN0N%4+)`^a!zdT=K!RZ*+ZBXSRp>E8I8NXV5wj_X-x}vh^tKG3 zPx4dV8Bm;{?od;rjkOBXA%rM>7tbb*5&;%o%Z8syx}mV_YqiamDpHP#=fnlQv9*qq zBy1qO&iRsTTA1e*>Z^|yd;wk;dHr+RT=E4+u5In$m5Rt*re$I(R?<;jH_dsh^kP@c zxJn1&2>Q}mC9x{tti&sO16D2OZKvLXbF<0V-jk>)T}W1~i|$m*6_mxwo?v%UjaRRw zIo@B`qLB-Nb0sUAYPu5i__zS9a4XunGg0~B73tB2!q^9K##sBI%mT4{0_5%_#?A3rG6 zs=CRHTYwTXLR;!AyCtM#$NPhjv5nGU7lh*zDmDV4-JMXvie8%O7#GlB>RL_jBlv47 zj?)j?vz0z%&rqc_Qx6ftuUMJyzwJkUz_8UO`Qse3aS#G|{MNU9Rb zauK+G@wRx~wbud)OzJ~Wp!H<+GLB%g9)ZO+5fAayI-bBL-7@HKvy%_x!5{fS8;Bdg zqqls#FaS(vwmiV~p6K>S=SWYew|k;Hq9oRn#cWA>3*%Kc-6NV^KdAs$ohtBXu{xu1 z9Jdbh6)&7(8k3}FwfRvkaaOCZGd>(wUq!VOd{k|INuCCm(~4^jL}ExGS^oH{Q03rf zcWT(KCn`^u1xNdNVH!r#iooj9W$U8Aq$&8XW0%uMw!UCYS?5VsUKH+O7|@q-yIByN zXtmVI3xZE*p_q}qB`7(K+#EGeXb6c>w2JD5il{;n9^eH+#rm^~Egc0odIT(7sUu7S zR1TQRZq9^msje>QD4fLU13&P-pDth8m{dJ^&WU9djr<=oA)Q)mrt`PK0gr5IY)H)A7p)TCKb+ zfc(8UIQQVbo8rn#gBQ?)8*eHhFS12cMd9~J1dq?Wz}$JMc?9^Swy_l%0YMkvcx{sG4+ziHCkN8#vSc^%=4fjzPSOW%{gK<)_WY`6WU#E>uO3^UcuDYj z`!95l{NKv!M_>Q?k@D-uTvF-vudhb-I{tbwz58K$-$}Q{O}~?l|B*d+H!e}X;TfE$ zb*OPEja-jvb!p==(XLAymm6t^8X!s~F^@6>TfHEtCavb9;sKI{lDGjW5U{7|6#12O z>f+!){XePM2WjR%(5#sDf1uglw&(6k8ehxF4?(;ec7y27v^92m;GJl``9>;MfH`{p z%JK^C(be?*41Hqu>cW3BS7*ce4(k6n^?nESf6<=Q|1wd^k4KID?RL_qY{#G4y3h}L zRfE2W>fNCKH{KZZ#VPWa8+fdtuBPFo%XD^i8G(nd5pIQFX*y?vozq~~)fq`B-%e*f zY|obeO=tG`&b&D54CHlR<0Y;a>cdN|Ik1Jc6q>z?4UZWZM%V!R3~{wApM!*5)_A?E zMdNzKy7*I2HxT$F-Fls0i`Ow%TASDTo|(v!fx-E0Ft{M5_g@7%OZG7^*joCb!3QPg zF_8Ot8q{q46%_7-s2N`$;K9$Y1BrA491x6-1P^jkF<=lBUbx3HAAhmAeHH-<*K?B)6+La9sH z5n+g=N8SBuy2q@0eAoI!Wo&$Wd{6+8m(t0I=m(adcSq;F}SEAH8qag)k)l>&ag_|H9HmVn`PQEp+NrXlYM=l@!}* z`bfBb@X?1r>yJ)Ig{`I!k?`1?-*Wu;&+c>y50Y@B5;pz~3H5JRpMM?HoDTne*MHH! zSDw)RwT$Osd$vG9{bneSARQlRyf%GDxs^ED3Gl5#IZ@f_-{B<6R_tW)(t%jAWYS`6 zy2uuZQKB`NdUYKuTKbL%+Eh*N;cn<(I(De>?Mx7Y3Oo=)8^UFhEVJYZ^TRxPEm9T6 zYt%|v)wudVw1X`~S5l|A&Mxb~iSkvT8}W#svO7(Rb0^m1`J+mZ3~ngrq!P0N*F8vMyq( z1sH)j?=4-Wn^d)Dhacf<+_kEzjX~~@uBm@pjT!zBGyBbq=7jXS`Zp^{ll~f#H0iI& zC;c^=^tJY+CpPIx{WWgVlXK!^odG53|GnN?WdY*&94SPl193H*f993xiD{l(w-zz7 z>o;esu|{6D8sEffoMUB^)_QVooKP}ZTW;-%*XZxL)Ik9f4<~!MDLiShRuT$gNuE6R zvMkAYl0x^;dh3OJfr@{=tzGMaIF6IU$yZ@GYwe+{^rp3@j>c*V$;LWdir3Ur_?GMU z9)k{gliF@?NmJ+PoU01+^78ON{aci>=2OO%0w^1p?7af`*~>d;H=K8Pt0d_`D=*fQV#F`lhwaJE0~8^J{8rPfTme zd_xCT=)fA)9fDy3&$~R=WQyY##uvor^VzvfKq4R#SU0~?glT5fL_Y`B5)EZ=L0;U! zKL+1f8kdsqz{t{(wy3nl&NQ^X9VlfT<@P8IJD2b!t8bsu_GP=f95y8{aQMXI9?^aW z-S6c!!>aUV;3H-N<0T$@!a#UZvD8H-htxuMRJOUE>?XC)aiubm=a8BYFwz#;brZZeQBXgUi9r;Q@lS_Byt?CMal~Cx|8EdH@bo61F55 zb%0^0>!po<2cu*j1*itf|2E$3l2@G`bV=it0J{}j%~0=3#{D1!=~ck4<+Owv0JadN z3RU1&OiLa+t`GrXY=8MXANi~Deu4%s*RL+Wx^!1-O~lb;7YS3e#iOha>Y6zMKTk- zC^z=JsBP>aN5-BPX{lDzcTsk?%I?m~?rxVQM`d@rvhSqqn#!)_W!Kte$x&G-km|jY zvWdzjdD*00mK>E$T-mo%Rw@bqw2f1(c3E;%w&lv+L0PkE#Ch4cU6ve`J;#;(Wy&%+ zD^Lw8-|Pe9W5AaCU~y})Zx1a9wsYzAo7@5*v0$lFCtV*Ni2jHFp7kHe&1*j1} zh6YeK6R?`k82W-Y{gI;?!yt1F;El0->)A!dg_x=8%XvuR}}M-f_9!)zv^Q<{)@ zqsg`hB&h-`_@yiy8gq;y+EZGb^_OUQQ(3Hb_iTo8!f)New84Fj%1N|P;|Q)`v9zC^{HN!y{?)Rp`)ueT1cq z8649ajt2?jykQTL8Ii!v`@{NvRS*>_IMg_|{?+Q2bJp7`vmAZ<-LztK9nssV9PmwN zX0(9{e~l|-Gp*)Wz)Kr@ZI3`E(OlSsQR-l2VQ1iSh8YE>g=l&%z`Pg*1XLDcZ^%> zD~Jf`o2xGm|B)9Fk}vBCC_k+XsM5_Zfx+jLPEk#-WCA!~;O56BTf z5NVP*lS7RQDC#!yqP#{202rIU0fdoJE=L#&! zRLH!s$5_M&i?`-fOYaT26v5-FBKTlc1h1@$n7c$ikQy^As+Ur-3F44l)R5Ah%+S}@ z(i=a%aXd(0m_GWaJe&R+9#>G%H&z1C6rj@urt%v5gb7W5YB zGriSvvV-BI98ugQW_3t8BJs{*9qo?YW2xEwKv{P;irsCOy|ZuX@nS0D`Q)t+eAcAX zb2UBfUoY4K9AB|T{>!(>zc`=l4NZDB+3nun+SSj$e#@`m9z~{()z9{?U0byCvb>!q zi|tUyled2QCq`8xTVM*mc5G4Oi?_(%w?+Po@*bTij_~czh;NqsWzqd=WLO`6(p@~ z@%`>C@)Hevx3sDSL(m1yMQe+X1VrwmMf+$`AFbwB0~xbDsQFaIlU8O@Wp+9;d>VwA z+0#}tK?4B5Mkr@{!@&X!KNHy$Fj3@2a?CpFi04=o52xLM`eF?ls$V7+F4WSh{es3G zsIw(WG!AWC8a6U!>pc0;VrlAo5f0In&(nzxu{3xkQ1(3Wnpg%cC14Ue&xY^Bj_luf zucVptP}h?xih}7mxAHVr-@Fh*1!+iy?TD+r()sOb>hsi%Rdj#HublXfJ&E5KRPrE@ ztI!FP%kaE1L$`+M@pnGPy?T)cwPB!)f@~8(h=AzFTfQejK#A zAh-&Zk@_I_-ZF(`@9aI9_{Zcr8w;jb89F9PW&X#U4-%(rJ(%c}A_m-MV}Owzkc;(P zY%xgIPMSU!s}(v{2MAFOM@4cDfkI9!{Gn{6t} z>g_GXsgPHE5Cj->Jbu#;y~XP}hJ(yYB`qY{WJ!P

*mI0o7Uz3gZua^jZy08B27MdL_6gkI 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 diff --git a/examples/websocket-relay/relay-app/src/lib/workflow.ts b/examples/websocket-relay/relay-app/src/lib/workflow.ts index c32fb005..19aaf894 100644 --- a/examples/websocket-relay/relay-app/src/lib/workflow.ts +++ b/examples/websocket-relay/relay-app/src/lib/workflow.ts @@ -50,7 +50,7 @@ export async function run(workflowId: WorkflowId) { // Record the first workflow that ran if (!firstWorkflowToRun) { - firstWorkflowToRunStore.set(workflowId) + firstWorkflowToRunStore.set(workflowId); } // Set workflow status to working @@ -164,11 +164,11 @@ export async function handleMessage(event: MessageEvent) { const updatedTasks = store[activeWorkflow.id].map((t) => t.id === taskId ? { - ...t, - status, - message: getTaskMessage(status), - receipt, - } + ...t, + status, + message: getTaskMessage(status), + receipt, + } : t ); @@ -259,7 +259,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, { @@ -274,7 +274,7 @@ export const workflowOneJson = { args: [ { "await/ok": { - "/": "bafyrmibalyvlsj3zo2vdgjmvawdszh546jz53ejhud7u6lkmpg6mcvq5ja", + "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy", }, }, ], @@ -282,7 +282,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, { @@ -297,7 +297,7 @@ export const workflowOneJson = { args: [ { "await/ok": { - "/": "bafyrmiepk7vo7qcndopjhuywtv6bly7ojd6gzmwlqm4uzsingbyxluytny", + "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4", }, }, 20.2, @@ -306,7 +306,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, ], @@ -336,7 +336,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, { @@ -351,7 +351,7 @@ export const workflowTwoJson = { args: [ { "await/ok": { - "/": "bafyrmibalyvlsj3zo2vdgjmvawdszh546jz53ejhud7u6lkmpg6mcvq5ja", + "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy", }, }, ], @@ -359,7 +359,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, { @@ -374,7 +374,7 @@ export const workflowTwoJson = { args: [ { "await/ok": { - "/": "bafyrmiepk7vo7qcndopjhuywtv6bly7ojd6gzmwlqm4uzsingbyxluytny", + "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4", }, }, ], @@ -382,7 +382,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", }, }, ], @@ -432,7 +432,7 @@ function sampleReceipt( workflowId: string, op: TaskOperation ) { - const base64Cat = getStore(base64CatStore) + const base64Cat = getStore(base64CatStore); return { metadata: { diff --git a/homestar-core/src/test_utils/ports.rs b/homestar-core/src/test_utils/ports.rs index 673fc0fa..f817b9b9 100644 --- a/homestar-core/src/test_utils/ports.rs +++ b/homestar-core/src/test_utils/ports.rs @@ -9,6 +9,6 @@ static PORTS: OnceCell = OnceCell::new(); /// Return a unique port(in runtime) for test pub fn get_port() -> usize { PORTS - .get_or_init(|| AtomicUsize::new(rand::thread_rng().gen_range(3000..6800))) + .get_or_init(|| AtomicUsize::new(rand::thread_rng().gen_range(2000..6800))) .fetch_add(1, Ordering::Relaxed) } diff --git a/homestar-core/src/test_utils/workflow.rs b/homestar-core/src/test_utils/workflow.rs index d03baf25..81d4b7d3 100644 --- a/homestar-core/src/test_utils/workflow.rs +++ b/homestar-core/src/test_utils/workflow.rs @@ -19,13 +19,13 @@ use std::collections::BTreeMap; use url::Url; const RAW: u64 = 0x55; +const WASM_CID: &str = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm"; type NonceBytes = Vec; /// Return a `mocked` `wasm/run` [Instruction]. pub fn wasm_instruction<'a, T>() -> Instruction<'a, T> { - let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); - let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); + let resource = Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap(); Instruction::new( resource, @@ -45,8 +45,7 @@ where Ipld: From, T: Clone, { - let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); - let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); + let resource = Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap(); let instr = Instruction::new( resource.clone(), @@ -100,8 +99,7 @@ where /// Return a `mocked` `wasm/run` [Instruction], along with it's [Nonce] as bytes. pub fn wasm_instruction_with_nonce<'a, T>() -> (Instruction<'a, T>, NonceBytes) { - let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); - let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); + let resource = Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap(); let nonce = Nonce::generate(); ( @@ -120,8 +118,7 @@ pub fn wasm_instruction_with_nonce<'a, T>() -> (Instruction<'a, T>, NonceBytes) /// Return a `mocked` [Instruction]. pub fn instruction<'a, T>() -> Instruction<'a, T> { - let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); - let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); + let resource = Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap(); Instruction::new( resource, @@ -132,8 +129,7 @@ pub fn instruction<'a, T>() -> Instruction<'a, T> { /// Return a `mocked` [Instruction], along with it's [Nonce] as bytes. pub fn instruction_with_nonce<'a, T>() -> (Instruction<'a, T>, NonceBytes) { - let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); - let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); + let resource = Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap(); let nonce = Nonce::generate(); ( diff --git a/homestar-core/src/workflow/input/parse.rs b/homestar-core/src/workflow/input/parse.rs index acf189ae..8e73858b 100644 --- a/homestar-core/src/workflow/input/parse.rs +++ b/homestar-core/src/workflow/input/parse.rs @@ -64,7 +64,7 @@ impl From> for Args { /// use libipld::Ipld; /// use url::Url; /// -/// let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); +/// let wasm = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".to_string(); /// let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); /// /// let inst = Instruction::unique( diff --git a/homestar-core/src/workflow/instruction.rs b/homestar-core/src/workflow/instruction.rs index 7aed2649..fbc11d09 100644 --- a/homestar-core/src/workflow/instruction.rs +++ b/homestar-core/src/workflow/instruction.rs @@ -132,7 +132,7 @@ where /// use libipld::Ipld; /// use url::Url; /// -/// let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); +/// let wasm = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".to_string(); /// let resource = Url::parse(format!("ipfs://{wasm}").as_str()).unwrap(); /// /// let instr = Instruction::unique( @@ -154,7 +154,7 @@ where /// use libipld::{cid::{multihash::{Code, MultihashDigest}, Cid}, Ipld, Link}; /// use url::Url; -/// let wasm = "bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".to_string(); +/// let wasm = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".to_string(); /// let resource = Url::parse(format!("ipfs://{wasm}").as_str()).expect("IPFS URL"); /// let h = Code::Blake3_256.digest(b"beep boop"); /// let cid = Cid::new_v1(0x55, h); @@ -330,7 +330,7 @@ mod test { ( RESOURCE_KEY.into(), Ipld::String( - "ipfs://bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".into() + "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".into() ) ), (OP_KEY.into(), Ipld::String("ipld/fun".to_string())), diff --git a/homestar-core/src/workflow/task.rs b/homestar-core/src/workflow/task.rs index e55e70fd..77e6e9d5 100644 --- a/homestar-core/src/workflow/task.rs +++ b/homestar-core/src/workflow/task.rs @@ -190,7 +190,7 @@ mod test { ( "rsc".into(), Ipld::String( - "ipfs://bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy".into(), + "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".into(), ), ), ("op".into(), Ipld::String("ipld/fun".to_string())), diff --git a/homestar-functions/README.md b/homestar-functions/README.md index c81354b3..4c030e1f 100644 --- a/homestar-functions/README.md +++ b/homestar-functions/README.md @@ -51,7 +51,7 @@ cd test && cargo build --target wasm32-unknown-unknown --profile release-wasm-fn cargo build -p homestar-functions-test --target wasm32-unknown-unknown --profile release-wasm-fn ``` -Guest Wasm modules will be generated within the top-level `homestar` directory: +Guest Wasm modules will be generated in the top-level `homestar` directory: `./target/wasm32-unknown-unknown/release-wasm-fn/homestar_functions_test.wasm`. Sadly, this module is **not yet** an actual `component`. But, we can leverage diff --git a/homestar-functions/add/src/lib.rs b/homestar-functions/add/src/lib.rs index 2c67e6a1..18bdd1b6 100644 --- a/homestar-functions/add/src/lib.rs +++ b/homestar-functions/add/src/lib.rs @@ -8,6 +8,10 @@ wit_bindgen::generate!({ pub struct Component; impl Guest for Component { + fn add_one(input: i32) -> i32 { + input + 1 + } + fn add_two(input: i32) -> i32 { input + 2 } diff --git a/homestar-functions/add/wit/host.wit b/homestar-functions/add/wit/host.wit index 27654fc1..0c2d6d2e 100644 --- a/homestar-functions/add/wit/host.wit +++ b/homestar-functions/add/wit/host.wit @@ -1,5 +1,6 @@ package homestar-functions:add world add { + export add-one: func(input: s32) -> s32 export add-two: func(input: s32) -> s32 } diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 3a9233d1..83f0b90b 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -206,6 +206,7 @@ serial_test = { version = "2.0", default-features = false, features = [ ] } strip-ansi-escapes = "0.2.0" sysinfo = { version = "0.29", default-features = false } +tokio-test = "0.4" tokio-tungstenite = { version = "0.20", default-features = false, features = [ "connect", ] } diff --git a/homestar-runtime/src/metrics/exporter.rs b/homestar-runtime/src/metrics/exporter.rs index f3e0c11d..52d52168 100644 --- a/homestar-runtime/src/metrics/exporter.rs +++ b/homestar-runtime/src/metrics/exporter.rs @@ -27,11 +27,10 @@ pub(crate) fn setup_metrics_recorder( .build() .expect("failed to install recorder/exporter"); + let hdl = recorder.handle(); let rt_hdl = Handle::current(); rt_hdl.spawn(exporter); - let hdl = recorder.handle(); - Stack::new(recorder) .push(PrefixLayer::new("homestar")) .install()?; diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index f18e4460..fac01e30 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -217,11 +217,11 @@ fn port_available(host: IpAddr, port: u16) -> bool { #[cfg(test)] mod test { use super::*; - use crate::{metrics, settings::Settings}; + use crate::settings::Settings; + use homestar_core::test_utils; #[cfg(feature = "websocket-notify")] use homestar_core::{ ipld::DagJson, - test_utils, workflow::{config::Resources, instruction::RunInstruction, prf::UcanPrf, Task}, }; #[cfg(feature = "websocket-notify")] @@ -231,6 +231,7 @@ mod test { use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; #[cfg(feature = "websocket-notify")] use notifier::NotifyReceipt; + use serial_test::file_serial; use tokio::sync::mpsc; fn set_ports(settings: &mut Settings) { @@ -238,17 +239,27 @@ mod test { settings.node.network.webserver_port = test_utils::ports::get_port() as u16; } + async fn metrics_handle(settings: Settings) -> PrometheusHandle { + #[cfg(feature = "monitoring")] + let metrics_hdl = crate::metrics::start(settings.monitoring(), settings.node.network()) + .await + .unwrap(); + + #[cfg(not(feature = "monitoring"))] + let metrics_hdl = crate::metrics::start(settings.node.network()) + .await + .unwrap(); + + metrics_hdl + } + #[tokio::test] + #[file_serial] async fn ws_connect() { let mut settings = Settings::load().unwrap(); set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); - #[cfg(feature = "monitoring")] - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); - #[cfg(not(feature = "monitoring"))] - let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = mpsc::channel(1); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -270,19 +281,19 @@ mod test { assert_eq!(http_resp.status(), 200); let http_resp = http_resp.json::().await.unwrap(); assert_eq!(http_resp, serde_json::json!({"healthy": true})); + + unsafe { metrics::clear_recorder() } } #[cfg(feature = "monitoring")] #[tokio::test] + #[file_serial] async fn ws_metrics_no_prefix() { let mut settings = Settings::load().unwrap(); set_ports(&mut settings); settings.monitoring.process_collector_interval = Duration::from_millis(100); let server = Server::new(settings.node().network()).unwrap(); - - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); + let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = mpsc::channel(1); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -304,20 +315,18 @@ mod test { }; assert!(len > 0); + + unsafe { metrics::clear_recorder() } } #[cfg(feature = "websocket-notify")] #[tokio::test] + #[file_serial] async fn ws_subscribe_unsubscribe_network_events() { let mut settings = Settings::load().unwrap(); set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); - #[cfg(feature = "monitoring")] - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); - #[cfg(not(feature = "monitoring"))] - let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = mpsc::channel(1); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -333,25 +342,27 @@ mod test { .await .unwrap(); - // send any bytes through (vec) let (invocation_receipt, runtime_receipt) = crate::test_utils::receipt::receipts(); let receipt = NotifyReceipt::with(invocation_receipt, runtime_receipt.cid(), None); server.notifier.notify(receipt.to_json().unwrap()).unwrap(); let msg = sub.next().await.unwrap().unwrap(); let returned: NotifyReceipt = DagJson::from_json(&msg).unwrap(); + assert_eq!(returned, receipt); assert!(sub.unsubscribe().await.is_ok()); + + unsafe { metrics::clear_recorder() } } #[cfg(feature = "websocket-notify")] #[tokio::test] + #[file_serial] async fn ws_subscribe_workflow_incorrect_params() { let mut settings = Settings::load().unwrap(); set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); + let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = mpsc::channel(1); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -374,17 +385,18 @@ mod test { } else { panic!("expected same error code"); } + + unsafe { metrics::clear_recorder() } } #[cfg(feature = "websocket-notify")] #[tokio::test] + #[file_serial] async fn ws_subscribe_workflow_runner_timeout() { let mut settings = Settings::load().unwrap(); set_ports(&mut settings); let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); + let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = mpsc::channel(1); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -410,6 +422,7 @@ mod test { r#"{{"name": "test","workflow": {}}}"#, workflow.to_json_string().unwrap() ); + let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); let client = WsClientBuilder::default().build(ws_url).await.unwrap(); let sub: Result>, jsonrpsee::core::error::Error> = client @@ -430,5 +443,7 @@ mod test { } else { panic!("expected same error code"); } + + unsafe { metrics::clear_recorder() } } } diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 4708aece..c02576b5 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -7,7 +7,6 @@ use crate::{ channel::AsyncBoundedChannelSender, db::Database, event_handler::{Event, EventHandler}, - metrics, network::{rpc, swarm, webserver}, worker::WorkerMessage, workflow, Settings, Worker, @@ -197,7 +196,7 @@ impl Runner { let message_buffer_len = self.settings.node.network.events_buffer_len; #[cfg(feature = "monitoring")] - let metrics_hdl: PrometheusHandle = self.runtime.block_on(metrics::start( + let metrics_hdl: PrometheusHandle = self.runtime.block_on(crate::metrics::start( self.settings.monitoring(), self.settings.node.network(), ))?; @@ -205,7 +204,7 @@ impl Runner { #[cfg(not(feature = "monitoring"))] let metrics_hdl: PrometheusHandle = self .runtime - .block_on(metrics::start(self.settings.node.network()))?; + .block_on(crate::metrics::start(self.settings.node.network()))?; let (mut ws_receiver, ws_hdl) = { let (mpsc_ws_tx, mpsc_ws_rx) = Self::setup_ws_mpsc_channel(message_buffer_len); @@ -636,18 +635,15 @@ mod test { use crate::{network::rpc::Client, test_utils::WorkerBuilder}; use homestar_core::test_utils as core_test_utils; use rand::thread_rng; + use serial_test::file_serial; use std::net::SocketAddr; use tarpc::context; use tokio::net::TcpStream; + #[file_serial] #[homestar_runtime_proc_macro::runner_test] fn shutdown() { - let TestRunner { - runner, - mut settings, - } = TestRunner::start(); - settings.node.network.metrics_port = 7001; - settings.node.network.webserver_port = 2001; + let TestRunner { runner, settings } = TestRunner::start(); let (tx, _rx) = Runner::setup_rpc_channel(1); let (runner_tx, _runner_rx) = Runner::setup_ws_mpsc_channel(1); let rpc_server = rpc::Server::new(settings.node.network(), Arc::new(tx)); @@ -661,11 +657,13 @@ mod test { let ws_hdl = runner.runtime.block_on(async { rpc_server.spawn().await.unwrap(); #[cfg(feature = "monitoring")] - let metrics_hdl = metrics::start(settings.monitoring(), settings.node.network()) + let metrics_hdl = crate::metrics::start(settings.monitoring(), settings.node.network()) .await .unwrap(); #[cfg(not(feature = "monitoring"))] - let metrics_hdl = metrics::start(settings.node.network()).await.unwrap(); + let metrics_hdl = crate::metrics::start(settings.node.network()) + .await + .unwrap(); let ws_hdl = runner .webserver @@ -696,6 +694,8 @@ mod test { _ => panic!("Shutdown failed."), } }); + + unsafe { metrics::clear_recorder() } } #[homestar_runtime_proc_macro::runner_test] @@ -819,7 +819,6 @@ mod test { #[homestar_runtime_proc_macro::runner_test] fn gc_while_workers_finished() { let TestRunner { runner, settings } = TestRunner::start(); - runner.runtime.block_on(async { let worker = WorkerBuilder::new(settings.node).build().await; let _ = worker.run(runner.running_tasks()).await; diff --git a/homestar-runtime/src/tasks/fetch.rs b/homestar-runtime/src/tasks/fetch.rs index cc752a33..54ce2e90 100644 --- a/homestar-runtime/src/tasks/fetch.rs +++ b/homestar-runtime/src/tasks/fetch.rs @@ -5,44 +5,32 @@ #[cfg(feature = "ipfs")] use crate::network::IpfsCli; -#[cfg(any(test, feature = "test-utils"))] -use crate::tasks::WasmContext; use crate::workflow::{self, Resource}; use anyhow::Result; use fnv::FnvHashSet; -#[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] -use futures::{stream::FuturesUnordered, TryStreamExt}; use indexmap::IndexMap; -#[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] -use libipld::Cid; use std::sync::Arc; -#[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] -use tracing::info; -#[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] -use tryhard::RetryFutureConfig; pub(crate) struct Fetch; +#[cfg(test)] +const WASM_CID: &str = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm"; + impl Fetch { /// Gather resources from IPFS or elsewhere, leveraging an exponential backoff. - #[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] + #[cfg(all(feature = "ipfs", not(test)))] #[cfg_attr(docsrs, doc(cfg(feature = "ipfs")))] pub(crate) async fn get_resources( resources: FnvHashSet, settings: Arc, ipfs: IpfsCli, ) -> Result>> { - let settings = settings.as_ref(); + use futures::{stream::FuturesUnordered, TryStreamExt}; + let _settings = settings.as_ref(); let tasks = FuturesUnordered::new(); for rsc in resources.iter() { - info!(rsc = rsc.to_string(), "Fetching resource"); - let task = tryhard::retry_fn(|| async { Self::fetch(rsc.clone(), ipfs.clone()).await }) - .with_config( - RetryFutureConfig::new(settings.retries) - .exponential_backoff(settings.retry_initial_delay) - .max_delay(settings.retry_max_delay), - ); - + tracing::info!(rsc = rsc.to_string(), "Fetching resource"); + let task = Self::fetch(rsc.clone(), ipfs.clone()); tasks.push(task); } @@ -51,14 +39,14 @@ impl Fetch { |mut acc, res| { let answer = res.1?; acc.insert(res.0, answer); + Ok::<_, anyhow::Error>(acc) }, ) } /// Gather resources via URLs, leveraging an exponential backoff. - /// TODO: Client calls (only) over http(s). - #[cfg(all(not(feature = "ipfs"), not(test), not(feature = "test-utils")))] + #[cfg(all(not(feature = "ipfs"), not(test)))] #[allow(dead_code)] pub(crate) async fn get_resources( _resources: FnvHashSet, @@ -67,7 +55,7 @@ impl Fetch { Ok(IndexMap::default()) } - #[cfg(all(not(feature = "ipfs"), any(test, feature = "test-utils")))] + #[cfg(all(not(feature = "ipfs"), test))] #[doc(hidden)] #[allow(dead_code)] pub(crate) async fn get_resources( @@ -77,17 +65,19 @@ impl Fetch { println!("Running in test mode"); use crate::tasks::FileLoad; let path = std::path::PathBuf::from(format!( - "{}/../homestar-wasm/fixtures/example_test.wasm", + "{}/../homestar-wasm/fixtures/example_add.wasm", env!("CARGO_MANIFEST_DIR") )); - let bytes = WasmContext::load(path).await?; + let bytes = crate::tasks::WasmContext::load(path).await.unwrap(); let mut map = IndexMap::default(); - let rsc = "ipfs://bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy"; - map.insert(Resource::Url(url::Url::parse(rsc)?), bytes); + map.insert( + Resource::Url(url::Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap()), + bytes, + ); Ok(map) } - #[cfg(all(feature = "ipfs", any(test, feature = "test-utils")))] + #[cfg(all(feature = "ipfs", test))] #[doc(hidden)] #[allow(dead_code)] pub(crate) async fn get_resources( @@ -98,25 +88,26 @@ impl Fetch { println!("Running in test mode"); use crate::tasks::FileLoad; let path = std::path::PathBuf::from(format!( - "{}/../homestar-wasm/fixtures/example_test.wasm", + "{}/../homestar-wasm/fixtures/example_add.wasm", env!("CARGO_MANIFEST_DIR") )); - let bytes = WasmContext::load(path).await?; + let bytes = crate::tasks::WasmContext::load(path).await.unwrap(); let mut map = IndexMap::default(); - let rsc = "ipfs://bafybeihzvrlcfqf6ffbp2juhuakspxj2bdsc54cabxnuxfvuqy5lvfxapy"; - map.insert(Resource::Url(url::Url::parse(rsc)?), bytes); + map.insert( + Resource::Url(url::Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap()), + bytes, + ); Ok(map) } - #[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] - #[cfg_attr(docsrs, doc(cfg(feature = "ipfs")))] + #[cfg(all(feature = "ipfs", not(test)))] async fn fetch(rsc: Resource, client: IpfsCli) -> Result<(Resource, Result>)> { match rsc { Resource::Url(url) => { let bytes = match (url.scheme(), url.domain(), url.path()) { ("ipfs", Some(cid), _) => { - let cid = Cid::try_from(cid)?; - client.get_cid(cid).await + let parsed_cid = libipld::Cid::try_from(cid)?; + client.get_cid(parsed_cid).await } (_, Some("ipfs.io"), _) => client.get_resource(&url).await, (_, _, path) if path.contains("/ipfs/") || path.contains("/ipns/") => { @@ -126,14 +117,14 @@ impl Fetch { let split: Vec<&str> = domain.splitn(3, '.').collect(); // subdomain-gateway case: // - if let (Ok(_cid), "ipfs") = (Cid::try_from(split[0]), split[1]) { + if let (Ok(_cid), "ipfs") = (libipld::Cid::try_from(split[0]), split[1]) { client.get_resource(&url).await } else { - // TODO: reqwest call + // TODO: reqwest call or error todo!() } } - // TODO: reqwest call + // TODO: reqwest call or error (_, _, _) => todo!(), }; diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index dc5c0313..dd83d1b3 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -35,7 +35,7 @@ impl Default for Settings { retry_max_delay: Duration::new(1, 0), retry_initial_delay: Duration::from_millis(50), p2p_timeout: Duration::from_millis(10), - timeout: Duration::from_secs(120), + timeout: Duration::from_secs(90), } } } diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index f08eb33a..034bfcce 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -197,7 +197,7 @@ fn test_workflow_run_serial() -> Result<()> { let mut homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") - .arg("tests/fixtures/test_workflow.toml") + .arg("tests/fixtures/test_workflow1.toml") .arg("--db") .arg("homestar.db") .stdout(Stdio::piped()) @@ -311,7 +311,7 @@ fn test_signal_kill_serial() -> Result<()> { #[cfg(feature = "ipfs")] let _ = startup_ipfs(); - let mut homestar_proc = Command::new(BIN.as_os_str()) + let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("--db") .arg("homestar.db") diff --git a/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json new file mode 100644 index 00000000..bf46aab7 --- /dev/null +++ b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json @@ -0,0 +1,75 @@ +{ + "tasks": [ + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm" + }, + 150, + 350, + 500, + 500 + ], + "func": "crop" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + } + }, + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "await/ok": { + "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy" + } + } + ], + "func": "rotate90" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + } + }, + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "await/ok": { + "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4" + } + } + ], + "func": "grayscale" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + } + } + ] +} diff --git a/homestar-runtime/tests/fixtures/test_workflow.toml b/homestar-runtime/tests/fixtures/test_workflow1.toml similarity index 100% rename from homestar-runtime/tests/fixtures/test_workflow.toml rename to homestar-runtime/tests/fixtures/test_workflow1.toml diff --git a/homestar-runtime/tests/fixtures/test_workflow2.toml b/homestar-runtime/tests/fixtures/test_workflow2.toml new file mode 100644 index 00000000..e81e6d9f --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_workflow2.toml @@ -0,0 +1,11 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5600 + +[node] + +[node.network] +metrics_port = 4070 +events_buffer_len = 1000 +rpc_port = 9860 +webserver_port = 8061 diff --git a/homestar-runtime/tests/main.rs b/homestar-runtime/tests/main.rs index cdaa6596..466774db 100644 --- a/homestar-runtime/tests/main.rs +++ b/homestar-runtime/tests/main.rs @@ -3,3 +3,5 @@ pub(crate) mod cli; pub(crate) mod metrics; pub(crate) mod network; pub(crate) mod utils; +#[cfg(all(feature = "websocket-notify", feature = "test-utils"))] +pub(crate) mod webserver; diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 37eb4078..9186786f 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -24,9 +24,10 @@ use wait_timeout::ChildExt; /// Binary name, which is different than the crate name. pub(crate) const BIN_NAME: &str = "homestar"; +/// TODO +pub(crate) const IPFS: &str = "ipfs"; static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); -const IPFS: &str = "ipfs"; /// Start-up IPFS daemon for tests with the feature turned-on. #[cfg(feature = "ipfs")] @@ -82,7 +83,6 @@ pub(crate) fn stop_ipfs() -> Result<()> { pub(crate) fn stop_all_bins() -> Result<()> { let _ = stop_ipfs(); let _ = stop_homestar(); - Ok(()) } diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs new file mode 100644 index 00000000..8bae4ba7 --- /dev/null +++ b/homestar-runtime/tests/webserver.rs @@ -0,0 +1,127 @@ +#[cfg(feature = "ipfs")] +use crate::utils::startup_ipfs; +use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME, IPFS}; +use anyhow::Result; +use futures::StreamExt; +use jsonrpsee::{ + core::client::{Subscription, SubscriptionClientT}, + rpc_params, + ws_client::WsClientBuilder, +}; +use once_cell::sync::Lazy; +use retry::{delay::Exponential, retry}; +use serial_test::file_serial; +use std::{ + fs, + net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, + path::PathBuf, + process::{Command, Stdio}, +}; + +static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); +const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; +const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; + +#[test] +#[file_serial] +fn test_workflow_run_serial() -> Result<()> { + let _ = stop_all_bins(); + + #[cfg(feature = "ipfs")] + let _ = startup_ipfs(); + + let add_image_args = vec![ + "add", + "--cid-version", + "1", + "../examples/websocket-relay/synthcat.png", + ]; + + let add_wasm_args = vec![ + "add", + "--cid-version", + "1", + "../examples/websocket-relay/example_test.wasm", + ]; + + fs::remove_file("homestar.db").unwrap(); + + let _ipfs_add_img = Command::new(IPFS) + .args(add_image_args) + .stdout(Stdio::piped()) + .output() + .expect("`ipfs add` of synthcat.png"); + + let _ipfs_add_wasm = Command::new(IPFS) + .args(add_wasm_args) + .stdout(Stdio::piped()) + .output() + .expect("`ipfs add` of wasm mod"); + + let mut homestar_proc = Command::new(BIN.as_os_str()) + .arg("start") + .arg("-c") + .arg("tests/fixtures/test_workflow2.toml") + .arg("--db") + .arg("homestar.db") + //.stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let ws_port = 8061; + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ws_port); + let result = retry(Exponential::from_millis(1000).take(10), || { + TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) + }); + + if result.is_err() { + homestar_proc.kill().unwrap(); + panic!("Homestar server/runtime failed to start in time"); + } + + let ws_url = format!("ws://{}:{}", Ipv4Addr::LOCALHOST, ws_port); + + tokio_test::block_on(async { + tokio_tungstenite::connect_async(ws_url.clone()) + .await + .unwrap(); + + let workflow_str = + fs::read_to_string("tests/fixtures/test-workflow-image-pipeline.json").unwrap(); + let json: serde_json::Value = serde_json::from_str(&workflow_str).unwrap(); + let json_string = serde_json::to_string(&json).unwrap(); + let run_str = format!(r#"{{"name": "test","workflow": {}}}"#, json_string); + let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let sub: Subscription> = client + .subscribe( + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![run], + UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await + .unwrap(); + + // we have 3 operations + sub.take(3) + .for_each(|msg| async move { + let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected1 = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + let expected2 = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + if check == &expected1 || check == &expected2 { + println!("JSONRPC response is expected"); + } else { + panic!("JSONRPC response is not expected"); + } + }) + .await; + }); + + let _ = Command::new(BIN.as_os_str()).arg("stop").output(); + let _ = kill_homestar(homestar_proc, None); + let _ = stop_all_bins(); + + Ok(()) +} diff --git a/homestar-wasm/fixtures/example_add.wasm b/homestar-wasm/fixtures/example_add.wasm index ff7d18bc174b0fb60040ba6a185d506963b932fc..23dced58dd0d910978b3d8211839be9b54a53bb5 100755 GIT binary patch delta 667 zcmZ8fK~ED=5Pmb;?qavK8w5p5>D$!<(X>IqUcB^0h*o2IAu-{yS?DTVpe?0H!Gjy6 zfK`djJDC_0qX{ALB0cCyi6>3eyGL(EqklnXF(JmoOXhvw_h!DCdAp+@M<1RE8F2>f zk&Jy&Rp;EfN^KdL<`j$RO4((w7uA<4Oo>i8lO?<6Is0MYXTeo4@>0Z+=`m*9_I;mmCcx9p_BKDTkF`JKpeZBp3fAO1Sngg6ggPm^JQH<} z`gZ3E_F#5eU|snBuFt$dWR^OJA<>xUIAew}hdEAwVX+wFs_5BMu(25E@>wVk>+)x4 zqEiU}0(o9*(e<+?V54Jnom5~$p3}d;dZ#x!0dQAVV_U@Z9c+e4_TNJUWGleQ4hN5{TbRY?0m>DQ0%}8h};VN{v9X^EUMux*qK9kbRR==J8Y>1cFc? v2jNn=x>#RfE1~^w1x!9N?b@RhEn>I<))FxGM zX_Bh72g@84L{NGu2K8A*}--kw7=5_H5Nj4(KR0I6HQ^3dZccm;j_1GJ&L(Zf26I`F5S&>>yC1Bx|W#ylnGHNtm2Q!wBj zdB$sqp+aDIP5uqaffm2y-4N^-FN5`A#CKAHbzV@v!eE#fnE|-VyV|y3^wEJV+O|Wl z1#^8Y49dD|F~F0)`0&w@4>)-h@Qy3V0=6(?AnVvU+;IcDs4zsq7I6$IhtFJ;BD-x< zi9suegg*&KaLvni!%hS!u_+N?Kotp)IXI;e3c&%t9C<6I2aeqUxX*7!&j_nKQ7YV zc*NgH!9M>U@BEi*COVSn;y)7?#L`YDQ~%7XB@@EMMsfmr!*|KY;Ct|Qm*g8S>BmEp uz|g&UkgN6Pa(k7m^5^=eSV9&kmdm+%t-@dsH5cnl3C$Ht)oYDPaelr&$3necGWu48k!dHyA!kZ`TkP8b% z%dr$2Uq2#$9!|bEI5dNWHoo*wu5c94u+c|VDrG4Wj3#oy#vPBL{ZZv^%mqb?c!cCH z$3KvK^w@QP4S7Ft#u}KQ9+t(ufzXO$QS%_?po6g?za+L{Q+AU16RVzM3nz3xM^BOT z>6=Ozn_j_GI5XDex8!u#QaL(ifLJiC1DAH{B8fjp%?t&2z`Nytrm5NJ$RDW|sB#pe z^jgd5mO{PqSNc4?;JHjrJN)Z=nKZ1*XPMK`?tjWWhu{-WS7VbhY{g{99FE)h5o591 z%$2IO@{LL@*SLG9sgC4FPswdljL~idRbY&cro+L$QL0i(@c+FVu<#Izb`em9th_=B Fe*r0`vitx5 delta 646 zcmZ`%O=uHA6rT4s*+jS7EH-M|{LL<*#9~4XF$iMmK#N3>Tok1qq<_|I+JwevQz{tq z=T8LIL8KN0Pe}Q z{)&*}gB>r?`a|1w=mxtfu<%@uKTy+t!MSaymQMYsWy7jiPKa&h` zuqlhB65#mhTI!IU<5P@grb3Y3JXR@ zUKxHvW=2M?0X&d*!zao4R+v-$G290!HzVmp(=}<~wC?5T&fto>^I_wW3z!OL#(nuY zQV5tT5ul(0r^2@6>F7C%+l^j0N_^Wwj!E+7diA&pMq7T1*8hub#OeyMeydeez` zO8a--t#}NY@@YH^E$2i08NjAoGBd;3bjDaK)>3oD^1{+$IaR%Rqo$6hPh@1@6cPGB beiay_rRnhZQ_+#7W!VhL^VX1(O^^Quk@}%+ diff --git a/homestar-wasm/fixtures/example_add_component.wat b/homestar-wasm/fixtures/example_add_component.wat index a2e56260..9e46312a 100644 --- a/homestar-wasm/fixtures/example_add_component.wat +++ b/homestar-wasm/fixtures/example_add_component.wat @@ -9,6 +9,12 @@ (type (;6;) (func (param i32 i32 i32) (result i32))) (func (;0;) (type 0)) (func (;1;) (type 1) (param i32) (result i32) + call 2 + local.get 0 + i32.const 1 + i32.add + ) + (func (;2;) (type 0) block ;; label = @1 i32.const 0 i32.load8_u offset=1049029 @@ -18,11 +24,14 @@ i32.const 1 i32.store8 offset=1049029 end + ) + (func (;3;) (type 1) (param i32) (result i32) + call 2 local.get 0 i32.const 2 i32.add ) - (func (;2;) (type 2) (param i32 i32) (result i32) + (func (;4;) (type 2) (param i32 i32) (result i32) (local i32 i32 i32 i32 i32) i32.const 0 local.set 2 @@ -54,7 +63,7 @@ i32.add i32.const 12 i32.add - call 3 + call 5 local.tee 1 i32.eqz br_if 0 (;@1;) @@ -154,7 +163,7 @@ i32.store offset=4 local.get 2 local.get 1 - call 4 + call 6 br 1 (;@2;) end local.get 2 @@ -217,7 +226,7 @@ i32.store offset=4 local.get 1 local.get 3 - call 4 + call 6 end local.get 0 i32.const 8 @@ -226,7 +235,7 @@ end local.get 2 ) - (func (;3;) (type 1) (param i32) (result i32) + (func (;5;) (type 1) (param i32) (result i32) (local i32 i32 i32 i32 i32 i32 i32 i32 i64) block ;; label = @1 block ;; label = @2 @@ -499,10 +508,6 @@ i32.eqz br_if 10 (;@2;) local.get 0 - i32.const 0 - local.get 0 - i32.sub - i32.and i32.ctz i32.const 2 i32.shl @@ -513,26 +518,23 @@ i32.load offset=4 i32.const -8 i32.and - local.set 1 - block ;; label = @13 - local.get 6 - i32.load offset=16 - local.tee 0 - br_if 0 (;@13;) - local.get 6 - i32.const 20 - i32.add - i32.load - local.set 0 - end - local.get 1 local.get 2 i32.sub local.set 5 block ;; label = @13 - local.get 0 - i32.eqz - br_if 0 (;@13;) + block ;; label = @14 + local.get 6 + i32.load offset=16 + local.tee 0 + br_if 0 (;@14;) + local.get 6 + i32.const 20 + i32.add + i32.load + local.tee 0 + i32.eqz + br_if 1 (;@13;) + end loop ;; label = @14 local.get 0 i32.load offset=4 @@ -572,7 +574,7 @@ end end local.get 6 - call 5 + call 7 local.get 5 i32.const 16 i32.lt_u @@ -618,11 +620,6 @@ local.get 1 i32.shl i32.and - local.tee 0 - i32.const 0 - local.get 0 - i32.sub - i32.and i32.ctz local.tee 1 i32.const 3 @@ -849,10 +846,6 @@ i32.eqz br_if 3 (;@2;) local.get 0 - i32.const 0 - local.get 0 - i32.sub - i32.and i32.ctz i32.const 2 i32.shl @@ -926,7 +919,7 @@ br_if 1 (;@2;) end local.get 6 - call 5 + call 7 block ;; label = @3 block ;; label = @4 local.get 1 @@ -958,7 +951,7 @@ br_if 0 (;@5;) local.get 0 local.get 1 - call 6 + call 8 br 2 (;@3;) end local.get 1 @@ -1321,7 +1314,7 @@ br_if 0 (;@14;) local.get 1 local.get 0 - call 6 + call 8 br 12 (;@2;) end local.get 0 @@ -1421,7 +1414,7 @@ i32.lt_u br_if 0 (;@15;) local.get 5 - call 5 + call 7 br 1 (;@14;) end block ;; label = @15 @@ -1567,7 +1560,7 @@ i32.load offset=1048996 local.get 8 i32.add - call 7 + call 9 br 6 (;@2;) end i32.const 0 @@ -1640,7 +1633,7 @@ br_if 0 (;@5;) local.get 0 local.get 2 - call 6 + call 8 br 1 (;@4;) end local.get 2 @@ -1960,7 +1953,7 @@ end local.get 1 ) - (func (;4;) (type 3) (param i32 i32) + (func (;6;) (type 3) (param i32 i32) (local i32 i32 i32 i32) local.get 0 local.get 1 @@ -2027,7 +2020,7 @@ i32.lt_u br_if 0 (;@4;) local.get 0 - call 5 + call 7 br 1 (;@3;) end block ;; label = @4 @@ -2113,7 +2106,7 @@ i32.lt_u br_if 0 (;@6;) local.get 2 - call 5 + call 7 br 1 (;@5;) end block ;; label = @6 @@ -2227,7 +2220,7 @@ br_if 0 (;@1;) local.get 0 local.get 1 - call 6 + call 8 return end local.get 1 @@ -2276,7 +2269,7 @@ local.get 1 i32.store offset=8 ) - (func (;5;) (type 4) (param i32) + (func (;7;) (type 4) (param i32) (local i32 i32 i32 i32 i32) local.get 0 i32.load offset=24 @@ -2437,7 +2430,7 @@ return end ) - (func (;6;) (type 3) (param i32 i32) + (func (;8;) (type 3) (param i32 i32) (local i32 i32 i32 i32) i32.const 31 local.set 2 @@ -2596,7 +2589,7 @@ local.get 0 i32.store offset=8 ) - (func (;7;) (type 3) (param i32 i32) + (func (;9;) (type 3) (param i32 i32) (local i32 i32) i32.const 0 local.get 0 @@ -2607,6 +2600,7 @@ local.tee 2 i32.const -8 i32.add + local.tee 3 i32.store offset=1049004 i32.const 0 local.get 0 @@ -2616,15 +2610,13 @@ i32.add i32.const 8 i32.add - local.tee 3 + local.tee 2 i32.store offset=1048996 - local.get 2 - i32.const -4 - i32.add local.get 3 + local.get 2 i32.const 1 i32.or - i32.store + i32.store offset=4 local.get 0 local.get 1 i32.add @@ -2634,7 +2626,7 @@ i32.const 2097152 i32.store offset=1049016 ) - (func (;8;) (type 4) (param i32) + (func (;10;) (type 4) (param i32) (local i32 i32 i32 i32 i32) local.get 0 i32.const -8 @@ -2697,9 +2689,7 @@ i32.const 1 i32.or i32.store offset=4 - local.get 1 - local.get 0 - i32.add + local.get 3 local.get 0 i32.store return @@ -2710,7 +2700,7 @@ i32.lt_u br_if 0 (;@3;) local.get 1 - call 5 + call 7 br 1 (;@2;) end block ;; label = @3 @@ -2799,7 +2789,7 @@ i32.lt_u br_if 0 (;@8;) local.get 3 - call 5 + call 7 br 1 (;@7;) end block ;; label = @8 @@ -2945,7 +2935,7 @@ br_if 0 (;@4;) end end - call 9 + call 11 i32.const 0 i32.load offset=1048996 i32.const 0 @@ -2964,7 +2954,7 @@ br_if 0 (;@2;) local.get 1 local.get 0 - call 6 + call 8 i32.const 0 i32.const 0 i32.load offset=1049024 @@ -2974,7 +2964,7 @@ i32.store offset=1049024 local.get 1 br_if 1 (;@1;) - call 9 + call 11 return end local.get 0 @@ -3024,7 +3014,7 @@ i32.store offset=8 end ) - (func (;9;) (type 0) + (func (;11;) (type 0) (local i32 i32) i32.const 0 local.set 0 @@ -3056,7 +3046,7 @@ select i32.store offset=1049024 ) - (func (;10;) (type 5) (param i32 i32 i32 i32) (result i32) + (func (;12;) (type 5) (param i32 i32 i32 i32) (result i32) (local i32 i32 i32 i32 i32) block ;; label = @1 block ;; label = @2 @@ -3080,7 +3070,7 @@ br_if 0 (;@8;) local.get 2 local.get 3 - call 2 + call 4 local.tee 2 i32.eqz br_if 5 (;@3;) @@ -3092,10 +3082,10 @@ local.get 3 i32.lt_u select - call 12 + call 14 local.set 3 local.get 0 - call 8 + call 10 local.get 3 return end @@ -3138,15 +3128,15 @@ local.get 0 i32.const -8 i32.add - local.set 6 + local.tee 6 + local.get 2 + i32.add + local.set 7 local.get 2 local.get 1 i32.ge_u br_if 1 (;@13;) - local.get 6 - local.get 2 - i32.add - local.tee 7 + local.get 7 i32.const 0 i32.load offset=1049004 i32.eq @@ -3181,7 +3171,7 @@ i32.lt_u br_if 2 (;@12;) local.get 7 - call 5 + call 7 br 3 (;@11;) end local.get 1 @@ -3226,18 +3216,15 @@ i32.const 3 i32.or i32.store offset=4 - local.get 1 - local.get 3 - i32.add - local.tee 2 - local.get 2 + local.get 7 + local.get 7 i32.load offset=4 i32.const 1 i32.or i32.store offset=4 local.get 1 local.get 3 - call 4 + call 6 local.get 0 return end @@ -3295,8 +3282,8 @@ i32.const 3 i32.or i32.store offset=4 - local.get 1 - local.get 3 + local.get 6 + local.get 2 i32.add local.tee 2 local.get 2 @@ -3306,7 +3293,7 @@ i32.store offset=4 local.get 1 local.get 3 - call 4 + call 6 local.get 0 return end @@ -3368,8 +3355,8 @@ i32.const 1 i32.or i32.store offset=4 - local.get 1 - local.get 3 + local.get 6 + local.get 2 i32.add local.tee 2 local.get 3 @@ -3400,7 +3387,7 @@ br_if 7 (;@1;) end local.get 3 - call 3 + call 5 local.tee 1 i32.eqz br_if 4 (;@3;) @@ -3424,10 +3411,10 @@ local.get 3 i32.lt_u select - call 12 + call 14 local.set 3 local.get 0 - call 8 + call 10 local.get 3 return end @@ -3440,7 +3427,7 @@ br_if 1 (;@5;) local.get 2 local.get 3 - call 2 + call 4 local.set 0 br 2 (;@4;) end @@ -3466,7 +3453,7 @@ br 3 (;@2;) end local.get 3 - call 3 + call 5 local.set 0 end local.get 0 @@ -3506,7 +3493,7 @@ i32.store offset=1049004 local.get 0 ) - (func (;11;) (type 6) (param i32 i32 i32) (result i32) + (func (;13;) (type 6) (param i32 i32 i32) (result i32) (local i32 i32 i32 i32 i32 i32 i32 i32) block ;; label = @1 block ;; label = @2 @@ -3687,34 +3674,38 @@ end local.get 0 ) - (func (;12;) (type 6) (param i32 i32 i32) (result i32) + (func (;14;) (type 6) (param i32 i32 i32) (result i32) local.get 0 local.get 1 local.get 2 - call 11 + call 13 ) (memory (;0;) 17) (global (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1049030) (global (;2;) i32 i32.const 1049040) (export "memory" (memory 0)) - (export "add-two" (func 1)) - (export "cabi_realloc" (func 10)) + (export "add-one" (func 1)) + (export "add-two" (func 3)) + (export "cabi_realloc" (func 12)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) (@producers - (processed-by "wit-component" "0.14.2") - (processed-by "wit-bindgen-rust" "0.12.0") + (processed-by "wit-component" "0.16.0") + (processed-by "wit-bindgen-rust" "0.13.0") ) ) (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) (type (;0;) (func (param "input" s32) (result s32))) - (alias core export 0 "add-two" (core func (;1;))) + (alias core export 0 "add-one" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) - (export (;1;) "add-two" (func 0)) + (export (;1;) "add-one" (func 0)) + (alias core export 0 "add-two" (core func (;2;))) + (func (;2;) (type 0) (canon lift (core func 2))) + (export (;3;) "add-two" (func 2)) (@producers - (processed-by "wit-component" "0.14.0") + (processed-by "wit-component" "0.16.0") ) ) diff --git a/homestar-wasm/fixtures/example_test.wasm b/homestar-wasm/fixtures/example_test.wasm index e4368bb748551fc41c66882a57f0e5db044b980f..b382b252b6466241d86a0e378e0019a8fd910b6c 100755 GIT binary patch literal 285147 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 yeLcRz)_Ft=FO=G#h08{(OPIts3OU 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�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%0DLq6d(OejT2eNsPm*(@ToS_%BWtvxO6NhNW1 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 RQEIIx{V6WmUf#>+{{bA*be;eJ diff --git a/homestar-wasm/fixtures/example_test_component.wasm b/homestar-wasm/fixtures/example_test_component.wasm index 033cc89a1d73a1012c6b94c5ee9781303c282924..914a7b504c272a5820a29a939d6f1acbdce062ef 100644 GIT binary patch literal 285548 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%0A7WhUpGIXJ4PB=5>hh?DB|zP$UPa9}$33WCv96N_ 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 P+1}sM19?Vz;Pd|gxf{9F diff --git a/homestar-wasm/tests/execute_wasm.rs b/homestar-wasm/tests/execute_wasm.rs index 3d3e8062..f387fc06 100644 --- a/homestar-wasm/tests/execute_wasm.rs +++ b/homestar-wasm/tests/execute_wasm.rs @@ -35,8 +35,6 @@ async fn test_wasm_exceeds_max_memory() { } else { panic!("Expected WasmRuntimeError") } - - //assert() } #[tokio::test] From ed774e4252eb06aef8f39f0c41a84ab5e9150507 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Thu, 2 Nov 2023 22:15:54 -0400 Subject: [PATCH 04/42] chore: return of the retry --- homestar-runtime/src/tasks/fetch.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homestar-runtime/src/tasks/fetch.rs b/homestar-runtime/src/tasks/fetch.rs index 54ce2e90..6b1deecc 100644 --- a/homestar-runtime/src/tasks/fetch.rs +++ b/homestar-runtime/src/tasks/fetch.rs @@ -26,11 +26,16 @@ impl Fetch { ipfs: IpfsCli, ) -> Result>> { use futures::{stream::FuturesUnordered, TryStreamExt}; - let _settings = settings.as_ref(); + let settings = settings.as_ref(); let tasks = FuturesUnordered::new(); for rsc in resources.iter() { tracing::info!(rsc = rsc.to_string(), "Fetching resource"); - let task = Self::fetch(rsc.clone(), ipfs.clone()); + let task = tryhard::retry_fn(|| async { Self::fetch(rsc.clone(), ipfs.clone()).await }) + .with_config( + tryhard::RetryFutureConfig::new(settings.retries) + .exponential_backoff(settings.retry_initial_delay) + .max_delay(settings.retry_max_delay), + ); tasks.push(task); } From 708369e187c290f65acd666ef0bf14ca94981304 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Fri, 3 Nov 2023 10:07:38 -0400 Subject: [PATCH 05/42] chore: test setup --- homestar-runtime/tests/cli.rs | 26 -------------------------- homestar-runtime/tests/webserver.rs | 8 ++++---- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 034bfcce..fb5d940a 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -1,7 +1,5 @@ #[cfg(not(windows))] use crate::utils::kill_homestar_daemon; -#[cfg(feature = "ipfs")] -use crate::utils::startup_ipfs; use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME}; use anyhow::Result; use assert_cmd::prelude::*; @@ -22,9 +20,6 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) fn test_help_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - Command::new(BIN.as_os_str()) .arg("help") .assert() @@ -57,9 +52,6 @@ fn test_help_serial() -> Result<()> { fn test_version_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - Command::new(BIN.as_os_str()) .arg("--version") .assert() @@ -80,9 +72,6 @@ fn test_version_serial() -> Result<()> { fn test_server_not_running_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - Command::new(BIN.as_os_str()) .arg("ping") .assert() @@ -123,9 +112,6 @@ fn test_server_not_running_serial() -> Result<()> { fn test_server_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - Command::new(BIN.as_os_str()) .arg("start") .arg("-db") @@ -191,9 +177,6 @@ fn test_server_serial() -> Result<()> { fn test_workflow_run_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - let mut homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") @@ -263,9 +246,6 @@ fn test_workflow_run_serial() -> Result<()> { fn test_daemon_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - Command::new(BIN.as_os_str()) .arg("start") .arg("-c") @@ -308,9 +288,6 @@ fn test_daemon_serial() -> Result<()> { fn test_signal_kill_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("--db") @@ -351,9 +328,6 @@ fn test_signal_kill_serial() -> Result<()> { fn test_server_v4_serial() -> Result<()> { let _ = stop_all_bins(); - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); - let mut homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 8bae4ba7..2becc51f 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -44,7 +44,7 @@ fn test_workflow_run_serial() -> Result<()> { "../examples/websocket-relay/example_test.wasm", ]; - fs::remove_file("homestar.db").unwrap(); + let _ = fs::remove_file("homestar_test_workflow_run_serial.db"); let _ipfs_add_img = Command::new(IPFS) .args(add_image_args) @@ -63,8 +63,8 @@ fn test_workflow_run_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_workflow2.toml") .arg("--db") - .arg("homestar.db") - //.stdout(Stdio::piped()) + .arg("homestar_test_workflow_run_serial.db") + .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -103,7 +103,7 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); - // we have 3 operations + // we have 3 operations0 sub.take(3) .for_each(|msg| async move { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); From 6da6e71d7b2965d4b7e95bde16d979313e88959c Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 6 Nov 2023 11:50:45 -0500 Subject: [PATCH 06/42] fix: subscription map, checks, multiple broadcasts --- homestar-runtime/src/event_handler.rs | 23 ++- homestar-runtime/src/event_handler/event.rs | 40 +++++- homestar-runtime/src/network/webserver.rs | 85 +++++++++-- .../src/network/webserver/notifier.rs | 82 +++++++++-- homestar-runtime/src/network/webserver/rpc.rs | 134 +++++++++++++++--- homestar-runtime/src/runner.rs | 18 ++- homestar-runtime/src/runner/response.rs | 5 +- homestar-runtime/src/workflow/info.rs | 27 +++- homestar-runtime/tests/utils.rs | 1 + homestar-runtime/tests/webserver.rs | 47 +++++- 10 files changed, 395 insertions(+), 67 deletions(-) diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index 1fe7e920..02db93c1 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -1,7 +1,7 @@ //! [EventHandler] implementation for handling network events and messages. #[cfg(feature = "websocket-notify")] -use crate::network::webserver; +use crate::network::webserver::{self, notifier}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -62,7 +62,8 @@ pub(crate) struct EventHandler { request_response_senders: FnvHashMap, rendezvous: Rendezvous, pubsub_enabled: bool, - ws_msg_sender: webserver::Notifier, + ws_evt_sender: webserver::Notifier, + ws_workflow_sender: webserver::Notifier, node_addresses: Vec, announce_addresses: Vec, external_address_limit: u32, @@ -125,7 +126,8 @@ where swarm: Swarm, db: DB, settings: &settings::Node, - ws_msg_sender: webserver::Notifier, + ws_evt_sender: webserver::Notifier, + ws_workflow_sender: webserver::Notifier, ) -> Self { let (sender, receiver) = Self::setup_channel(settings); let sender = Arc::new(sender); @@ -151,7 +153,8 @@ where cookies: FnvHashMap::default(), }, pubsub_enabled: settings.network.enable_pubsub, - ws_msg_sender, + ws_evt_sender, + ws_workflow_sender, node_addresses: settings.network.node_addresses.clone(), announce_addresses: settings.network.announce_addresses.clone(), external_address_limit: settings.network.max_announce_addresses, @@ -206,8 +209,16 @@ where #[cfg(feature = "websocket-notify")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(dead_code)] - pub(crate) fn ws_sender(&self) -> webserver::Notifier { - self.ws_msg_sender.clone() + pub(crate) fn ws_workflow_sender(&self) -> webserver::Notifier { + self.ws_workflow_sender.clone() + } + + /// TODO + #[cfg(feature = "websocket-notify")] + #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] + #[allow(dead_code)] + pub(crate) fn ws_evt_sender(&self) -> webserver::Notifier { + self.ws_evt_sender.clone() } /// Start [EventHandler] that matches on swarm and pubsub [events]. diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 9e07587d..368f0325 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -2,7 +2,7 @@ use super::EventHandler; #[cfg(feature = "websocket-notify")] -use crate::network::webserver::notifier::NotifyReceipt; +use crate::network::webserver::notifier::{Header, Message, NotifyReceipt, SubscriptionTyp}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -18,7 +18,13 @@ use anyhow::Result; use async_trait::async_trait; use homestar_core::workflow::Receipt as InvocationReceipt; #[cfg(feature = "websocket-notify")] -use homestar_core::{ipld::DagJson, workflow::Pointer}; +use homestar_core::{ + ipld::DagJson, + workflow::{ + receipt::metadata::{WORKFLOW_KEY, WORKFLOW_NAME_KEY}, + Pointer, + }, +}; use libipld::{Cid, Ipld}; use libp2p::{ kad::{record::Key, Quorum, Record}, @@ -246,7 +252,7 @@ impl Captured { #[cfg(feature = "websocket-notify")] { let invocation_notification = invocation_receipt.clone(); - let ws_tx = event_handler.ws_sender(); + let ws_tx = event_handler.ws_workflow_sender(); let metadata = self.metadata.to_owned(); let receipt = NotifyReceipt::with(invocation_notification, receipt_cid, metadata); if let Ok(json) = receipt.to_json() { @@ -254,7 +260,13 @@ impl Captured { cid = receipt_cid.to_string(), "Sending receipt to websocket" ); - let _ = ws_tx.notify(json); + let _ = ws_tx.notify(Message::new( + Header::new( + SubscriptionTyp::Cid(self.workflow.cid), + self.workflow.name.clone(), + ), + json, + )); } } @@ -360,7 +372,7 @@ impl Replay { let invocation_notification = invocation_receipt; let receipt_cid = receipt.cid(); - let ws_tx = event_handler.ws_sender(); + let ws_tx = event_handler.ws_workflow_sender(); let metadata = self.metadata.to_owned(); let receipt = NotifyReceipt::with(invocation_notification, receipt_cid, metadata); if let Ok(json) = receipt.to_json() { @@ -368,7 +380,23 @@ impl Replay { cid = receipt_cid.to_string(), "Sending receipt to websocket" ); - let _ = ws_tx.notify(json); + + if let Some(ipld) = &self.metadata { + match (ipld.get(WORKFLOW_KEY), ipld.get(WORKFLOW_NAME_KEY)) { + (Ok(Ipld::Link(cid)), Ok(Ipld::String(name))) => { + let header = Header::new( + SubscriptionTyp::Cid(*cid), + Some((name.to_string()).into()), + ); + let _ = ws_tx.notify(Message::new(header, json)); + } + (Ok(Ipld::Link(cid)), Err(_err)) => { + let header = Header::new(SubscriptionTyp::Cid(*cid), None); + let _ = ws_tx.notify(Message::new(header, json)); + } + _ => (), + } + } } }); diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index fac01e30..738986c0 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -9,8 +9,9 @@ use homestar_wasm::io::Arg; use http::{header::CONTENT_TYPE, Method}; use jsonrpsee::{ self, - server::{middleware::ProxyGetRequestLayer, ServerHandle}, + server::{middleware::ProxyGetRequestLayer, RandomStringIdProvider, ServerHandle}, }; +use libipld::Cid; use metrics_exporter_prometheus::PrometheusHandle; use std::{ net::{IpAddr, SocketAddr, TcpListener}, @@ -45,7 +46,7 @@ pub(crate) enum Message { /// Run a workflow, given a tuple of name, and [Workflow]. RunWorkflow((FastStr, Workflow<'static, Arg>)), /// Acknowledgement of a [Workflow] run. - AckWorkflow, + AckWorkflow((Cid, FastStr)), } /// WebSocket server fields. @@ -56,9 +57,11 @@ pub(crate) struct Server { addr: SocketAddr, /// TODO capacity: usize, + /// TODO + evt_notifier: Notifier, /// Message sender for broadcasting to clients connected to the /// websocket server. - notifier: Notifier, + workflow_msg_notifier: Notifier, /// Receiver timeout for the websocket server. receiver_timeout: Duration, /// TODO @@ -84,13 +87,17 @@ impl Server { #[cfg(feature = "websocket-notify")] fn setup_channel( capacity: usize, - ) -> (broadcast::Sender>, broadcast::Receiver>) { + ) -> ( + broadcast::Sender, + broadcast::Receiver, + ) { broadcast::channel(capacity) } #[cfg(feature = "websocket-notify")] pub(crate) fn new(settings: &settings::Network) -> Result { - let (sender, _receiver) = Self::setup_channel(settings.websocket_capacity); + let (evt_sender, _receiver) = Self::setup_channel(settings.websocket_capacity); + let (msg_sender, _receiver) = Self::setup_channel(settings.websocket_capacity); let host = IpAddr::from_str(&settings.webserver_host.to_string())?; let port_setting = settings.webserver_port; let addr = if port_available(host, port_setting) { @@ -105,7 +112,8 @@ impl Server { Ok(Self { addr, capacity: settings.websocket_capacity, - notifier: Notifier::new(sender), + evt_notifier: Notifier::new(evt_sender), + workflow_msg_notifier: Notifier::new(msg_sender), receiver_timeout: settings.websocket_receiver_timeout, webserver_timeout: settings.webserver_timeout, }) @@ -141,7 +149,8 @@ impl Server { ) -> Result { let module = JsonRpc::new(Context::new( metrics_hdl, - self.notifier.clone(), + self.evt_notifier.clone(), + self.workflow_msg_notifier.clone(), runner_sender, self.receiver_timeout, )) @@ -169,8 +178,15 @@ impl Server { /// Get websocket message sender for broadcasting messages to websocket /// clients. #[cfg(feature = "websocket-notify")] - pub(crate) fn notifier(&self) -> Notifier { - self.notifier.clone() + pub(crate) fn evt_notifier(&self) -> Notifier { + self.evt_notifier.clone() + } + + /// Get websocket message sender for broadcasting messages to websocket + /// clients. + #[cfg(feature = "websocket-notify")] + pub(crate) fn workflow_msg_notifier(&self) -> Notifier { + self.workflow_msg_notifier.clone() } async fn start_inner(&self, module: JsonRpc) -> Result { @@ -198,6 +214,7 @@ impl Server { let server = jsonrpsee::server::Server::builder() .custom_tokio_runtime(runtime_hdl.clone()) .set_middleware(middleware) + .set_id_provider(Box::new(RandomStringIdProvider::new(16))) .set_message_buffer_capacity(self.capacity as u32) .build(addr) .await @@ -230,7 +247,7 @@ mod test { use jsonrpsee::types::error::ErrorCode; use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; #[cfg(feature = "websocket-notify")] - use notifier::NotifyReceipt; + use notifier::{self, Header, NotifyReceipt}; use serial_test::file_serial; use tokio::sync::mpsc; @@ -345,11 +362,51 @@ mod test { // send any bytes through (Vec) let (invocation_receipt, runtime_receipt) = crate::test_utils::receipt::receipts(); let receipt = NotifyReceipt::with(invocation_receipt, runtime_receipt.cid(), None); - server.notifier.notify(receipt.to_json().unwrap()).unwrap(); - let msg = sub.next().await.unwrap().unwrap(); - let returned: NotifyReceipt = DagJson::from_json(&msg).unwrap(); + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + ), + None, + ), + receipt.to_json().unwrap(), + )) + .unwrap(); + + // send an unknown msg: this should be dropped + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub("test".to_string()), + None, + ), + vec![], + )) + .unwrap(); + + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + ), + None, + ), + receipt.to_json().unwrap(), + )) + .unwrap(); + + let msg1 = sub.next().await.unwrap().unwrap(); + let returned1: NotifyReceipt = DagJson::from_json(&msg1).unwrap(); + assert_eq!(returned1, receipt); + + let msg2 = sub.next().await.unwrap().unwrap(); + let _returned1: NotifyReceipt = DagJson::from_json(&msg2).unwrap(); - assert_eq!(returned, receipt); assert!(sub.unsubscribe().await.is_ok()); unsafe { metrics::clear_recorder() } diff --git a/homestar-runtime/src/network/webserver/notifier.rs b/homestar-runtime/src/network/webserver/notifier.rs index 31e118e0..44c861bf 100644 --- a/homestar-runtime/src/network/webserver/notifier.rs +++ b/homestar-runtime/src/network/webserver/notifier.rs @@ -1,51 +1,117 @@ //! Notifier for broadcasting messages to websocket clients. use anyhow::Result; +use faststr::FastStr; use homestar_core::{ipld::DagJson, workflow::Receipt}; use libipld::{ipld, Cid, Ipld}; -use std::sync::Arc; +use std::{fmt, sync::Arc}; use tokio::sync::broadcast; /// Type-wrapper for websocket sender. #[derive(Debug)] -pub(crate) struct Notifier(Arc>>); +pub(crate) struct Notifier(Arc>); -impl Clone for Notifier { +impl Clone for Notifier { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Notifier { +impl Notifier +where + T: Send + Sync + fmt::Debug + 'static, +{ /// Create a new [Notifier]. - pub(crate) fn new(sender: broadcast::Sender>) -> Self { + pub(crate) fn new(sender: broadcast::Sender) -> Self { Self(sender.into()) } /// Get a reference to the inner [broadcast::Sender]. #[allow(dead_code)] - pub(crate) fn inner(&self) -> &Arc>> { + pub(crate) fn inner(&self) -> &Arc> { &self.0 } /// Get and take ownership of the inner [broadcast::Sender]. #[allow(dead_code)] - pub(crate) fn into_inner(self) -> Arc>> { + pub(crate) fn into_inner(self) -> Arc> { self.0 } /// Send a message to all connected websocket clients. - pub(crate) fn notify(&self, msg: Vec) -> Result<()> { + pub(crate) fn notify(&self, msg: T) -> Result<()> { let _ = self.0.send(msg)?; Ok(()) } } +/// Subscription type: either directed via a [Cid] or an event subscription string. +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub(crate) enum SubscriptionTyp { + EventSub(String), + Cid(Cid), +} + +/// A header for a message to be sent to a websocket client. +#[derive(Debug, Clone)] +pub(crate) struct Header { + pub(crate) subscription: SubscriptionTyp, + pub(crate) ident: Option, +} + +impl Header { + /// Create a new [Header]. + pub(crate) fn new(sub: SubscriptionTyp, ident: Option) -> Self { + Self { + subscription: sub, + ident, + } + } +} + +/// A message to be sent to a websocket client, with a header and payload. +#[derive(Debug, Clone)] +pub(crate) struct Message { + pub(crate) header: Header, + pub(crate) payload: Vec, +} + +impl Message { + /// TODO + pub(crate) fn new(header: Header, payload: Vec) -> Self { + Self { header, payload } + } + + /// TODO + #[allow(dead_code)] + pub(crate) fn header(&self) -> &Header { + &self.header + } + + /// TODO + pub(crate) fn payload(&self) -> &[u8] { + &self.payload + } +} + /// A [Receipt] that is sent out *just* for websocket notifications. #[derive(Debug, Clone, PartialEq)] pub(crate) struct NotifyReceipt(Ipld); impl NotifyReceipt { + /// TODO + #[allow(dead_code)] + pub(crate) fn inner(&self) -> &Ipld { + &self.0 + } + + /// TODO + #[allow(dead_code)] + pub(crate) fn into_inner(self) -> Ipld { + self.0.to_owned() + } + pub(crate) fn with(receipt: Receipt, cid: Cid, metadata: Option) -> Self { let receipt: Ipld = receipt.into(); let data = ipld!({ diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index 6749585d..e9fae049 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -1,19 +1,30 @@ use super::{listener, prom::PrometheusData}; #[cfg(feature = "websocket-notify")] -use super::{Message, Notifier}; +use super::{ + notifier::{self, Header, Notifier, SubscriptionTyp}, + Message, +}; use crate::runner::WsSender; #[cfg(feature = "websocket-notify")] use anyhow::anyhow; use anyhow::Result; #[cfg(feature = "websocket-notify")] +use dashmap::DashMap; +#[cfg(feature = "websocket-notify")] +use faststr::FastStr; +#[cfg(feature = "websocket-notify")] use futures::StreamExt; use jsonrpsee::{ server::RpcModule, types::{error::ErrorCode, ErrorObjectOwned}, }; #[cfg(feature = "websocket-notify")] -use jsonrpsee::{SubscriptionMessage, SubscriptionSink, TrySendError}; +use jsonrpsee::{ConnectionId, SubscriptionMessage, SubscriptionSink, TrySendError}; +#[cfg(feature = "websocket-notify")] +use libipld::Cid; use metrics_exporter_prometheus::PrometheusHandle; +#[cfg(feature = "websocket-notify")] +use std::sync::Arc; use std::time::Duration; #[cfg(feature = "websocket-notify")] use tokio::{ @@ -25,13 +36,12 @@ use tokio::{ #[cfg(feature = "websocket-notify")] use tokio_stream::wrappers::BroadcastStream; #[cfg(feature = "websocket-notify")] -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; /// Health endpoint. pub(crate) const HEALTH_ENDPOINT: &str = "health"; /// Metrics endpoint for prometheus / openmetrics polling. pub(crate) const METRICS_ENDPOINT: &str = "metrics"; - /// Run a workflow and subscribe to that workflow's events. #[cfg(feature = "websocket-notify")] pub(crate) const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; @@ -49,9 +59,11 @@ pub(crate) const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_networ #[cfg(feature = "websocket-notify")] pub(crate) struct Context { metrics_hdl: PrometheusHandle, - notifier: Notifier, + evt_notifier: Notifier, + workflow_msg_notifier: Notifier, runner_sender: WsSender, receiver_timeout: Duration, + workflow_listeners: Arc>, } /// TODO @@ -69,15 +81,18 @@ impl Context { #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] pub(crate) fn new( metrics_hdl: PrometheusHandle, - notifier: Notifier, + evt_notifier: Notifier, + workflow_msg_notifier: Notifier, runner_sender: WsSender, receiver_timeout: Duration, ) -> Self { Self { metrics_hdl, - notifier, + evt_notifier, + workflow_msg_notifier, runner_sender, receiver_timeout, + workflow_listeners: DashMap::new().into(), } } @@ -145,9 +160,14 @@ impl JsonRpc { UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, |_, pending, ctx| async move { let sink = pending.accept().await?; - let rx = ctx.notifier.inner().subscribe(); + let rx = ctx.evt_notifier.inner().subscribe(); let stream = BroadcastStream::new(rx); - Self::handle_event_subscription(sink, stream).await?; + Self::handle_event_subscription( + sink, + stream, + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + ) + .await?; Ok(()) }, )?; @@ -165,10 +185,17 @@ impl JsonRpc { .send((Message::RunWorkflow((name, workflow)), Some(tx))) .await?; - if (time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await) - .is_err() + if let Ok(Ok(Message::AckWorkflow((cid, name)))) = + time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await { - error!("did not acknowledge message in time"); + ctx.workflow_listeners + .insert(pending.connection_id(), (cid, name)); + debug!( + "inserted workflow listener with connection_id: {:#?}", + pending.connection_id() + ); + } else { + warn!("did not acknowledge message in time"); let _ = pending .reject(ErrorObjectOwned::from(ErrorObjectOwned::from( ErrorCode::InternalError, @@ -183,10 +210,11 @@ impl JsonRpc { return Ok(()); } } + let sink = pending.accept().await?; - let rx = ctx.notifier.inner().subscribe(); + let rx = ctx.workflow_msg_notifier.inner().subscribe(); let stream = BroadcastStream::new(rx); - Self::handle_event_subscription(sink, stream).await?; + Self::handle_workflow_subscription(sink, stream, ctx).await?; Ok(()) }, )?; @@ -197,7 +225,8 @@ impl JsonRpc { #[cfg(feature = "websocket-notify")] async fn handle_event_subscription( mut sink: SubscriptionSink, - mut stream: BroadcastStream>, + mut stream: BroadcastStream, + subscription_type: String, ) -> Result<()> { let rt_hdl = Handle::current(); rt_hdl.spawn(async move { @@ -208,7 +237,14 @@ impl JsonRpc { } next_msg = stream.next() => { let msg = match next_msg { - Some(Ok(msg)) => msg, + Some(Ok(notifier::Message { + header: Header { + subscription: SubscriptionTyp::EventSub(evt), + .. + }, + payload, + })) if evt == subscription_type => payload, + Some(Ok(_)) => continue, Some(Err(err)) => { error!("subscription stream error: {}", err); break Err(err.into()); @@ -232,4 +268,70 @@ impl JsonRpc { Ok(()) } + + #[cfg(feature = "websocket-notify")] + async fn handle_workflow_subscription( + mut sink: SubscriptionSink, + mut stream: BroadcastStream, + ctx: Arc, + ) -> Result<()> { + let rt_hdl = Handle::current(); + rt_hdl.spawn(async move { + loop { + select! { + _ = sink.closed() => { + ctx.workflow_listeners.remove(&sink.connection_id()); + break Ok(()); + } + next_msg = stream.next() => { + let msg = match next_msg { + Some(Ok(notifier::Message { + header: Header { subscription: SubscriptionTyp::Cid(cid), ident }, + payload, + })) => { + let msg = ctx.workflow_listeners + .get(&sink.connection_id()) + .and_then(|v| { + let (v_cid, v_name) = v.value(); + if v_cid == &cid && (Some(v_name) == ident.as_ref() || ident.is_none()) { + Some(payload) + } else { + None + } + }); + msg + } + Some(Ok(notifier::Message { + header: notifier::Header { subscription: _sub, ..}, + .. + })) => { + continue; + } + Some(Err(err)) => { + error!("subscription stream error: {}", err); + ctx.workflow_listeners.remove(&sink.connection_id()); + break Err(err.into()); + } + None => break Ok(()), + }; + + if let Some(msg) = msg { + let sub_msg = SubscriptionMessage::from_json(&msg)?; + match sink.try_send(sub_msg) { + Ok(()) => (), + Err(TrySendError::Closed(_)) => { + break Err(anyhow!("subscription sink closed")); + } + Err(TrySendError::Full(_)) => { + info!("subscription sink full"); + } + } + } + } + } + } + }); + + Ok(()) + } } diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index c02576b5..3511f26f 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -160,11 +160,17 @@ impl Runner { let swarm = runtime.block_on(swarm::new(settings.node()))?; let webserver = webserver::Server::new(settings.node().network())?; + #[cfg(feature = "websocket-notify")] - let ws_msg_tx = webserver.notifier(); + let (ws_msg_tx, ws_evt_tx) = { + let ws_msg_tx = webserver.workflow_msg_notifier(); + let ws_evt_tx = webserver.evt_notifier(); + + (ws_msg_tx, ws_evt_tx) + }; #[cfg(feature = "websocket-notify")] - let event_handler = EventHandler::new(swarm, db, settings.node(), ws_msg_tx); + let event_handler = EventHandler::new(swarm, db, settings.node(), ws_evt_tx, ws_msg_tx); #[cfg(not(feature = "websocket-notify"))] let event_handler = EventHandler::new(swarm, db, settings.node()); @@ -268,9 +274,9 @@ impl Runner { runner_worker_tx.clone(), db.clone(), ).await { - Ok(_) => { + Ok(data) => { info!("sending message to rpc server"); - let _ = oneshot_tx.send(webserver::Message::AckWorkflow); + let _ = oneshot_tx.send(webserver::Message::AckWorkflow((data.info.cid, data.name))); } Err(err) => { error!(err=?err, "error handling ws message"); @@ -577,7 +583,7 @@ impl Runner { // `clone`, as the underlying type is an `Arc`. let initial_info = Arc::clone(&worker.workflow_info); let workflow_timeout = worker.workflow_settings.timeout; - let workflow_name = worker.workflow_name.to_string(); + let workflow_name = worker.workflow_name.clone(); let timestamp = worker.workflow_started; // Spawn worker, which initializees the scheduler and runs @@ -619,7 +625,7 @@ impl Runner { struct WorkflowData { info: Arc, - name: String, + name: FastStr, timestamp: NaiveDateTime, } diff --git a/homestar-runtime/src/runner/response.rs b/homestar-runtime/src/runner/response.rs index 7f656456..ea08e08b 100644 --- a/homestar-runtime/src/runner/response.rs +++ b/homestar-runtime/src/runner/response.rs @@ -6,6 +6,7 @@ use crate::{ workflow::{self, IndexedResources}, }; use chrono::NaiveDateTime; +use faststr::FastStr; use libipld::Cid; use serde::{Deserialize, Serialize}; use std::{fmt, net::SocketAddr, sync::Arc}; @@ -20,7 +21,7 @@ use tabled::{ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Tabled)] pub struct AckWorkflow { pub(crate) cid: Cid, - pub(crate) name: String, + pub(crate) name: FastStr, pub(crate) num_tasks: u32, #[tabled(skip)] pub(crate) progress: Vec, @@ -44,7 +45,7 @@ impl AckWorkflow { /// Workflow information for response / display. pub(crate) fn new( workflow_info: Arc, - name: String, + name: FastStr, timestamp: NaiveDateTime, ) -> Self { Self { diff --git a/homestar-runtime/src/workflow/info.rs b/homestar-runtime/src/workflow/info.rs index a82f6e99..4222b9e0 100644 --- a/homestar-runtime/src/workflow/info.rs +++ b/homestar-runtime/src/workflow/info.rs @@ -30,6 +30,7 @@ use tracing::info; pub const WORKFLOW_TAG: &str = "ipvm/workflow"; const CID_KEY: &str = "cid"; +const NAME_KEY: &str = "name"; const NUM_TASKS_KEY: &str = "num_tasks"; const PROGRESS_KEY: &str = "progress"; const PROGRESS_COUNT_KEY: &str = "progress_count"; @@ -137,6 +138,7 @@ impl StoredReceipt { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Info { pub(crate) cid: Cid, + pub(crate) name: Option, pub(crate) num_tasks: u32, pub(crate) progress: Vec, pub(crate) progress_count: u32, @@ -147,8 +149,11 @@ impl fmt::Display for Info { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "cid: {}, progress: {}/{}", - self.cid, self.progress_count, self.num_tasks + "cid: {}, local_name: {}, name, progress: {}/{}", + self.cid, + self.name.clone().unwrap_or(self.cid.to_string().into()), + self.progress_count, + self.num_tasks ) } } @@ -161,6 +166,7 @@ impl Info { let cid = stored.cid.cid(); Self { cid, + name: stored.name.map(|name| name.into()), num_tasks: stored.num_tasks as u32, progress, progress_count, @@ -173,6 +179,7 @@ impl Info { let cid = stored.cid.cid(); Self { cid, + name: stored.name.map(|name| name.into()), num_tasks: stored.num_tasks as u32, progress: vec![], progress_count: 0, @@ -379,6 +386,14 @@ impl From for Ipld { fn from(workflow: Info) -> Self { Ipld::Map(BTreeMap::from([ (CID_KEY.into(), Ipld::Link(workflow.cid)), + ( + NAME_KEY.into(), + workflow + .name + .as_ref() + .map(|name| name.to_string().into()) + .unwrap_or(Ipld::Null), + ), ( NUM_TASKS_KEY.into(), Ipld::Integer(workflow.num_tasks as i128), @@ -406,6 +421,13 @@ impl TryFrom for Info { .ok_or_else(|| anyhow!("no `cid` set"))? .to_owned(), )?; + let name = map + .get(NAME_KEY) + .and_then(|ipld| match ipld { + Ipld::Null => None, + ipld => Some(ipld), + }) + .and_then(|ipld| from_ipld(ipld.to_owned()).ok()); let num_tasks = from_ipld( map.get(NUM_TASKS_KEY) .ok_or_else(|| anyhow!("no `num_tasks` set"))? @@ -429,6 +451,7 @@ impl TryFrom for Info { Ok(Self { cid, + name, num_tasks, progress, progress_count, diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 9186786f..3ce917f4 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -30,6 +30,7 @@ pub(crate) const IPFS: &str = "ipfs"; static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); /// Start-up IPFS daemon for tests with the feature turned-on. +#[allow(dead_code)] #[cfg(feature = "ipfs")] pub(crate) fn startup_ipfs() -> Result<()> { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".ipfs"); diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 2becc51f..7f4ccc65 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -2,7 +2,7 @@ use crate::utils::startup_ipfs; use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME, IPFS}; use anyhow::Result; -use futures::StreamExt; +use futures::{FutureExt, StreamExt}; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, rpc_params, @@ -64,7 +64,7 @@ fn test_workflow_run_serial() -> Result<()> { .arg("tests/fixtures/test_workflow2.toml") .arg("--db") .arg("homestar_test_workflow_run_serial.db") - .stdout(Stdio::piped()) + //.stdout(Stdio::piped()) .spawn() .unwrap(); @@ -93,18 +93,21 @@ fn test_workflow_run_serial() -> Result<()> { let run_str = format!(r#"{{"name": "test","workflow": {}}}"#, json_string); let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); - let client = WsClientBuilder::default().build(ws_url).await.unwrap(); - let sub: Subscription> = client + let client1 = WsClientBuilder::default() + .build(ws_url.clone()) + .await + .unwrap(); + let sub1: Subscription> = client1 .subscribe( SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, - rpc_params![run], + rpc_params![run.clone()], UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, ) .await .unwrap(); - // we have 3 operations0 - sub.take(3) + // we have 3 operations + sub1.take(3) .for_each(|msg| async move { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let check = json.get("metadata").unwrap(); @@ -117,6 +120,36 @@ fn test_workflow_run_serial() -> Result<()> { } }) .await; + + // separate subscription, only 3 events too + let mut sub2: Subscription> = client1 + .subscribe( + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![run.clone()], + UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await + .unwrap(); + + assert!(sub2.next().await.is_some()); + assert!(sub2.next().await.is_some()); + assert!(sub2.next().await.is_some()); + assert!(sub2.next().now_or_never().is_none()); + + let client2 = WsClientBuilder::default().build(ws_url).await.unwrap(); + let mut sub3: Subscription> = client2 + .subscribe( + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![run], + UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await + .unwrap(); + + assert!(sub3.next().await.is_some()); + assert!(sub3.next().await.is_some()); + assert!(sub3.next().await.is_some()); + assert!(sub3.next().now_or_never().is_none()); }); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); From 3af2ff1f01d5ae7e81e0a9a2845a31ab76b2d96a Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 6 Nov 2023 16:28:09 -0500 Subject: [PATCH 07/42] fix: move to sub id as lookup key --- homestar-runtime/src/network/webserver/rpc.rs | 30 ++++++--------- homestar-runtime/tests/utils.rs | 27 ++++++++++++++ homestar-runtime/tests/webserver.rs | 37 +++++++++++++++---- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index e9fae049..67461f7a 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -19,7 +19,7 @@ use jsonrpsee::{ types::{error::ErrorCode, ErrorObjectOwned}, }; #[cfg(feature = "websocket-notify")] -use jsonrpsee::{ConnectionId, SubscriptionMessage, SubscriptionSink, TrySendError}; +use jsonrpsee::{types::SubscriptionId, SubscriptionMessage, SubscriptionSink, TrySendError}; #[cfg(feature = "websocket-notify")] use libipld::Cid; use metrics_exporter_prometheus::PrometheusHandle; @@ -36,7 +36,7 @@ use tokio::{ #[cfg(feature = "websocket-notify")] use tokio_stream::wrappers::BroadcastStream; #[cfg(feature = "websocket-notify")] -use tracing::{debug, error, info, warn}; +use tracing::{error, info, warn}; /// Health endpoint. pub(crate) const HEALTH_ENDPOINT: &str = "health"; @@ -63,7 +63,7 @@ pub(crate) struct Context { workflow_msg_notifier: Notifier, runner_sender: WsSender, receiver_timeout: Duration, - workflow_listeners: Arc>, + workflow_listeners: Arc, (Cid, FastStr)>>, } /// TODO @@ -188,12 +188,12 @@ impl JsonRpc { if let Ok(Ok(Message::AckWorkflow((cid, name)))) = time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await { + let sink = pending.accept().await?; ctx.workflow_listeners - .insert(pending.connection_id(), (cid, name)); - debug!( - "inserted workflow listener with connection_id: {:#?}", - pending.connection_id() - ); + .insert(sink.subscription_id(), (cid, name)); + let rx = ctx.workflow_msg_notifier.inner().subscribe(); + let stream = BroadcastStream::new(rx); + Self::handle_workflow_subscription(sink, stream, ctx).await?; } else { warn!("did not acknowledge message in time"); let _ = pending @@ -201,20 +201,13 @@ impl JsonRpc { ErrorCode::InternalError, ))) .await; - return Ok(()); } } Err(err) => { warn!("failed to parse run workflow params: {}", err); let _ = pending.reject(err).await; - return Ok(()); } } - - let sink = pending.accept().await?; - let rx = ctx.workflow_msg_notifier.inner().subscribe(); - let stream = BroadcastStream::new(rx); - Self::handle_workflow_subscription(sink, stream, ctx).await?; Ok(()) }, )?; @@ -280,7 +273,7 @@ impl JsonRpc { loop { select! { _ = sink.closed() => { - ctx.workflow_listeners.remove(&sink.connection_id()); + ctx.workflow_listeners.remove(&sink.subscription_id()); break Ok(()); } next_msg = stream.next() => { @@ -290,7 +283,7 @@ impl JsonRpc { payload, })) => { let msg = ctx.workflow_listeners - .get(&sink.connection_id()) + .get(&sink.subscription_id()) .and_then(|v| { let (v_cid, v_name) = v.value(); if v_cid == &cid && (Some(v_name) == ident.as_ref() || ident.is_none()) { @@ -309,7 +302,7 @@ impl JsonRpc { } Some(Err(err)) => { error!("subscription stream error: {}", err); - ctx.workflow_listeners.remove(&sink.connection_id()); + ctx.workflow_listeners.remove(&sink.subscription_id()); break Err(err.into()); } None => break Ok(()), @@ -320,6 +313,7 @@ impl JsonRpc { match sink.try_send(sub_msg) { Ok(()) => (), Err(TrySendError::Closed(_)) => { + ctx.workflow_listeners.remove(&sink.subscription_id()); break Err(anyhow!("subscription sink closed")); } Err(TrySendError::Full(_)) => { diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 3ce917f4..125b7328 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -13,6 +13,7 @@ use retry::{delay::Fixed, retry}; #[cfg(feature = "ipfs")] use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}; use std::{ + future::Future, path::PathBuf, process::{Child, Command, Stdio}, time::Duration, @@ -20,6 +21,7 @@ use std::{ #[cfg(not(windows))] use sysinfo::PidExt; use sysinfo::{ProcessExt, SystemExt}; +use tokio::time::{timeout, Timeout}; use wait_timeout::ChildExt; /// Binary name, which is different than the crate name. @@ -217,3 +219,28 @@ pub(crate) fn kill_homestar_daemon() -> Result<()> { Ok(()) } + +/// Helper extension trait which allows to limit execution time for the futures. +/// It is helpful in tests to ensure that no future will ever get stuck forever. +pub(crate) trait TimeoutFutureExt: Future + Sized { + /// Returns a reasonable value that can be used as a future timeout with a certain + /// degree of confidence that timeout won't be triggered by the test specifics. + fn default_timeout() -> Duration { + // If some future wasn't done in 60 seconds, it's either a poorly written test + // or (most likely) a bug related to some future never actually being completed. + const TIMEOUT_SECONDS: u64 = 60; + Duration::from_secs(TIMEOUT_SECONDS) + } + + /// Adds a fixed timeout to the future. + fn with_default_timeout(self) -> Timeout { + self.with_timeout(Self::default_timeout()) + } + + /// Adds a custom timeout to the future. + fn with_timeout(self, timeout_value: Duration) -> Timeout { + timeout(timeout_value, self) + } +} + +impl TimeoutFutureExt for U where U: Future + Sized {} diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 7f4ccc65..272e158b 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -1,8 +1,8 @@ #[cfg(feature = "ipfs")] use crate::utils::startup_ipfs; -use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME, IPFS}; +use crate::utils::{kill_homestar, stop_all_bins, TimeoutFutureExt, BIN_NAME, IPFS}; use anyhow::Result; -use futures::{FutureExt, StreamExt}; +use futures::StreamExt; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, rpc_params, @@ -64,7 +64,7 @@ fn test_workflow_run_serial() -> Result<()> { .arg("tests/fixtures/test_workflow2.toml") .arg("--db") .arg("homestar_test_workflow_run_serial.db") - //.stdout(Stdio::piped()) + .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -113,9 +113,7 @@ fn test_workflow_run_serial() -> Result<()> { let check = json.get("metadata").unwrap(); let expected1 = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); let expected2 = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); - if check == &expected1 || check == &expected2 { - println!("JSONRPC response is expected"); - } else { + if check != &expected1 && check != &expected2 { panic!("JSONRPC response is not expected"); } }) @@ -134,7 +132,6 @@ fn test_workflow_run_serial() -> Result<()> { assert!(sub2.next().await.is_some()); assert!(sub2.next().await.is_some()); assert!(sub2.next().await.is_some()); - assert!(sub2.next().now_or_never().is_none()); let client2 = WsClientBuilder::default().build(ws_url).await.unwrap(); let mut sub3: Subscription> = client2 @@ -146,10 +143,34 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); + let _ = sub2 + .next() + .with_timeout(std::time::Duration::from_millis(500)) + .await + .is_err(); assert!(sub3.next().await.is_some()); assert!(sub3.next().await.is_some()); assert!(sub3.next().await.is_some()); - assert!(sub3.next().now_or_never().is_none()); + + let another_run_str = format!(r#"{{"name": "another_test","workflow": {}}}"#, json_string); + let another_run: serde_json::Value = serde_json::from_str(&another_run_str).unwrap(); + let mut sub4: Subscription> = client2 + .subscribe( + SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![another_run], + UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await + .unwrap(); + + let _ = sub3 + .next() + .with_timeout(std::time::Duration::from_millis(500)) + .await + .is_err(); + assert!(sub4.next().await.is_some()); + assert!(sub4.next().await.is_some()); + assert!(sub4.next().await.is_some()); }); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); From 5659f06580c940b0bbcc151bcfe4993bf7fa65c4 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 6 Nov 2023 16:48:40 -0500 Subject: [PATCH 08/42] chore: cleanup --- homestar-runtime/tests/cli.rs | 34 ++++++++++++++++----------------- homestar-runtime/tests/utils.rs | 1 + 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index fb5d940a..65a4c80b 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -1,6 +1,6 @@ #[cfg(not(windows))] use crate::utils::kill_homestar_daemon; -use crate::utils::{kill_homestar, stop_all_bins, BIN_NAME}; +use crate::utils::{kill_homestar, stop_homestar, BIN_NAME}; use anyhow::Result; use assert_cmd::prelude::*; use once_cell::sync::Lazy; @@ -18,7 +18,7 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) #[test] #[file_serial] fn test_help_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("help") @@ -42,7 +42,7 @@ fn test_help_serial() -> Result<()> { .stdout(predicate::str::contains("help")) .stdout(predicate::str::contains("version")); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -50,7 +50,7 @@ fn test_help_serial() -> Result<()> { #[test] #[file_serial] fn test_version_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("--version") @@ -62,7 +62,7 @@ fn test_version_serial() -> Result<()> { env!("CARGO_PKG_VERSION") ))); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -70,7 +70,7 @@ fn test_version_serial() -> Result<()> { #[test] #[file_serial] fn test_server_not_running_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("ping") @@ -102,7 +102,7 @@ fn test_server_not_running_serial() -> Result<()> { .or(predicate::str::contains("No connection could be made"))), ); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -110,7 +110,7 @@ fn test_server_not_running_serial() -> Result<()> { #[test] #[file_serial] fn test_server_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("start") @@ -166,7 +166,7 @@ fn test_server_serial() -> Result<()> { let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -175,7 +175,7 @@ fn test_server_serial() -> Result<()> { #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); let mut homestar_proc = Command::new(BIN.as_os_str()) .arg("start") @@ -235,7 +235,7 @@ fn test_workflow_run_serial() -> Result<()> { let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -244,7 +244,7 @@ fn test_workflow_run_serial() -> Result<()> { #[file_serial] #[cfg(not(windows))] fn test_daemon_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("start") @@ -276,7 +276,7 @@ fn test_daemon_serial() -> Result<()> { .stdout(predicate::str::contains("127.0.0.1")) .stdout(predicate::str::contains("pong")); - let _ = stop_all_bins(); + let _ = stop_homestar(); let _ = kill_homestar_daemon(); Ok(()) @@ -286,7 +286,7 @@ fn test_daemon_serial() -> Result<()> { #[file_serial] #[cfg(windows)] fn test_signal_kill_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") @@ -317,7 +317,7 @@ fn test_signal_kill_serial() -> Result<()> { Command::new(BIN.as_os_str()).arg("ping").assert().failure(); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } @@ -326,7 +326,7 @@ fn test_signal_kill_serial() -> Result<()> { #[file_serial] #[cfg(windows)] fn test_server_v4_serial() -> Result<()> { - let _ = stop_all_bins(); + let _ = stop_homestar(); let mut homestar_proc = Command::new(BIN.as_os_str()) .arg("start") @@ -362,7 +362,7 @@ fn test_server_v4_serial() -> Result<()> { let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); - let _ = stop_all_bins(); + let _ = stop_homestar(); Ok(()) } diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 125b7328..61d0aea0 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -83,6 +83,7 @@ pub(crate) fn stop_ipfs() -> Result<()> { } /// Stop all binaries. +#[allow(dead_code)] pub(crate) fn stop_all_bins() -> Result<()> { let _ = stop_ipfs(); let _ = stop_homestar(); From 049a4ba738af76a04d0ca62cf181058704a6f19f Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 6 Nov 2023 18:14:16 -0500 Subject: [PATCH 09/42] chore: fix warning --- homestar-runtime/src/network/swarm.rs | 2 +- homestar-runtime/src/network/webserver/notifier.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homestar-runtime/src/network/swarm.rs b/homestar-runtime/src/network/swarm.rs index 825b221d..ff4f2392 100644 --- a/homestar-runtime/src/network/swarm.rs +++ b/homestar-runtime/src/network/swarm.rs @@ -40,7 +40,7 @@ pub(crate) async fn new(settings: &settings::Node) -> Result &[u8] { &self.payload } From 44b8485211356a10106ccf769bd0b6c3e178a664 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 6 Nov 2023 18:41:05 -0500 Subject: [PATCH 10/42] chore: comment --- homestar-runtime/src/network/webserver/notifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homestar-runtime/src/network/webserver/notifier.rs b/homestar-runtime/src/network/webserver/notifier.rs index 9a5e30b5..2630dd15 100644 --- a/homestar-runtime/src/network/webserver/notifier.rs +++ b/homestar-runtime/src/network/webserver/notifier.rs @@ -78,7 +78,7 @@ pub(crate) struct Message { } impl Message { - /// TODO + /// Create a new [Message]. pub(crate) fn new(header: Header, payload: Vec) -> Self { Self { header, payload } } From 64bfe77c6e27d9d620ba1d95c6b6545c142979ae Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 7 Nov 2023 04:55:03 -0500 Subject: [PATCH 11/42] chore: websocket->webserver --- homestar-runtime/tests/fixtures/test_rendezvous1.toml | 6 +++--- homestar-runtime/tests/fixtures/test_rendezvous2.toml | 2 +- homestar-runtime/tests/fixtures/test_rendezvous3.toml | 2 +- homestar-runtime/tests/fixtures/test_rendezvous4.toml | 2 +- homestar-runtime/tests/fixtures/test_rendezvous6.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homestar-runtime/tests/fixtures/test_rendezvous1.toml b/homestar-runtime/tests/fixtures/test_rendezvous1.toml index 46d2bd97..585e8837 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous1.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous1.toml @@ -5,9 +5,9 @@ console_subscriber_port = 5585 [node] [node.network] -metrics_port = 4035 -rpc_port = 9825 -websocket_port = 8025 +metrics_port = 4034 +rpc_port = 9824 +webserver_port = 8024 listen_address = "/ip4/127.0.0.1/tcp/7000" enable_rendezvous_server = true enable_mdns = false diff --git a/homestar-runtime/tests/fixtures/test_rendezvous2.toml b/homestar-runtime/tests/fixtures/test_rendezvous2.toml index b285c6f2..bb142da0 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous2.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous2.toml @@ -7,7 +7,7 @@ console_subscriber_port = 5586 [node.network] metrics_port = 4036 rpc_port = 9826 -websocket_port = 8026 +webserver_port = 8026 listen_address = "/ip4/127.0.0.1/tcp/7001" announce_addresses = [ "/ip4/127.0.0.1/tcp/7001/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", diff --git a/homestar-runtime/tests/fixtures/test_rendezvous3.toml b/homestar-runtime/tests/fixtures/test_rendezvous3.toml index e8f8d444..a9fceb06 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous3.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous3.toml @@ -7,7 +7,7 @@ console_subscriber_port = 5587 [node.network] metrics_port = 4037 rpc_port = 9827 -websocket_port = 8027 +webserver_port = 8027 listen_address = "/ip4/127.0.0.1/tcp/7002" node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", diff --git a/homestar-runtime/tests/fixtures/test_rendezvous4.toml b/homestar-runtime/tests/fixtures/test_rendezvous4.toml index 07c57f3a..9e404c84 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous4.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous4.toml @@ -7,7 +7,7 @@ console_subscriber_port = 5588 [node.network] metrics_port = 4038 rpc_port = 9828 -websocket_port = 8028 +webserver_port = 8028 listen_address = "/ip4/127.0.0.1/tcp/7003" announce_addresses = [ "/ip4/127.0.0.1/tcp/7003/p2p/12D3KooWJWoaqZhDaoEFshF7Rh1bpY9ohihFhzcW6d69Lr2NASuq", diff --git a/homestar-runtime/tests/fixtures/test_rendezvous6.toml b/homestar-runtime/tests/fixtures/test_rendezvous6.toml index a1792f00..c96d9a14 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous6.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous6.toml @@ -7,7 +7,7 @@ console_subscriber_port = 5588 [node.network] metrics_port = 4040 rpc_port = 9830 -websocket_port = 8030 +webserver_port = 8030 listen_address = "/ip4/127.0.0.1/tcp/7005" announce_addresses = [ "/ip4/127.0.0.1/tcp/7005/p2p/12D3KooWPT98FXMfDQYavZm66EeVjTqP9Nnehn1gyaydqV8L8BQw", From 058deefe8bc79f2ece4454ee1e0061873dc7fc29 Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:54:28 -0800 Subject: [PATCH 12/42] feat: Add event notifications (#410) # Description This pull request implements the following changes: - [x] Add event notifications - [x] Add `emit_event` notification utility function - [x] Add `emit_receipt` notification utility function - [x] Move receipt notifications to event handler notifications - [x] Add `ConnnectionEstablished`, `ConnnectionClosed`, `ListeningOn`, `OutgoingConnectionError`, and `IncomingConnectionError` network notifications - [x] Test JSON event notification bytes roundtrip - [x] Test JSON event notification string roundtrip - [x] Integration test connection notifications with two Homestar nodes ## Link to issue Closes #407 ## Type of change - [x] New feature (non-breaking change that adds functionality) ## Test plan (required) We have included unit tests to check roundtrip conversions between JSON bytes and strings. In addition, we have included an integration test that subscribes and listens for connection messages between Homestar nodes. --------- Co-authored-by: Zeeshan Lakhani --- homestar-runtime/Cargo.toml | 2 +- homestar-runtime/src/event_handler.rs | 1 + homestar-runtime/src/event_handler/event.rs | 70 +---- .../src/event_handler/notification.rs | 242 ++++++++++++++++++ .../src/event_handler/notification/receipt.rs | 44 ++++ .../src/event_handler/notification/swarm.rs | 44 ++++ .../src/event_handler/swarm_event.rs | 72 +++++- homestar-runtime/src/network/webserver.rs | 11 +- .../src/network/webserver/notifier.rs | 45 +--- .../tests/fixtures/test_notification1.toml | 20 ++ .../tests/fixtures/test_notification2.toml | 20 ++ homestar-runtime/tests/network.rs | 6 +- .../tests/network/notification.rs | 127 +++++++++ 13 files changed, 591 insertions(+), 113 deletions(-) create mode 100644 homestar-runtime/src/event_handler/notification.rs create mode 100644 homestar-runtime/src/event_handler/notification/receipt.rs create mode 100644 homestar-runtime/src/event_handler/notification/swarm.rs create mode 100644 homestar-runtime/tests/fixtures/test_notification1.toml create mode 100644 homestar-runtime/tests/fixtures/test_notification2.toml create mode 100644 homestar-runtime/tests/network/notification.rs diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 83f0b90b..9d146f3f 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -106,6 +106,7 @@ libp2p = { version = "0.52", default-features = false, features = [ libsqlite3-sys = { version = "0.26", default-features = false, features = [ "bundled", ] } +maplit = "1.0" metrics = { version = "0.21", default-features = false } metrics-exporter-prometheus = { version = "0.12.1", default-features = false, features = [ "http-listener", @@ -194,7 +195,6 @@ homestar_runtime_proc_macro = { path = "src/test_utils/proc_macro", package = "h jsonrpsee = { version = "0.20", default-features = false, features = [ "client", ] } -maplit = "1.0" nix = { version = "0.27", features = ["signal"] } predicates = { version = "3.0", default-features = false } prometheus-parse = "0.2.4" diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index 02db93c1..269bc736 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -25,6 +25,7 @@ pub(crate) mod cache; pub mod channel; pub(crate) mod error; pub(crate) mod event; +pub(crate) mod notification; pub(crate) mod swarm_event; pub(crate) use cache::{setup_cache, CacheValue}; pub(crate) use error::RequestResponseError; diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 368f0325..7c222cf7 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -2,7 +2,7 @@ use super::EventHandler; #[cfg(feature = "websocket-notify")] -use crate::network::webserver::notifier::{Header, Message, NotifyReceipt, SubscriptionTyp}; +use crate::event_handler::notification::emit_receipt; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -16,15 +16,9 @@ use crate::{ }; use anyhow::Result; use async_trait::async_trait; -use homestar_core::workflow::Receipt as InvocationReceipt; #[cfg(feature = "websocket-notify")] -use homestar_core::{ - ipld::DagJson, - workflow::{ - receipt::metadata::{WORKFLOW_KEY, WORKFLOW_NAME_KEY}, - Pointer, - }, -}; +use homestar_core::workflow::Pointer; +use homestar_core::workflow::Receipt as InvocationReceipt; use libipld::{Cid, Ipld}; use libp2p::{ kad::{record::Key, Quorum, Record}, @@ -251,23 +245,11 @@ impl Captured { #[cfg(feature = "websocket-notify")] { - let invocation_notification = invocation_receipt.clone(); - let ws_tx = event_handler.ws_workflow_sender(); - let metadata = self.metadata.to_owned(); - let receipt = NotifyReceipt::with(invocation_notification, receipt_cid, metadata); - if let Ok(json) = receipt.to_json() { - info!( - cid = receipt_cid.to_string(), - "Sending receipt to websocket" - ); - let _ = ws_tx.notify(Message::new( - Header::new( - SubscriptionTyp::Cid(self.workflow.cid), - self.workflow.name.clone(), - ), - json, - )); - } + emit_receipt( + event_handler.ws_workflow_sender(), + receipt.clone(), + self.metadata.to_owned(), + ) } if event_handler.pubsub_enabled { @@ -367,37 +349,13 @@ impl Replay { self.pointers.iter().collect::>() ); + #[cfg(feature = "websocket-notify")] receipts.into_iter().for_each(|receipt| { - let invocation_receipt = InvocationReceipt::from(&receipt); - let invocation_notification = invocation_receipt; - let receipt_cid = receipt.cid(); - - let ws_tx = event_handler.ws_workflow_sender(); - let metadata = self.metadata.to_owned(); - let receipt = NotifyReceipt::with(invocation_notification, receipt_cid, metadata); - if let Ok(json) = receipt.to_json() { - info!( - cid = receipt_cid.to_string(), - "Sending receipt to websocket" - ); - - if let Some(ipld) = &self.metadata { - match (ipld.get(WORKFLOW_KEY), ipld.get(WORKFLOW_NAME_KEY)) { - (Ok(Ipld::Link(cid)), Ok(Ipld::String(name))) => { - let header = Header::new( - SubscriptionTyp::Cid(*cid), - Some((name.to_string()).into()), - ); - let _ = ws_tx.notify(Message::new(header, json)); - } - (Ok(Ipld::Link(cid)), Err(_err)) => { - let header = Header::new(SubscriptionTyp::Cid(*cid), None); - let _ = ws_tx.notify(Message::new(header, json)); - } - _ => (), - } - } - } + emit_receipt( + event_handler.ws_workflow_sender(), + receipt, + self.metadata.to_owned(), + ); }); Ok(()) diff --git a/homestar-runtime/src/event_handler/notification.rs b/homestar-runtime/src/event_handler/notification.rs new file mode 100644 index 00000000..8daa09a3 --- /dev/null +++ b/homestar-runtime/src/event_handler/notification.rs @@ -0,0 +1,242 @@ +use crate::{ + network::webserver::{ + notifier::{self, Header, Message, Notifier, SubscriptionTyp}, + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + }, + Receipt, +}; +use anyhow::anyhow; +use chrono::prelude::Utc; +use homestar_core::{ + ipld::DagJson, + workflow::{ + receipt::metadata::{WORKFLOW_KEY, WORKFLOW_NAME_KEY}, + Receipt as InvocationReceipt, + }, +}; +use libipld::{serde::from_ipld, Ipld}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, str::FromStr}; +use tracing::{info, warn}; + +pub(crate) mod receipt; +pub(crate) mod swarm; +pub(crate) use receipt::ReceiptNotification; +pub(crate) use swarm::SwarmNotification; + +const TYPE_KEY: &str = "type"; +const DATA_KEY: &str = "data"; +const TIMESTAMP_KEY: &str = "timestamp"; + +/// Send receipt notification as bytes. +pub(crate) fn emit_receipt( + notifier: Notifier, + receipt: Receipt, + metadata: Option, +) { + let invocation_receipt = InvocationReceipt::from(&receipt); + let receipt_cid = receipt.cid(); + let notification = ReceiptNotification::with(invocation_receipt, receipt_cid, metadata.clone()); + + if let Ok(json) = notification.to_json() { + info!( + cid = receipt_cid.to_string(), + "Sending receipt to websocket" + ); + if let Some(ipld) = metadata { + match (ipld.get(WORKFLOW_KEY), ipld.get(WORKFLOW_NAME_KEY)) { + (Ok(Ipld::Link(cid)), Ok(Ipld::String(name))) => { + let header = + Header::new(SubscriptionTyp::Cid(*cid), Some((name.to_string()).into())); + let _ = notifier.notify(Message::new(header, json)); + } + (Ok(Ipld::Link(cid)), Err(_err)) => { + let header = Header::new(SubscriptionTyp::Cid(*cid), None); + let _ = notifier.notify(Message::new(header, json)); + } + _ => (), + } + } + } else { + warn!("Unable to serialize receipt as bytes: {receipt:?}"); + } +} + +/// Send event notification as bytes. +pub(crate) fn emit_event( + notifier: Notifier, + ty: EventNotificationTyp, + data: BTreeMap<&str, String>, +) { + let header = Header::new( + SubscriptionTyp::EventSub(SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string()), + None, + ); + let notification = EventNotification::new(ty, data); + + if let Ok(json) = notification.to_json() { + let _ = notifier.notify(Message::new(header, json)); + } else { + warn!("Unable to serialize notification as bytes: {notification:?}"); + } +} + +/// Notification sent to clients. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) struct EventNotification { + typ: EventNotificationTyp, + data: Ipld, + timestamp: i64, +} + +impl EventNotification { + pub(crate) fn new(typ: EventNotificationTyp, data: BTreeMap<&str, String>) -> Self { + let ipld_data = data + .iter() + .map(|(key, val)| (key.to_string(), Ipld::String(val.to_owned()))) + .collect(); + + Self { + typ, + data: Ipld::Map(ipld_data), + timestamp: Utc::now().timestamp_millis(), + } + } +} + +impl DagJson for EventNotification where Ipld: From {} + +impl From for Ipld { + fn from(notification: EventNotification) -> Self { + Ipld::Map(BTreeMap::from([ + ("type".into(), notification.typ.into()), + ("data".into(), notification.data), + ("timestamp".into(), notification.timestamp.into()), + ])) + } +} + +impl TryFrom for EventNotification { + type Error = anyhow::Error; + + fn try_from(ipld: Ipld) -> Result { + let map = from_ipld::>(ipld)?; + + let typ: EventNotificationTyp = map + .get(TYPE_KEY) + .ok_or_else(|| anyhow!("missing {TYPE_KEY}"))? + .to_owned() + .try_into()?; + + let data = map + .get(DATA_KEY) + .ok_or_else(|| anyhow!("missing {DATA_KEY}"))? + .to_owned(); + + let timestamp = from_ipld( + map.get(TIMESTAMP_KEY) + .ok_or_else(|| anyhow!("missing {TIMESTAMP_KEY}"))? + .to_owned(), + )?; + + Ok(EventNotification { + typ, + data, + timestamp, + }) + } +} + +/// Types of notification sent to clients. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) enum EventNotificationTyp { + SwarmNotification(SwarmNotification), +} + +impl DagJson for EventNotificationTyp where Ipld: From {} + +impl From for Ipld { + fn from(typ: EventNotificationTyp) -> Self { + match typ { + EventNotificationTyp::SwarmNotification(subtype) => { + Ipld::String(format!("network:{}", subtype)) + } + } + } +} + +impl TryFrom for EventNotificationTyp { + type Error = anyhow::Error; + + fn try_from(ipld: Ipld) -> Result { + if let Some((ty, subtype)) = from_ipld::(ipld)?.split_once(':') { + match ty { + "network" => Ok(EventNotificationTyp::SwarmNotification( + SwarmNotification::from_str(subtype)?, + )), + _ => Err(anyhow!("Missing event notification type: {}", ty)), + } + } else { + Err(anyhow!( + "Event notification type missing colon delimiter between type and subtype." + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use libp2p::PeerId; + use maplit::btreemap; + + #[test] + fn notification_bytes_rountrip() { + let peer_id = PeerId::random().to_string(); + let address: String = "/ip4/127.0.0.1/tcp/7000".to_string(); + + let notification = EventNotification::new( + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished), + btreemap! { + "peer_id" => peer_id.clone(), + "address" => address.clone() + }, + ); + let bytes = notification.to_json().unwrap(); + + let parsed = EventNotification::from_json(bytes.as_ref()).unwrap(); + let data: BTreeMap = from_ipld(parsed.data).unwrap(); + + assert_eq!( + parsed.typ, + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished) + ); + assert_eq!(data.get("peer_id").unwrap(), &peer_id); + assert_eq!(data.get("address").unwrap(), &address); + } + + #[test] + fn notification_json_string_rountrip() { + let peer_id = PeerId::random().to_string(); + let address: String = "/ip4/127.0.0.1/tcp/7000".to_string(); + + let notification = EventNotification::new( + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished), + btreemap! { + "peer_id" => peer_id.clone(), + "address" => address.clone() + }, + ); + let json_string = notification.to_json_string().unwrap(); + + let parsed = EventNotification::from_json_string(json_string).unwrap(); + let data: BTreeMap = from_ipld(parsed.data).unwrap(); + + assert_eq!( + parsed.typ, + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished) + ); + assert_eq!(data.get("peer_id").unwrap(), &peer_id); + assert_eq!(data.get("address").unwrap(), &address); + } +} diff --git a/homestar-runtime/src/event_handler/notification/receipt.rs b/homestar-runtime/src/event_handler/notification/receipt.rs new file mode 100644 index 00000000..f2f222ec --- /dev/null +++ b/homestar-runtime/src/event_handler/notification/receipt.rs @@ -0,0 +1,44 @@ +use homestar_core::{ipld::DagJson, workflow::Receipt}; +use libipld::{ipld, Cid, Ipld}; + +/// A [Receipt] that is sent out *just* for websocket notifications. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ReceiptNotification(Ipld); + +impl ReceiptNotification { + /// TODO + #[allow(dead_code)] + pub(crate) fn inner(&self) -> &Ipld { + &self.0 + } + + /// TODO + #[allow(dead_code)] + pub(crate) fn into_inner(self) -> Ipld { + self.0.to_owned() + } + + pub(crate) fn with(receipt: Receipt, cid: Cid, metadata: Option) -> Self { + let receipt: Ipld = receipt.into(); + let data = ipld!({ + "receipt": receipt, + "metadata": metadata.as_ref().map(|m| m.to_owned()).map_or(Ipld::Null, |m| m), + "receipt_cid": cid, + }); + ReceiptNotification(data) + } +} + +impl DagJson for ReceiptNotification where Ipld: From {} + +impl From for Ipld { + fn from(receipt: ReceiptNotification) -> Self { + receipt.0 + } +} + +impl From for ReceiptNotification { + fn from(ipld: Ipld) -> Self { + ReceiptNotification(ipld) + } +} diff --git a/homestar-runtime/src/event_handler/notification/swarm.rs b/homestar-runtime/src/event_handler/notification/swarm.rs new file mode 100644 index 00000000..8e98947b --- /dev/null +++ b/homestar-runtime/src/event_handler/notification/swarm.rs @@ -0,0 +1,44 @@ +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; +use std::{fmt, str::FromStr}; + +// Swarm notification types sent to clients +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) enum SwarmNotification { + ConnnectionEstablished, + ConnnectionClosed, + ListeningOn, + OutgoingConnectionError, + IncomingConnectionError, +} + +impl fmt::Display for SwarmNotification { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + SwarmNotification::ConnnectionEstablished => write!(f, "connectionEstablished"), + SwarmNotification::ConnnectionClosed => write!(f, "connectionClosed"), + SwarmNotification::ListeningOn => write!(f, "listeningOn"), + SwarmNotification::OutgoingConnectionError => { + write!(f, "outgoingConnectionError") + } + SwarmNotification::IncomingConnectionError => { + write!(f, "incomingConnectionError") + } + } + } +} + +impl FromStr for SwarmNotification { + type Err = anyhow::Error; + + fn from_str(ty: &str) -> Result { + match ty { + "connectionEstablished" => Ok(Self::ConnnectionEstablished), + "connectionClosed" => Ok(Self::ConnnectionClosed), + "listeningOn" => Ok(Self::ListeningOn), + "outgoingConnectionError" => Ok(Self::OutgoingConnectionError), + "incomingConnectionError" => Ok(Self::IncomingConnectionError), + _ => Err(anyhow!("Missing swarm notification type: {}", ty)), + } + } +} diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index 3bed8c4a..2c22db6e 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -8,6 +8,7 @@ use crate::{ event_handler::{ cache::{self, CacheData, CacheValue}, event::QueryRecord, + notification::{self, EventNotificationTyp, SwarmNotification}, Event, Handler, RequestResponseError, }, libp2p::multiaddr::MultiaddrExt, @@ -39,6 +40,7 @@ use libp2p::{ swarm::{dial_opts::DialOpts, SwarmEvent}, PeerId, StreamProtocol, }; +use maplit::btreemap; use std::{ collections::{HashMap, HashSet}, fmt, @@ -711,9 +713,20 @@ async fn handle_swarm_event( } SwarmEvent::NewListenAddr { address, .. } => { let local_peer = *event_handler.swarm.local_peer_id(); + info!( - "local node is listening on {}", - address.with(Protocol::P2p(local_peer)) + peer_id = local_peer.to_string(), + "local node is listening on {}", address + ); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification(SwarmNotification::ListeningOn), + btreemap! { + "peer_id" => local_peer.to_string(), + "address" => address.to_string() + }, ); } SwarmEvent::IncomingConnection { .. } => {} @@ -722,9 +735,27 @@ async fn handle_swarm_event( } => { debug!(peer_id=peer_id.to_string(), endpoint=?endpoint, "peer connection established"); // add peer to connected peers list - event_handler.connections.peers.insert(peer_id, endpoint); + event_handler + .connections + .peers + .insert(peer_id, endpoint.clone()); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished), + btreemap! { + "peer_id" => peer_id.to_string(), + "address" => endpoint.get_remote_address().to_string() + }, + ); } - SwarmEvent::ConnectionClosed { peer_id, cause, .. } => { + SwarmEvent::ConnectionClosed { + peer_id, + cause, + endpoint, + .. + } => { debug!( peer_id = peer_id.to_string(), "peer connection closed, cause: {cause:?}" @@ -753,6 +784,16 @@ async fn handle_swarm_event( "removed peer from kademlia table" ); } + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionClosed), + btreemap! { + "peer_id" => peer_id.to_string(), + "address" => endpoint.get_remote_address().to_string() + }, + ); } SwarmEvent::OutgoingConnectionError { connection_id, @@ -764,7 +805,17 @@ async fn handle_swarm_event( err=?error, connection_id=?connection_id, "outgoing connection error" - ) + ); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification(SwarmNotification::OutgoingConnectionError), + btreemap! { + "peer_id" => peer_id.map_or("Unknown peer".into(), |p| p.to_string()), + "error" => error.to_string() + }, + ); } SwarmEvent::IncomingConnectionError { connection_id, @@ -778,7 +829,16 @@ async fn handle_swarm_event( local_address=local_addr.to_string(), remote_address=send_back_addr.to_string(), "incoming connection error" - ) + ); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification(SwarmNotification::IncomingConnectionError), + btreemap! { + "error" => error.to_string() + }, + ); } SwarmEvent::ListenerError { listener_id, error } => { error!(err=?error, listener_id=?listener_id, "listener error") diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index 738986c0..91573977 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -32,6 +32,7 @@ mod rpc; #[cfg(feature = "websocket-notify")] pub(crate) use notifier::Notifier; +pub(crate) use rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT; use rpc::{Context, JsonRpc}; /// Message type for messages sent back from the @@ -234,7 +235,7 @@ fn port_available(host: IpAddr, port: u16) -> bool { #[cfg(test)] mod test { use super::*; - use crate::settings::Settings; + use crate::{event_handler::notification::ReceiptNotification, settings::Settings}; use homestar_core::test_utils; #[cfg(feature = "websocket-notify")] use homestar_core::{ @@ -247,7 +248,7 @@ mod test { use jsonrpsee::types::error::ErrorCode; use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; #[cfg(feature = "websocket-notify")] - use notifier::{self, Header, NotifyReceipt}; + use notifier::{self, Header}; use serial_test::file_serial; use tokio::sync::mpsc; @@ -361,7 +362,7 @@ mod test { // send any bytes through (Vec) let (invocation_receipt, runtime_receipt) = crate::test_utils::receipt::receipts(); - let receipt = NotifyReceipt::with(invocation_receipt, runtime_receipt.cid(), None); + let receipt = ReceiptNotification::with(invocation_receipt, runtime_receipt.cid(), None); server .evt_notifier .notify(notifier::Message::new( @@ -401,11 +402,11 @@ mod test { .unwrap(); let msg1 = sub.next().await.unwrap().unwrap(); - let returned1: NotifyReceipt = DagJson::from_json(&msg1).unwrap(); + let returned1: ReceiptNotification = DagJson::from_json(&msg1).unwrap(); assert_eq!(returned1, receipt); let msg2 = sub.next().await.unwrap().unwrap(); - let _returned1: NotifyReceipt = DagJson::from_json(&msg2).unwrap(); + let _returned1: ReceiptNotification = DagJson::from_json(&msg2).unwrap(); assert!(sub.unsubscribe().await.is_ok()); diff --git a/homestar-runtime/src/network/webserver/notifier.rs b/homestar-runtime/src/network/webserver/notifier.rs index 2630dd15..dc7abd6a 100644 --- a/homestar-runtime/src/network/webserver/notifier.rs +++ b/homestar-runtime/src/network/webserver/notifier.rs @@ -2,8 +2,7 @@ use anyhow::Result; use faststr::FastStr; -use homestar_core::{ipld::DagJson, workflow::Receipt}; -use libipld::{ipld, Cid, Ipld}; +use libipld::Cid; use std::{fmt, sync::Arc}; use tokio::sync::broadcast; @@ -95,45 +94,3 @@ impl Message { &self.payload } } - -/// A [Receipt] that is sent out *just* for websocket notifications. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct NotifyReceipt(Ipld); - -impl NotifyReceipt { - /// TODO - #[allow(dead_code)] - pub(crate) fn inner(&self) -> &Ipld { - &self.0 - } - - /// TODO - #[allow(dead_code)] - pub(crate) fn into_inner(self) -> Ipld { - self.0.to_owned() - } - - pub(crate) fn with(receipt: Receipt, cid: Cid, metadata: Option) -> Self { - let receipt: Ipld = receipt.into(); - let data = ipld!({ - "receipt": receipt, - "metadata": metadata.as_ref().map(|m| m.to_owned()).map_or(Ipld::Null, |m| m), - "receipt_cid": cid, - }); - NotifyReceipt(data) - } -} - -impl DagJson for NotifyReceipt where Ipld: From {} - -impl From for Ipld { - fn from(receipt: NotifyReceipt) -> Self { - receipt.0 - } -} - -impl From for NotifyReceipt { - fn from(ipld: Ipld) -> Self { - NotifyReceipt(ipld) - } -} diff --git a/homestar-runtime/tests/fixtures/test_notification1.toml b/homestar-runtime/tests/fixtures/test_notification1.toml new file mode 100644 index 00000000..979515fd --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_notification1.toml @@ -0,0 +1,20 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5582 + +[node] + +[node.network] +metrics_port = 4032 +rpc_port = 9822 +webserver_port = 8022 +listen_address = "/ip4/127.0.0.1/tcp/7010" +node_addresses = [ + "/ip4/127.0.0.1/tcp/7011/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", +] +enable_mdns = false +enable_rendezvous_client = false + +# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } diff --git a/homestar-runtime/tests/fixtures/test_notification2.toml b/homestar-runtime/tests/fixtures/test_notification2.toml new file mode 100644 index 00000000..dd22b7a1 --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_notification2.toml @@ -0,0 +1,20 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5583 + +[node] + +[node.network] +metrics_port = 4033 +rpc_port = 9823 +webserver_port = 8023 +listen_address = "/ip4/127.0.0.1/tcp/7011" +node_addresses = [ + "/ip4/127.0.0.1/tcp/7010/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", +] +enable_mdns = false +enable_rendezvous_client = false + +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index 7bfd507d..0884b91c 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -11,6 +11,9 @@ use std::{ time::Duration, }; +#[cfg(feature = "websocket-notify")] +mod notification; + #[allow(dead_code)] static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); @@ -65,7 +68,8 @@ fn test_libp2p_listens_on_address_serial() -> Result<()> { stdout, vec![ "local node is listening", - "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", + "/ip4/127.0.0.1/tcp/7000", + "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ], ); diff --git a/homestar-runtime/tests/network/notification.rs b/homestar-runtime/tests/network/notification.rs new file mode 100644 index 00000000..ea2d9b19 --- /dev/null +++ b/homestar-runtime/tests/network/notification.rs @@ -0,0 +1,127 @@ +use crate::utils::{kill_homestar, stop_homestar, BIN_NAME}; +use anyhow::Result; +use jsonrpsee::{ + core::client::{Subscription, SubscriptionClientT}, + rpc_params, + ws_client::WsClientBuilder, +}; +use once_cell::sync::Lazy; +use retry::{delay::Exponential, retry}; +use serial_test::file_serial; +use std::{ + net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, + path::PathBuf, + process::{Command, Stdio}, + time::Duration, +}; + +static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); +const SUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "subscribe_network_events"; +const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_network_events"; + +#[test] +#[file_serial] +fn test_connection_notifications_serial() -> Result<()> { + let _ = stop_homestar(); + + let homestar_proc1 = Command::new(BIN.as_os_str()) + .env( + "RUST_LOG", + "homestar=debug,homestar_runtime=debug,libp2p=debug,libp2p_gossipsub::behaviour=debug", + ) + .arg("start") + .arg("-c") + .arg("tests/fixtures/test_notification1.toml") + .arg("--db") + .arg("homestar1.db") + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let ws_port = 8022; + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ws_port); + let result = retry(Exponential::from_millis(1000).take(10), || { + TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) + }); + + if result.is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + + let ws_url = format!("ws://{}:{}", Ipv4Addr::LOCALHOST, ws_port); + tokio_test::block_on(async { + tokio_tungstenite::connect_async(ws_url.clone()) + .await + .unwrap(); + + let client = WsClientBuilder::default() + .build(ws_url.clone()) + .await + .unwrap(); + let mut sub: Subscription> = client + .subscribe( + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + rpc_params![], + UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + ) + .await + .unwrap(); + + tokio::time::sleep(Duration::from_millis(200)).await; + + let homestar_proc2 = Command::new(BIN.as_os_str()) + .env( + "RUST_LOG", + "homestar=debug,homestar_runtime=debug,libp2p=debug,libp2p_gossipsub::behaviour=debug", + ) + .arg("start") + .arg("-c") + .arg("tests/fixtures/test_notification2.toml") + .arg("--db") + .arg("homestar2.db") + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let _ = kill_homestar(homestar_proc2, None); + + tokio::time::sleep(Duration::from_secs(2)).await; + + { + let msg = sub + .next() + .await + .expect("Subscription did not receive a connection established message"); + let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); + let typ = json["type"].as_str().unwrap(); + let peer_id = json["data"]["peer_id"].as_str().unwrap(); + + assert_eq!(typ, "network:connectionEstablished"); + assert_eq!( + peer_id, + "16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc" + ); + } + + { + let msg = sub + .next() + .await + .expect("Subscription did not receive a connection closed message"); + let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); + let typ = json["type"].as_str().unwrap(); + let peer_id = json["data"]["peer_id"].as_str().unwrap(); + + assert_eq!(typ, "network:connectionClosed"); + assert_eq!( + peer_id, + "16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc" + ); + } + }); + + let _ = kill_homestar(homestar_proc1, None); + + Ok(()) +} From 713718e7c9ae1ca4210afe6b2b3b677e69f39437 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 05:11:02 -0500 Subject: [PATCH 13/42] fix: p2p timeout, ipfs settings, more --- .github/workflows/tests_and_checks.yml | 4 +- Cargo.lock | 5 + DEVELOPMENT.md | 2 +- flake.nix | 16 +- homestar-runtime/Cargo.toml | 2 + homestar-runtime/src/event_handler.rs | 5 +- homestar-runtime/src/event_handler/event.rs | 83 +++- .../src/event_handler/notification.rs | 4 +- .../src/event_handler/swarm_event.rs | 8 +- homestar-runtime/src/network/ipfs.rs | 21 +- homestar-runtime/src/network/pubsub.rs | 7 +- homestar-runtime/src/network/webserver.rs | 460 +++++++++--------- homestar-runtime/src/network/webserver/rpc.rs | 83 +++- homestar-runtime/src/runner.rs | 115 +++-- homestar-runtime/src/runner/nodeinfo.rs | 21 + homestar-runtime/src/scheduler.rs | 15 +- homestar-runtime/src/settings.rs | 61 ++- .../src/test_utils/worker_builder.rs | 96 +++- homestar-runtime/src/worker.rs | 40 +- homestar-runtime/src/workflow/settings.rs | 2 +- homestar-runtime/tests/webserver.rs | 21 +- 21 files changed, 701 insertions(+), 370 deletions(-) create mode 100644 homestar-runtime/src/runner/nodeinfo.rs diff --git a/.github/workflows/tests_and_checks.yml b/.github/workflows/tests_and_checks.yml index f6788cca..512e1878 100644 --- a/.github/workflows/tests_and_checks.yml +++ b/.github/workflows/tests_and_checks.yml @@ -189,7 +189,7 @@ jobs: - name: Run Tests (no-default-features) if: ${{ matrix.default-features == 'none' }} - run: cargo nextest run --workspace --profile ci --no-default-features --features "test-utils" + run: cargo nextest run --profile ci --no-default-features --features "test-utils" - name: Run Doc Tests if: ${{ matrix.default-features == 'all' }} @@ -241,7 +241,7 @@ jobs: - name: Run Tests (no-default-features) if: ${{ matrix.default-features == 'none' }} - run: cargo nextest run --workspace --profile ci --no-default-features --features "test-utils" + run: cargo nextest run --profile ci --no-default-features --features "test-utils" - name: Run Doc Tests if: ${{ matrix.default-features == 'all' }} diff --git a/Cargo.lock b/Cargo.lock index 4383bc23..23a5e284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3449,6 +3449,7 @@ dependencies = [ "quick-protobuf", "rand", "rw-stream-sink", + "serde", "smallvec", "thiserror", "unsigned-varint", @@ -3497,6 +3498,7 @@ dependencies = [ "quick-protobuf-codec", "rand", "regex", + "serde", "sha2 0.10.8", "smallvec", "unsigned-varint", @@ -3541,6 +3543,7 @@ dependencies = [ "multihash 0.19.1", "quick-protobuf", "rand", + "serde", "sha2 0.10.8", "thiserror", "zeroize", @@ -3567,6 +3570,7 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "rand", + "serde", "sha2 0.10.8", "smallvec", "thiserror", @@ -4307,6 +4311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" dependencies = [ "core2", + "serde", "unsigned-varint", ] diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 694ae5de..072e8502 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -101,7 +101,7 @@ hooks. Please run this before every commit and/or push. * **`nx-test`**, which translates to `cargo nextest run --workspace && cargo test --workspace --doc` * **`x-test`** for testing continuously as files change, translating to - `cargo watch -c -s "cargo nextest run --workspace --nocapture && cargo test --doc"` + `cargo watch -c -s "cargo nextest run --workspace --no-capture && cargo test --doc"` * **`x-`** for running a variety of `cargo watch` execution stages * **`nx-test-`**, which is just like `nx-test`, but adds `all` or `0` diff --git a/flake.nix b/flake.nix index f4fd8bb3..4bb6df8f 100644 --- a/flake.nix +++ b/flake.nix @@ -134,7 +134,7 @@ xFuncNoDefault = cmd: pkgs.writeScriptBin "x-${cmd}-0" '' #!${pkgs.stdenv.shell} - cargo watch -c -s "cargo ${cmd} --workspace --no-default-features" + cargo watch -c -s "cargo ${cmd} --no-default-features" ''; xFuncPackage = cmd: crate: @@ -145,19 +145,19 @@ xFuncTest = pkgs.writeScriptBin "x-test" '' #!${pkgs.stdenv.shell} - cargo watch -c -s "cargo nextest run --workspace --nocapture && cargo test --doc" + cargo watch -c -s "cargo nextest run --workspace --no-capture && cargo test --doc" ''; xFuncTestAll = pkgs.writeScriptBin "x-test-all" '' #!${pkgs.stdenv.shell} - cargo watch -c -s "cargo nextest run --workspace --all-features --nocapture \ + cargo watch -c -s "cargo nextest run --workspace --all-features --no-capture \ && cargo test --workspace --doc --all-features" ''; xFuncTestNoDefault = pkgs.writeScriptBin "x-test-0" '' #!${pkgs.stdenv.shell} - cargo watch -c -s "cargo nextest run --workspace --no-default-features --nocapture \ - && cargo test --workspace --doc --no-default-features" + cargo watch -c -s "cargo nextest run --no-default-features --no-capture \ + && cargo test --doc --no-default-features" ''; xFuncTestPackage = crate: @@ -175,14 +175,14 @@ nxTestAll = pkgs.writeScriptBin "nx-test-all" '' #!${pkgs.stdenv.shell} - cargo nextest run --workspace --all-features --nocapture + cargo nextest run --workspace --all-features --no-capture cargo test --workspace --doc --all-features ''; nxTestNoDefault = pkgs.writeScriptBin "nx-test-0" '' #!${pkgs.stdenv.shell} - cargo nextest run --workspace --no-default-features --nocapture - cargo test --workspace --doc --no-default-features + cargo nextest run --no-default-features --no-capture + cargo test --doc --no-default-features ''; wasmTest = pkgs.writeScriptBin "wasm-ex-test" '' diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 9d146f3f..c5453f9f 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -102,6 +102,7 @@ libp2p = { version = "0.52", default-features = false, features = [ "noise", "cbor", "yamux", + "serde", ] } libsqlite3-sys = { version = "0.26", default-features = false, features = [ "bundled", @@ -166,6 +167,7 @@ tower = { version = "0.4", default-features = false, features = [ ] } tower-http = { version = "0.4", default-features = false, features = [ "trace", + "sensitive-headers", "catch-panic", "cors", ] } diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index 269bc736..f1838afb 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -25,6 +25,7 @@ pub(crate) mod cache; pub mod channel; pub(crate) mod error; pub(crate) mod event; +#[cfg(feature = "websocket-notify")] pub(crate) mod notification; pub(crate) mod swarm_event; pub(crate) use cache::{setup_cache, CacheValue}; @@ -80,7 +81,7 @@ pub(crate) struct EventHandler { p2p_provider_timeout: Duration, db: DB, swarm: Swarm, - cache: Cache, + cache: Arc>, sender: Arc>, receiver: channel::AsyncBoundedChannelReceiver, query_senders: FnvHashMap)>, @@ -228,7 +229,7 @@ where #[cfg(not(feature = "ipfs"))] pub(crate) async fn start(mut self) -> Result<()> { let handle = Handle::current(); - handle.spawn(poll_cache(self.cache.clone())); + handle.spawn(poll_cache(self.cache.clone(), self.poll_cache_interval)); loop { select! { diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 7c222cf7..360d339d 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -7,7 +7,7 @@ use crate::event_handler::notification::emit_receipt; use crate::network::IpfsCli; use crate::{ db::Database, - event_handler::{Handler, P2PSender}, + event_handler::{Handler, P2PSender, ResponseEvent}, network::{ pubsub, swarm::{CapsuleTag, RequestResponseKey, TopicMessage}, @@ -127,8 +127,8 @@ impl Event { event_handler.shutdown().await; let _ = tx.send(()); } - Event::FindRecord(record) => record.find(event_handler), - Event::RemoveRecord(record) => record.remove(event_handler), + Event::FindRecord(record) => record.find(event_handler).await, + Event::RemoveRecord(record) => record.remove(event_handler).await, Event::OutboundRequest(PeerRequest { peer, request, @@ -144,7 +144,7 @@ impl Event { .request_response_senders .insert(request_id, (request, sender)); } - Event::GetProviders(record) => record.get_providers(event_handler), + Event::GetProviders(record) => record.get_providers(event_handler).await, Event::ProvideRecord(cid, sender, capsule_tag) => { let query_id = event_handler .swarm @@ -247,7 +247,7 @@ impl Captured { { emit_receipt( event_handler.ws_workflow_sender(), - receipt.clone(), + &receipt, self.metadata.to_owned(), ) } @@ -258,12 +258,15 @@ impl Captured { TopicMessage::CapturedReceipt(receipt), ) { Ok(msg_id) => info!( - "message {msg_id} published on {} topic for receipt with cid: {receipt_cid}", + cid = receipt_cid.to_string(), + "message {msg_id} published on {} topic for receipt", pubsub::RECEIPTS_TOPIC ), - Err(_err) => { - error!( - "message not published on {} topic for receipt with cid: {receipt_cid}", + Err(err) => { + warn!( + err=?err, + cid = receipt_cid.to_string(), + "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC ) } @@ -328,7 +331,7 @@ impl Replay { Self { pointers, metadata } } - fn notify(self, event_handler: &EventHandler) -> Result<()> + fn notify(self, event_handler: &mut EventHandler) -> Result<()> where DB: Database, { @@ -350,7 +353,7 @@ impl Replay { ); #[cfg(feature = "websocket-notify")] - receipts.into_iter().for_each(|receipt| { + receipts.iter().for_each(|receipt| { emit_receipt( event_handler.ws_workflow_sender(), receipt, @@ -358,6 +361,28 @@ impl Replay { ); }); + // gossiping replayed receipts + receipts.into_iter().for_each(|receipt| { + if event_handler.pubsub_enabled { + let receipt_cid = receipt.cid().to_string(); + let _ = event_handler + .swarm + .behaviour_mut() + .gossip_publish( + pubsub::RECEIPTS_TOPIC, + TopicMessage::CapturedReceipt(receipt), + ) + .map(|msg_id| + info!(cid=receipt_cid, + "message {msg_id} published on {} topic for receipt", pubsub::RECEIPTS_TOPIC)) + .map_err( + |err| + warn!(err=?err, cid=receipt_cid, + "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC), + ); + } + }); + Ok(()) } } @@ -372,10 +397,20 @@ impl QueryRecord { } } - fn find(self, event_handler: &mut EventHandler) + async fn find(self, event_handler: &mut EventHandler) where DB: Database, { + if event_handler.connections.peers.is_empty() { + info!("no connections to send request to"); + + if let Some(sender) = self.sender { + let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; + } + + return; + } + let id = event_handler .swarm .behaviour_mut() @@ -386,10 +421,20 @@ impl QueryRecord { event_handler.query_senders.insert(id, (key, self.sender)); } - fn remove(self, event_handler: &mut EventHandler) + async fn remove(self, event_handler: &mut EventHandler) where DB: Database, { + if event_handler.connections.peers.is_empty() { + info!("no connections to send request to"); + + if let Some(sender) = self.sender { + let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; + } + + return; + } + event_handler .swarm .behaviour_mut() @@ -403,10 +448,20 @@ impl QueryRecord { .stop_providing(&Key::new(&self.cid.to_bytes())); } - fn get_providers(self, event_handler: &mut EventHandler) + async fn get_providers(self, event_handler: &mut EventHandler) where DB: Database, { + if event_handler.connections.peers.is_empty() { + info!("no connections to send request to"); + + if let Some(sender) = self.sender { + let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; + } + + return; + } + let id = event_handler .swarm .behaviour_mut() diff --git a/homestar-runtime/src/event_handler/notification.rs b/homestar-runtime/src/event_handler/notification.rs index 8daa09a3..1d630d6d 100644 --- a/homestar-runtime/src/event_handler/notification.rs +++ b/homestar-runtime/src/event_handler/notification.rs @@ -31,10 +31,10 @@ const TIMESTAMP_KEY: &str = "timestamp"; /// Send receipt notification as bytes. pub(crate) fn emit_receipt( notifier: Notifier, - receipt: Receipt, + receipt: &Receipt, metadata: Option, ) { - let invocation_receipt = InvocationReceipt::from(&receipt); + let invocation_receipt = InvocationReceipt::from(receipt); let receipt_cid = receipt.cid(); let notification = ReceiptNotification::with(invocation_receipt, receipt_cid, metadata.clone()); diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index 2c22db6e..03379420 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -1,6 +1,8 @@ //! Internal libp2p [SwarmEvent] handling and [Handler] implementation. use super::EventHandler; +#[cfg(feature = "websocket-notify")] +use crate::event_handler::notification::{self, EventNotificationTyp, SwarmNotification}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ @@ -8,7 +10,6 @@ use crate::{ event_handler::{ cache::{self, CacheData, CacheValue}, event::QueryRecord, - notification::{self, EventNotificationTyp, SwarmNotification}, Event, Handler, RequestResponseError, }, libp2p::multiaddr::MultiaddrExt, @@ -40,6 +41,7 @@ use libp2p::{ swarm::{dial_opts::DialOpts, SwarmEvent}, PeerId, StreamProtocol, }; +#[cfg(feature = "websocket-notify")] use maplit::btreemap; use std::{ collections::{HashMap, HashSet}, @@ -56,6 +58,8 @@ const RENDEZVOUS_NAMESPACE: &str = "homestar"; pub(crate) enum ResponseEvent { /// Found [PeerRecord] on the DHT. Found(Result), + /// TODO + NoPeersAvailable, /// Found Providers/[PeerId]s on the DHT. Providers(Result>), } @@ -758,7 +762,7 @@ async fn handle_swarm_event( } => { debug!( peer_id = peer_id.to_string(), - "peer connection closed, cause: {cause:?}" + "peer connection closed, cause: {cause:#?}, endpoint: {endpoint:#?}" ); event_handler.connections.peers.remove_entry(&peer_id); diff --git a/homestar-runtime/src/network/ipfs.rs b/homestar-runtime/src/network/ipfs.rs index 1e018634..7e3aada1 100644 --- a/homestar-runtime/src/network/ipfs.rs +++ b/homestar-runtime/src/network/ipfs.rs @@ -2,14 +2,17 @@ //! //! [IpfsClient]: ipfs_api::IpfsClient +use crate::settings; use anyhow::Result; use futures::TryStreamExt; use homestar_core::workflow::Receipt; +use http::uri::Scheme; use ipfs_api::{ request::{DagCodec, DagPut}, response::DagPutResponse, IpfsApi, IpfsClient, }; +use ipfs_api_backend_hyper::TryFromUri; use libipld::{Cid, Ipld}; use std::{io::Cursor, sync::Arc}; use url::Url; @@ -20,15 +23,21 @@ const SHA3_256: &str = "sha3-256"; #[allow(missing_debug_implementations)] pub(crate) struct IpfsCli(Arc); -impl Clone for IpfsCli { - fn clone(&self) -> Self { - IpfsCli(Arc::clone(&self.0)) +impl IpfsCli { + /// Create a new [IpfsCli] from a [IpfsClient]. + pub(crate) fn new(settings: &settings::Ipfs) -> Result { + let cli = Self(Arc::new(IpfsClient::from_host_and_port( + Scheme::HTTP, + settings.host.as_str(), + settings.port, + )?)); + Ok(cli) } } -impl Default for IpfsCli { - fn default() -> Self { - Self(Arc::new(IpfsClient::default())) +impl Clone for IpfsCli { + fn clone(&self) -> Self { + IpfsCli(Arc::clone(&self.0)) } } diff --git a/homestar-runtime/src/network/pubsub.rs b/homestar-runtime/src/network/pubsub.rs index 710b2c41..ae570cae 100644 --- a/homestar-runtime/src/network/pubsub.rs +++ b/homestar-runtime/src/network/pubsub.rs @@ -34,9 +34,10 @@ pub(crate) fn new(keypair: Keypair, settings: &settings::Node) -> Result)), /// Acknowledgement of a [Workflow] run. AckWorkflow((Cid, FastStr)), + /// TODO + GetNodeInfo, + /// TODO + AckNodeInfo(NodeInfo), } /// WebSocket server fields. @@ -208,6 +224,7 @@ impl Server { rpc::METRICS_ENDPOINT, )?) .layer(cors) + .layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION))) .timeout(self.webserver_timeout); let runtime_hdl = Handle::current(); @@ -235,11 +252,13 @@ fn port_available(host: IpAddr, port: u16) -> bool { #[cfg(test)] mod test { use super::*; - use crate::{event_handler::notification::ReceiptNotification, settings::Settings}; - use homestar_core::test_utils; + #[cfg(feature = "websocket-notify")] + use crate::event_handler::notification::ReceiptNotification; + use crate::{db::Database, settings::Settings}; #[cfg(feature = "websocket-notify")] use homestar_core::{ ipld::DagJson, + test_utils, workflow::{config::Resources, instruction::RunInstruction, prf::UcanPrf, Task}, }; #[cfg(feature = "websocket-notify")] @@ -249,14 +268,8 @@ mod test { use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; #[cfg(feature = "websocket-notify")] use notifier::{self, Header}; - use serial_test::file_serial; use tokio::sync::mpsc; - fn set_ports(settings: &mut Settings) { - settings.node.network.metrics_port = test_utils::ports::get_port() as u16; - settings.node.network.webserver_port = test_utils::ports::get_port() as u16; - } - async fn metrics_handle(settings: Settings) -> PrometheusHandle { #[cfg(feature = "monitoring")] let metrics_hdl = crate::metrics::start(settings.monitoring(), settings.node.network()) @@ -271,237 +284,246 @@ mod test { metrics_hdl } - #[tokio::test] - #[file_serial] - async fn ws_connect() { - let mut settings = Settings::load().unwrap(); - set_ports(&mut settings); - let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); - server.start(runner_tx, metrics_hdl).await.unwrap(); - - let ws_url = format!("ws://{}", server.addr); - let http_url = format!("http://{}", server.addr); - - tokio_tungstenite::connect_async(ws_url.clone()) - .await - .unwrap(); - - let client = WsClientBuilder::default().build(ws_url).await.unwrap(); - let ws_resp: serde_json::Value = client - .request(rpc::HEALTH_ENDPOINT, rpc_params![]) - .await - .unwrap(); - - assert_eq!(ws_resp, serde_json::json!({"healthy": true})); - let http_resp = reqwest::get(format!("{}/health", http_url)).await.unwrap(); - assert_eq!(http_resp.status(), 200); - let http_resp = http_resp.json::().await.unwrap(); - assert_eq!(http_resp, serde_json::json!({"healthy": true})); + #[homestar_runtime_proc_macro::runner_test] + fn ws_connect() { + let TestRunner { runner, settings } = TestRunner::start(); + runner.runtime.block_on(async { + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics_handle(settings).await; + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + let http_url = format!("http://{}", server.addr); + + tokio_tungstenite::connect_async(ws_url.clone()) + .await + .unwrap(); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let ws_resp: serde_json::Value = client + .request(rpc::HEALTH_ENDPOINT, rpc_params![]) + .await + .unwrap(); + let peer_id = + libp2p::PeerId::from_str("12D3KooWRNw2pJC9748Fmq4WNV27HoSTcX3r37132FLkQMrbKAiC") + .unwrap(); + let nodeinfo = NodeInfo::new(peer_id); + assert_eq!( + ws_resp, + serde_json::json!({"healthy": true, "nodeInfo": nodeinfo}) + ); + let http_resp = reqwest::get(format!("{}/health", http_url)).await.unwrap(); + assert_eq!(http_resp.status(), 200); + let http_resp = http_resp.json::().await.unwrap(); + assert_eq!( + http_resp, + serde_json::json!({"healthy": true, "nodeInfo": nodeinfo}) + ); + }); unsafe { metrics::clear_recorder() } } #[cfg(feature = "monitoring")] - #[tokio::test] - #[file_serial] + #[homestar_runtime_proc_macro::runner_test] async fn ws_metrics_no_prefix() { - let mut settings = Settings::load().unwrap(); - set_ports(&mut settings); - settings.monitoring.process_collector_interval = Duration::from_millis(100); - let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); - server.start(runner_tx, metrics_hdl).await.unwrap(); - - let ws_url = format!("ws://{}", server.addr); - - // wait for interval to pass - std::thread::sleep(Duration::from_millis(100)); - - let client = WsClientBuilder::default().build(ws_url).await.unwrap(); - let ws_resp1: serde_json::Value = client - .request(rpc::METRICS_ENDPOINT, rpc_params![]) - .await - .unwrap(); - - let len = if let serde_json::Value::Array(array) = &ws_resp1["metrics"] { - array.len() - } else { - panic!("expected array"); - }; - - assert!(len > 0); - - unsafe { metrics::clear_recorder() } + let TestRunner { runner, settings } = TestRunner::start(); + runner.runtime.block_on(async { + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics_handle(settings).await; + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + // wait for interval to pass + std::thread::sleep(Duration::from_millis(150)); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let ws_resp1: serde_json::Value = client + .request(rpc::METRICS_ENDPOINT, rpc_params![]) + .await + .unwrap(); + + let len = if let serde_json::Value::Array(array) = &ws_resp1["metrics"] { + array.len() + } else { + panic!("expected array"); + }; + + assert!(len > 0); + + unsafe { metrics::clear_recorder() } + }); } #[cfg(feature = "websocket-notify")] - #[tokio::test] - #[file_serial] + #[homestar_runtime_proc_macro::runner_test] async fn ws_subscribe_unsubscribe_network_events() { - let mut settings = Settings::load().unwrap(); - set_ports(&mut settings); - let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); - server.start(runner_tx, metrics_hdl).await.unwrap(); - - let ws_url = format!("ws://{}", server.addr); - - let client1 = WsClientBuilder::default().build(ws_url).await.unwrap(); - let mut sub: Subscription> = client1 - .subscribe( - rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, - rpc_params![], - rpc::UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, - ) - .await - .unwrap(); - - // send any bytes through (Vec) - let (invocation_receipt, runtime_receipt) = crate::test_utils::receipt::receipts(); - let receipt = ReceiptNotification::with(invocation_receipt, runtime_receipt.cid(), None); - server - .evt_notifier - .notify(notifier::Message::new( - Header::new( - notifier::SubscriptionTyp::EventSub( - rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + let TestRunner { runner, settings } = TestRunner::start(); + runner.runtime.block_on(async { + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics_handle(settings).await; + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let client1 = WsClientBuilder::default().build(ws_url).await.unwrap(); + let mut sub: Subscription> = client1 + .subscribe( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + rpc_params![], + rpc::UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + ) + .await + .unwrap(); + + // send any bytes through (Vec) + let (invocation_receipt, runtime_receipt) = crate::test_utils::receipt::receipts(); + let receipt = + ReceiptNotification::with(invocation_receipt, runtime_receipt.cid(), None); + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + ), + None, ), - None, - ), - receipt.to_json().unwrap(), - )) - .unwrap(); - - // send an unknown msg: this should be dropped - server - .evt_notifier - .notify(notifier::Message::new( - Header::new( - notifier::SubscriptionTyp::EventSub("test".to_string()), - None, - ), - vec![], - )) - .unwrap(); - - server - .evt_notifier - .notify(notifier::Message::new( - Header::new( - notifier::SubscriptionTyp::EventSub( - rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + receipt.to_json().unwrap(), + )) + .unwrap(); + + // send an unknown msg: this should be dropped + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub("test".to_string()), + None, ), - None, - ), - receipt.to_json().unwrap(), - )) - .unwrap(); + vec![], + )) + .unwrap(); + + server + .evt_notifier + .notify(notifier::Message::new( + Header::new( + notifier::SubscriptionTyp::EventSub( + rpc::SUBSCRIBE_NETWORK_EVENTS_ENDPOINT.to_string(), + ), + None, + ), + receipt.to_json().unwrap(), + )) + .unwrap(); - let msg1 = sub.next().await.unwrap().unwrap(); - let returned1: ReceiptNotification = DagJson::from_json(&msg1).unwrap(); - assert_eq!(returned1, receipt); + let msg1 = sub.next().await.unwrap().unwrap(); + let returned1: ReceiptNotification = DagJson::from_json(&msg1).unwrap(); + assert_eq!(returned1, receipt); - let msg2 = sub.next().await.unwrap().unwrap(); - let _returned1: ReceiptNotification = DagJson::from_json(&msg2).unwrap(); + let msg2 = sub.next().await.unwrap().unwrap(); + let _returned1: ReceiptNotification = DagJson::from_json(&msg2).unwrap(); - assert!(sub.unsubscribe().await.is_ok()); + assert!(sub.unsubscribe().await.is_ok()); - unsafe { metrics::clear_recorder() } + unsafe { metrics::clear_recorder() } + }); } #[cfg(feature = "websocket-notify")] - #[tokio::test] - #[file_serial] + #[homestar_runtime_proc_macro::runner_test] async fn ws_subscribe_workflow_incorrect_params() { - let mut settings = Settings::load().unwrap(); - set_ports(&mut settings); - let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); - server.start(runner_tx, metrics_hdl).await.unwrap(); - - let ws_url = format!("ws://{}", server.addr); - - let client = WsClientBuilder::default().build(ws_url).await.unwrap(); - let sub: Result>, jsonrpsee::core::error::Error> = client - .subscribe( - rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, - rpc_params![], - rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, - ) - .await; - - assert!(sub.is_err()); - - if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { - let check = ErrorCode::InvalidParams; - assert_eq!(err.code(), check.code()); - } else { - panic!("expected same error code"); - } - - unsafe { metrics::clear_recorder() } + let TestRunner { runner, settings } = TestRunner::start(); + runner.runtime.block_on(async { + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics_handle(settings).await; + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let sub: Result>, jsonrpsee::core::error::Error> = client + .subscribe( + rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![], + rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await; + + assert!(sub.is_err()); + + if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { + let check = ErrorCode::InvalidParams; + assert_eq!(err.code(), check.code()); + } else { + panic!("expected same error code"); + } + + unsafe { metrics::clear_recorder() } + }); } #[cfg(feature = "websocket-notify")] - #[tokio::test] - #[file_serial] + #[homestar_runtime_proc_macro::runner_test] async fn ws_subscribe_workflow_runner_timeout() { - let mut settings = Settings::load().unwrap(); - set_ports(&mut settings); - let server = Server::new(settings.node().network()).unwrap(); - let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); - server.start(runner_tx, metrics_hdl).await.unwrap(); - - let ws_url = format!("ws://{}", server.addr); - - let config = Resources::default(); - let instruction1 = test_utils::workflow::instruction::(); - let (instruction2, _) = test_utils::workflow::wasm_instruction_with_nonce::(); - - let task1 = Task::new( - RunInstruction::Expanded(instruction1), - config.clone().into(), - UcanPrf::default(), - ); - let task2 = Task::new( - RunInstruction::Expanded(instruction2), - config.into(), - UcanPrf::default(), - ); - - let workflow = Workflow::new(vec![task1.clone(), task2.clone()]); - let run_str = format!( - r#"{{"name": "test","workflow": {}}}"#, - workflow.to_json_string().unwrap() - ); - - let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); - let client = WsClientBuilder::default().build(ws_url).await.unwrap(); - let sub: Result>, jsonrpsee::core::error::Error> = client - .subscribe( - rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, - rpc_params![run], - rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, - ) - .await; - - assert!(sub.is_err()); - - // Assure error is not on parse of params, but due to runner - // timeout (as runner is not available). - if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { - let check = ErrorCode::InternalError; - assert_eq!(err.code(), check.code()); - } else { - panic!("expected same error code"); - } - - unsafe { metrics::clear_recorder() } + let TestRunner { runner, settings } = TestRunner::start(); + runner.runtime.block_on(async { + let server = Server::new(settings.node().network()).unwrap(); + let metrics_hdl = metrics_handle(settings).await; + let (runner_tx, _runner_rx) = mpsc::channel(1); + server.start(runner_tx, metrics_hdl).await.unwrap(); + + let ws_url = format!("ws://{}", server.addr); + + let config = Resources::default(); + let instruction1 = test_utils::workflow::instruction::(); + let (instruction2, _) = test_utils::workflow::wasm_instruction_with_nonce::(); + + let task1 = Task::new( + RunInstruction::Expanded(instruction1), + config.clone().into(), + UcanPrf::default(), + ); + let task2 = Task::new( + RunInstruction::Expanded(instruction2), + config.into(), + UcanPrf::default(), + ); + + let workflow = Workflow::new(vec![task1.clone(), task2.clone()]); + let run_str = format!( + r#"{{"name": "test","workflow": {}}}"#, + workflow.to_json_string().unwrap() + ); + + let run: serde_json::Value = serde_json::from_str(&run_str).unwrap(); + let client = WsClientBuilder::default().build(ws_url).await.unwrap(); + let sub: Result>, jsonrpsee::core::error::Error> = client + .subscribe( + rpc::SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + rpc_params![run], + rpc::UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + ) + .await; + + assert!(sub.is_err()); + + // Assure error is not on parse of params, but due to runner + // timeout (as runner is not available). + if let Err(jsonrpsee::core::error::Error::Call(err)) = sub { + let check = ErrorCode::ServerIsBusy; + assert_eq!(err.code(), check.code()); + } else { + panic!("expected same error code"); + } + + unsafe { metrics::clear_recorder() } + }); } } diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index 67461f7a..509e1243 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -1,9 +1,7 @@ -use super::{listener, prom::PrometheusData}; #[cfg(feature = "websocket-notify")] -use super::{ - notifier::{self, Header, Notifier, SubscriptionTyp}, - Message, -}; +use super::notifier::{self, Header, Notifier, SubscriptionTyp}; +#[allow(unused_imports)] +use super::{listener, prom::PrometheusData, Message}; use crate::runner::WsSender; #[cfg(feature = "websocket-notify")] use anyhow::anyhow; @@ -16,7 +14,7 @@ use faststr::FastStr; use futures::StreamExt; use jsonrpsee::{ server::RpcModule, - types::{error::ErrorCode, ErrorObjectOwned}, + types::error::{ErrorCode, ErrorObject}, }; #[cfg(feature = "websocket-notify")] use jsonrpsee::{types::SubscriptionId, SubscriptionMessage, SubscriptionSink, TrySendError}; @@ -27,16 +25,18 @@ use metrics_exporter_prometheus::PrometheusHandle; use std::sync::Arc; use std::time::Duration; #[cfg(feature = "websocket-notify")] +use tokio::{runtime::Handle, select}; +#[allow(unused_imports)] use tokio::{ - runtime::Handle, - select, sync::oneshot, time::{self, Instant}, }; #[cfg(feature = "websocket-notify")] use tokio_stream::wrappers::BroadcastStream; +#[allow(unused_imports)] +use tracing::warn; #[cfg(feature = "websocket-notify")] -use tracing::{error, info, warn}; +use tracing::{debug, error, info}; /// Health endpoint. pub(crate) const HEALTH_ENDPOINT: &str = "health"; @@ -135,8 +135,34 @@ impl JsonRpc { async fn register(ctx: Context) -> Result> { let mut module = RpcModule::new(ctx); + #[cfg(not(test))] + module.register_async_method(HEALTH_ENDPOINT, |_, ctx| async move { + let (tx, rx) = oneshot::channel(); + ctx.runner_sender + .send((Message::GetNodeInfo, Some(tx))) + .await + .map_err(|err| internal_err(err.to_string()))?; + + if let Ok(Ok(Message::AckNodeInfo(info))) = + time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await + { + Ok(serde_json::json!({ "healthy": true, "nodeInfo": info})) + } else { + warn!(sub = HEALTH_ENDPOINT, "did not acknowledge message in time"); + Err(internal_err("failed to get node information".to_string())) + } + })?; + + #[cfg(test)] module.register_async_method(HEALTH_ENDPOINT, |_, _| async move { - serde_json::json!({ "healthy": true }) + use crate::runner::NodeInfo; + use std::str::FromStr; + let peer_id = + libp2p::PeerId::from_str("12D3KooWRNw2pJC9748Fmq4WNV27HoSTcX3r37132FLkQMrbKAiC") + .unwrap(); + Ok::>(serde_json::json!({ + "healthy": true, "nodeInfo": NodeInfo::new(peer_id) + })) })?; module.register_async_method(METRICS_ENDPOINT, |params, ctx| async move { @@ -144,12 +170,15 @@ impl JsonRpc { // TODO: Handle prefix specific metrics in parser. match params.one::() { - Ok(listener::MetricsPrefix { prefix: _prefix }) => { - PrometheusData::from_string(&render) - .map_err(|_err| ErrorObjectOwned::from(ErrorCode::InternalError)) - } + Ok(listener::MetricsPrefix { prefix }) => PrometheusData::from_string(&render) + .map_err(|err| { + internal_err(format!( + "failed to render metrics @prefix {} : {:#?}", + prefix, err + )) + }), Err(_) => PrometheusData::from_string(&render) - .map_err(|_err| ErrorObjectOwned::from(ErrorCode::InternalError)), + .map_err(|err| internal_err(format!("failed to render metrics: {:#?}", err))), } })?; @@ -182,7 +211,7 @@ impl JsonRpc { Ok(listener::Run { name, workflow }) => { let (tx, rx) = oneshot::channel(); ctx.runner_sender - .send((Message::RunWorkflow((name, workflow)), Some(tx))) + .send((Message::RunWorkflow((name.clone(), workflow)), Some(tx))) .await?; if let Ok(Ok(Message::AckWorkflow((cid, name)))) = @@ -195,11 +224,13 @@ impl JsonRpc { let stream = BroadcastStream::new(rx); Self::handle_workflow_subscription(sink, stream, ctx).await?; } else { - warn!("did not acknowledge message in time"); + warn!( + sub = SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, + workflow_name = name.to_string(), + "did not acknowledge message in time" + ); let _ = pending - .reject(ErrorObjectOwned::from(ErrorObjectOwned::from( - ErrorCode::InternalError, - ))) + .reject(busy_err("workflow not able to run workflow: {cid}")) .await; } } @@ -287,6 +318,9 @@ impl JsonRpc { .and_then(|v| { let (v_cid, v_name) = v.value(); if v_cid == &cid && (Some(v_name) == ident.as_ref() || ident.is_none()) { + debug!(cid = cid.to_string(), + ident = ident.clone().unwrap_or( + "undefined".into()).to_string(), "received message"); Some(payload) } else { None @@ -329,3 +363,12 @@ impl JsonRpc { Ok(()) } } + +fn internal_err<'a, T: ToString>(msg: T) -> ErrorObject<'a> { + ErrorObject::owned(ErrorCode::InternalError.code(), msg.to_string(), None::<()>) +} + +#[allow(dead_code)] +fn busy_err<'a, T: ToString>(msg: T) -> ErrorObject<'a> { + ErrorObject::owned(ErrorCode::ServerIsBusy.code(), msg.to_string(), None::<()>) +} diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 3511f26f..06f59ff1 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -8,15 +8,18 @@ use crate::{ db::Database, event_handler::{Event, EventHandler}, network::{rpc, swarm, webserver}, + tasks::Fetch, worker::WorkerMessage, - workflow, Settings, Worker, + workflow::{self, Resource}, + Settings, Worker, }; use anyhow::{anyhow, Context, Result}; use atomic_refcell::AtomicRefCell; use chrono::NaiveDateTime; use dashmap::DashMap; use faststr::FastStr; -use futures::future::poll_fn; +use fnv::FnvHashSet; +use futures::{future::poll_fn, FutureExt}; use homestar_core::Workflow; use homestar_wasm::io::Arg; use jsonrpsee::server::ServerHandle; @@ -40,8 +43,10 @@ use tracing::{error, info, warn}; mod error; pub(crate) mod file; +mod nodeinfo; pub(crate) mod response; pub(crate) use error::Error; +pub(crate) use nodeinfo::NodeInfo; #[cfg(not(test))] const HOMESTAR_THREAD: &str = "homestar-runtime"; @@ -100,10 +105,11 @@ impl ModifiedSet for RunningTaskSet { pub struct Runner { event_sender: Arc>, expiration_queue: Rc>>, + node_info: NodeInfo, running_tasks: Arc, running_workers: RunningWorkerSet, - runtime: tokio::runtime::Runtime, - settings: Arc, + pub(crate) runtime: tokio::runtime::Runtime, + pub(crate) settings: Arc, webserver: Arc, } @@ -158,6 +164,7 @@ impl Runner { runtime: tokio::runtime::Runtime, ) -> Result { let swarm = runtime.block_on(swarm::new(settings.node()))?; + let peer_id = *swarm.local_peer_id(); let webserver = webserver::Server::new(settings.node().network())?; @@ -178,7 +185,7 @@ impl Runner { #[cfg(feature = "ipfs")] let _event_handler_hdl = runtime.spawn({ - let ipfs = IpfsCli::default(); + let ipfs = IpfsCli::new(settings.node.network.ipfs())?; event_handler.start(ipfs) }); @@ -188,6 +195,7 @@ impl Runner { Ok(Self { event_sender, expiration_queue: Rc::new(AtomicRefCell::new(DelayQueue::new())), + node_info: NodeInfo::new(peer_id), running_tasks: DashMap::new().into(), running_workers: DashMap::new(), runtime, @@ -263,27 +271,39 @@ impl Runner { _ => {} } } - Some((webserver::Message::RunWorkflow((name, workflow)), Some(oneshot_tx))) = ws_receiver.recv() => { - info!("running workflow: {}", name); - // TODO: Parse this from the workflow data itself. - let workflow_settings = workflow::Settings::default(); - match self.run_worker( - workflow, - workflow_settings, - Some(name), - runner_worker_tx.clone(), - db.clone(), - ).await { - Ok(data) => { - info!("sending message to rpc server"); - let _ = oneshot_tx.send(webserver::Message::AckWorkflow((data.info.cid, data.name))); + Some(msg) = ws_receiver.recv() => { + println!("ws message: {:?}", msg); + match msg { + (webserver::Message::RunWorkflow((name, workflow)), Some(oneshot_tx)) => { + info!("running workflow: {}", name); + // TODO: Parse this from the workflow data itself. + let workflow_settings = workflow::Settings::default(); + match self.run_worker( + workflow, + workflow_settings, + Some(name), + runner_worker_tx.clone(), + db.clone(), + ).await { + Ok(data) => { + info!("sending message to rpc server"); + let _ = oneshot_tx.send(webserver::Message::AckWorkflow((data.info.cid, data.name))); + } + Err(err) => { + error!(err=?err, "error handling ws message"); + let _ = oneshot_tx.send(webserver::Message::RunErr(err.into())); + } + } + } - Err(err) => { - error!(err=?err, "error handling ws message"); - let _ = oneshot_tx.send(webserver::Message::RunErr(err.into())); + (webserver::Message::GetNodeInfo, Some(oneshot_tx)) => { + info!("getting node info"); + let _ = oneshot_tx.send(webserver::Message::AckNodeInfo(self.node_info.clone())); } + _ => () } } + // Handle messages from the worker. Some(msg) = runner_worker_rx.recv() => { match msg { @@ -584,6 +604,7 @@ impl Runner { let initial_info = Arc::clone(&worker.workflow_info); let workflow_timeout = worker.workflow_settings.timeout; let workflow_name = worker.workflow_name.clone(); + let workflow_settings = worker.workflow_settings.clone(); let timestamp = worker.workflow_started; // Spawn worker, which initializees the scheduler and runs @@ -602,7 +623,23 @@ impl Runner { )) .await?; - let handle = self.runtime.spawn(worker.run(self.running_tasks())); + #[cfg(feature = "ipfs")] + let fetch_fn = { + let settings = Arc::clone(&self.settings); + let ipfs = IpfsCli::new(settings.node.network.ipfs())?; + move |rscs: FnvHashSet| { + async move { Fetch::get_resources(rscs, workflow_settings, ipfs).await }.boxed() + } + }; + + #[cfg(not(feature = "ipfs"))] + let fetch_fn = |rscs: FnvHashSet| { + async move { Fetch::get_resources(rscs, workflow_settings).await }.boxed() + }; + + let handle = self + .runtime + .spawn(worker.run(self.running_tasks(), fetch_fn)); // Add Cid to expirations timing wheel let delay_key = self @@ -641,12 +678,10 @@ mod test { use crate::{network::rpc::Client, test_utils::WorkerBuilder}; use homestar_core::test_utils as core_test_utils; use rand::thread_rng; - use serial_test::file_serial; use std::net::SocketAddr; use tarpc::context; use tokio::net::TcpStream; - #[file_serial] #[homestar_runtime_proc_macro::runner_test] fn shutdown() { let TestRunner { runner, settings } = TestRunner::start(); @@ -730,10 +765,14 @@ mod test { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let worker = WorkerBuilder::new(settings.node).build().await; + let builder = WorkerBuilder::new(settings.node); + let fetch_fn = builder.fetch_fn(); + let worker = builder.build().await; let workflow_cid = worker.workflow_info.cid; let workflow_timeout = worker.workflow_settings.timeout; - let handle = runner.runtime.spawn(worker.run(runner.running_tasks())); + let handle = runner + .runtime + .spawn(worker.run(runner.running_tasks(), fetch_fn)); let delay_key = runner .expiration_queue .try_borrow_mut() @@ -763,10 +802,14 @@ mod test { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let worker = WorkerBuilder::new(settings.node).build().await; + let builder = WorkerBuilder::new(settings.node); + let fetch_fn = builder.fetch_fn(); + let worker = builder.build().await; let workflow_cid = worker.workflow_info.cid; let workflow_timeout = worker.workflow_settings.timeout; - let handle = runner.runtime.spawn(worker.run(runner.running_tasks())); + let handle = runner + .runtime + .spawn(worker.run(runner.running_tasks(), fetch_fn)); let delay_key = runner .expiration_queue .try_borrow_mut() @@ -787,10 +830,14 @@ mod test { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let worker = WorkerBuilder::new(settings.node).build().await; + let builder = WorkerBuilder::new(settings.node); + let fetch_fn = builder.fetch_fn(); + let worker = builder.build().await; let workflow_cid = worker.workflow_info.cid; let workflow_timeout = worker.workflow_settings.timeout; - let handle = runner.runtime.spawn(worker.run(runner.running_tasks())); + let handle = runner + .runtime + .spawn(worker.run(runner.running_tasks(), fetch_fn)); let delay_key = runner .expiration_queue .try_borrow_mut() @@ -826,8 +873,10 @@ mod test { fn gc_while_workers_finished() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let worker = WorkerBuilder::new(settings.node).build().await; - let _ = worker.run(runner.running_tasks()).await; + let builder = WorkerBuilder::new(settings.node); + let fetch_fn = builder.fetch_fn(); + let worker = builder.build().await; + let _ = worker.run(runner.running_tasks(), fetch_fn).await; }); runner.running_tasks.iter().for_each(|handles| { diff --git a/homestar-runtime/src/runner/nodeinfo.rs b/homestar-runtime/src/runner/nodeinfo.rs new file mode 100644 index 00000000..fd916151 --- /dev/null +++ b/homestar-runtime/src/runner/nodeinfo.rs @@ -0,0 +1,21 @@ +use libp2p::PeerId; +use serde::{Deserialize, Serialize}; + +/// TODO +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct NodeInfo { + pub(crate) peer_id: PeerId, +} + +impl NodeInfo { + /// TODO + pub(crate) fn new(peer_id: PeerId) -> Self { + Self { peer_id } + } + + /// TODO + #[allow(dead_code)] + pub(crate) fn peer_id(&self) -> &PeerId { + &self.peer_id + } +} diff --git a/homestar-runtime/src/scheduler.rs b/homestar-runtime/src/scheduler.rs index 4ca8ca07..df3f48bf 100644 --- a/homestar-runtime/src/scheduler.rs +++ b/homestar-runtime/src/scheduler.rs @@ -106,7 +106,7 @@ impl<'a> TaskScheduler<'a> { let mut_graph = Arc::make_mut(&mut graph); let schedule: &mut Schedule<'a> = mut_graph.schedule.as_mut(); let schedule_length = schedule.len(); - let mut resources_to_fetch: FnvHashSet = FnvHashSet::default(); + let mut resources_to_fetch = vec![]; let resume = 'resume: { for (idx, vec) in schedule.iter().enumerate().rev() { @@ -117,7 +117,7 @@ impl<'a> TaskScheduler<'a> { .get(&cid) .map(|resource| { resource.iter().for_each(|rsc| { - resources_to_fetch.insert(rsc.to_owned()); + resources_to_fetch.push((cid, rsc)); }); ptrs.push(Pointer::new(cid)); }) @@ -131,6 +131,13 @@ impl<'a> TaskScheduler<'a> { let linkmap = found.iter().fold( LinkMap::>::new(), |mut map, receipt| { + if let Some(idx) = resources_to_fetch + .iter() + .position(|(cid, _rsc)| cid == &receipt.instruction().cid()) + { + resources_to_fetch.swap_remove(idx); + } + let _ = map.insert( receipt.instruction().cid(), receipt.output_as_arg(), @@ -160,6 +167,10 @@ impl<'a> TaskScheduler<'a> { ControlFlow::Continue(()) }; + let resources_to_fetch: FnvHashSet = resources_to_fetch + .into_iter() + .map(|(_, rsc)| rsc.to_owned()) + .collect(); let fetched = fetch_fn(resources_to_fetch).await?; match resume { diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 3b2ecdea..ed301134 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -4,6 +4,8 @@ use config::{Config, ConfigError, Environment, File}; use http::Uri; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr, DurationMilliSeconds, DurationSeconds}; +#[cfg(feature = "ipfs")] +use std::net::Ipv4Addr; use std::{ net::{IpAddr, Ipv6Addr}, path::PathBuf, @@ -34,26 +36,17 @@ impl Settings { } /// Monitoring settings. -#[cfg(feature = "monitoring")] #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Monitoring { /// Tokio console port. pub console_subscriber_port: u16, /// Monitoring collection interval in milliseconds. + #[cfg(feature = "monitoring")] #[serde_as(as = "DurationMilliSeconds")] pub process_collector_interval: Duration, } -/// Monitoring settings. -#[cfg(not(feature = "monitoring"))] -#[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct Monitoring { - /// Tokio console port. - pub console_subscriber_port: u16, -} - /// Server settings. #[serde_as] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] @@ -123,6 +116,14 @@ pub struct Network { /// Pub/sub idle timeout #[serde_as(as = "DurationSeconds")] pub(crate) pubsub_idle_timeout: Duration, + /// TODO + pub(crate) pubsub_mesh_n_low: usize, + /// TODO + pub(crate) pubsub_mesh_n_high: usize, + /// TODO + pub(crate) pubsub_mesh_n: usize, + /// TODO + pub(crate) pubsub_mesh_outbound_min: usize, /// Quorum for receipt records on the DHT. pub(crate) receipt_quorum: usize, /// RPC-server port. @@ -172,6 +173,20 @@ pub struct Network { /// Event handler poll cache interval in milliseconds. #[serde_as(as = "DurationMilliSeconds")] pub(crate) poll_cache_interval: Duration, + /// TODO + #[cfg(feature = "ipfs")] + pub(crate) ipfs: Ipfs, +} + +#[cfg(feature = "ipfs")] +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Ipfs { + /// TODO + pub(crate) host: String, + /// TODO + pub(crate) port: u16, } /// Database-related settings for a homestar node. @@ -210,6 +225,16 @@ impl Default for Monitoring { } } +#[cfg(feature = "ipfs")] +impl Default for Ipfs { + fn default() -> Self { + Self { + host: Ipv4Addr::LOCALHOST.to_string(), + port: 5001, + } + } +} + impl Default for Database { fn default() -> Self { Self { @@ -239,6 +264,10 @@ impl Default for Network { pubsub_duplication_cache_time: Duration::new(1, 0), pubsub_heartbeat: Duration::new(60, 0), pubsub_idle_timeout: Duration::new(60 * 60 * 24, 0), + pubsub_mesh_n_low: 1, + pubsub_mesh_n_high: 10, + pubsub_mesh_n: 2, + pubsub_mesh_outbound_min: 1, receipt_quorum: 2, rpc_host: IpAddr::V6(Ipv6Addr::LOCALHOST), rpc_max_connections: 10, @@ -247,7 +276,7 @@ impl Default for Network { transport_connection_timeout: Duration::new(20, 0), webserver_host: Uri::from_static("127.0.0.1"), webserver_port: 1337, - webserver_timeout: Duration::new(60, 0), + webserver_timeout: Duration::new(120, 0), websocket_capacity: 1024, websocket_receiver_timeout: Duration::from_millis(200), workflow_quorum: 3, @@ -257,6 +286,8 @@ impl Default for Network { max_connected_peers: 32, max_announce_addresses: 10, poll_cache_interval: Duration::from_millis(1000), + #[cfg(feature = "ipfs")] + ipfs: Default::default(), } } } @@ -272,6 +303,14 @@ impl Node { } } +impl Network { + /// TODO + #[cfg(feature = "ipfs")] + pub(crate) fn ipfs(&self) -> &Ipfs { + &self.ipfs + } +} + fn default_shutdown_timeout() -> Duration { Duration::new(20, 0) } diff --git a/homestar-runtime/src/test_utils/worker_builder.rs b/homestar-runtime/src/test_utils/worker_builder.rs index 4546fdb6..587800cc 100644 --- a/homestar-runtime/src/test_utils/worker_builder.rs +++ b/homestar-runtime/src/test_utils/worker_builder.rs @@ -1,10 +1,20 @@ //! Module for building out [Worker]s for testing purposes. use super::{db::MemoryDb, event}; +#[cfg(feature = "ipfs")] +use crate::network::IpfsCli; use crate::{ - channel::AsyncBoundedChannelSender, db::Database, event_handler::Event, settings, - worker::WorkerMessage, workflow, Settings, Worker, + channel::AsyncBoundedChannelSender, + db::Database, + event_handler::Event, + settings, + tasks::Fetch, + worker::WorkerMessage, + workflow::{self, Resource}, + Settings, Worker, }; +use fnv::FnvHashSet; +use futures::{future::BoxFuture, FutureExt}; use homestar_core::{ ipld::DagCbor, test_utils::workflow as workflow_test_utils, @@ -12,9 +22,24 @@ use homestar_core::{ Workflow, }; use homestar_wasm::io::Arg; +use indexmap::IndexMap; use libipld::Cid; use tokio::sync::mpsc; +/// TODO +#[cfg(feature = "ipfs")] +pub(crate) struct WorkerBuilder<'a> { + db: MemoryDb, + event_sender: AsyncBoundedChannelSender, + ipfs: IpfsCli, + runner_sender: mpsc::Sender, + name: Option, + workflow: Workflow<'a, Arg>, + workflow_settings: workflow::Settings, +} + +/// TODO +#[cfg(not(feature = "ipfs"))] pub(crate) struct WorkerBuilder<'a> { db: MemoryDb, event_sender: AsyncBoundedChannelSender, @@ -31,12 +56,12 @@ impl<'a> WorkerBuilder<'a> { let (instruction1, instruction2, _) = workflow_test_utils::related_wasm_instructions::(); let task1 = Task::new( - RunInstruction::Expanded(instruction1), + RunInstruction::Expanded(instruction1.clone()), config.clone().into(), UcanPrf::default(), ); let task2 = Task::new( - RunInstruction::Expanded(instruction2), + RunInstruction::Expanded(instruction2.clone()), config.into(), UcanPrf::default(), ); @@ -46,13 +71,31 @@ impl<'a> WorkerBuilder<'a> { let workflow = Workflow::new(vec![task1, task2]); let workflow_cid = workflow.clone().to_cid().unwrap(); - Self { - db: MemoryDb::setup_connection_pool(&settings, None).unwrap(), - event_sender: evt_tx, - runner_sender: wk_tx, - name: Some(workflow_cid.to_string()), - workflow, - workflow_settings: workflow::Settings::default(), + + #[cfg(feature = "ipfs")] + { + let ipfs = IpfsCli::new(settings.network.ipfs()).unwrap(); + Self { + db: MemoryDb::setup_connection_pool(&settings, None).unwrap(), + event_sender: evt_tx, + ipfs: ipfs.clone(), + runner_sender: wk_tx, + name: Some(workflow_cid.to_string()), + workflow, + workflow_settings: workflow::Settings::default(), + } + } + + #[cfg(not(feature = "ipfs"))] + { + Self { + db: MemoryDb::setup_connection_pool(&settings, None).unwrap(), + event_sender: evt_tx, + runner_sender: wk_tx, + name: Some(workflow_cid.to_string()), + workflow, + workflow_settings: workflow::Settings::default(), + } } } @@ -71,6 +114,37 @@ impl<'a> WorkerBuilder<'a> { .unwrap() } + /// TODO + #[cfg(feature = "ipfs")] + #[allow(dead_code)] + pub(crate) fn fetch_fn( + &self, + ) -> impl FnOnce(FnvHashSet) -> BoxFuture<'a, anyhow::Result>>> + { + let fetch_settings = self.workflow_settings.clone().into(); + let ipfs = self.ipfs.clone(); + let fetch_fn = move |rscs: FnvHashSet| { + async move { Fetch::get_resources(rscs, fetch_settings, ipfs).await }.boxed() + }; + + fetch_fn + } + + /// TODO + #[cfg(not(feature = "ipfs"))] + #[allow(dead_code)] + pub(crate) fn fetch_fn( + &self, + ) -> impl FnOnce(FnvHashSet) -> BoxFuture<'a, anyhow::Result>>> + { + let fetch_settings = self.workflow_settings.clone().into(); + let fetch_fn = |rscs: FnvHashSet| { + async move { Fetch::get_resources(rscs, fetch_settings).await }.boxed() + }; + + fetch_fn + } + /// Get the [Cid] of the workflow from the builder state. #[allow(dead_code)] pub(crate) fn workflow_cid(&self) -> Cid { diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index f7e1b64a..e14042c7 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -6,8 +6,6 @@ #[cfg(feature = "websocket-notify")] use crate::event_handler::event::Replay; -#[cfg(feature = "ipfs")] -use crate::network::IpfsCli; use crate::{ channel::{AsyncBoundedChannel, AsyncBoundedChannelSender}, db::Database, @@ -19,7 +17,7 @@ use crate::{ network::swarm::CapsuleTag, runner::{ModifiedSet, RunningTaskSet}, scheduler::{ExecutionGraph, TaskScheduler}, - tasks::{Fetch, RegisteredTasks, WasmContext}, + tasks::{RegisteredTasks, WasmContext}, workflow::{self, Resource}, Db, Receipt, }; @@ -27,7 +25,7 @@ use anyhow::{anyhow, Result}; use chrono::NaiveDateTime; use faststr::FastStr; use fnv::FnvHashSet; -use futures::FutureExt; +use futures::{future::BoxFuture, FutureExt}; use homestar_core::{ bail, ipld::DagCbor, @@ -145,23 +143,10 @@ where /// /// [Instruction]: homestar_core::workflow::Instruction /// [Swarm]: crate::network::swarm - pub(crate) async fn run(self, running_tasks: Arc) -> Result<()> { - let workflow_settings_fetch = self.workflow_settings.clone(); - #[cfg(feature = "ipfs")] - let fetch_fn = { - let ipfs = IpfsCli::default(); - - move |rscs: FnvHashSet| { - async move { Fetch::get_resources(rscs, workflow_settings_fetch, ipfs).await } - .boxed() - } - }; - - #[cfg(not(feature = "ipfs"))] - let fetch_fn = |rscs: FnvHashSet| { - async move { Fetch::get_resources(rscs, workflow_settings_fetch).await }.boxed() - }; - + pub(crate) async fn run(self, running_tasks: Arc, fetch_fn: F) -> Result<()> + where + F: FnOnce(FnvHashSet) -> BoxFuture<'a, Result>>>, + { let scheduler_ctx = TaskScheduler::init( self.graph.clone(), // Arc'ed &mut self.db.conn()?, @@ -204,7 +189,7 @@ where ); if let Some(result) = linkmap.read().await.get(&cid) { - info!(cid = cid.to_string(), "found in in-memory linkmap"); + debug!(cid = cid.to_string(), "found in in-memory linkmap"); Ok(result.to_owned()) } else if let Some(bytes) = resources.read().await.get(&Resource::Cid(cid)) { Ok(InstructionResult::Ok(Arg::Ipld(Ipld::Bytes( @@ -237,6 +222,11 @@ where "failure in attempting to find event: {err}" ))) } + Ok(Ok(ResponseEvent::NoPeersAvailable)) => { + bail!(ResolveError::UnresolvedCid( + "no peers available to communicate with".to_string() + )) + } Ok(Ok(_)) => bail!(ResolveError::UnresolvedCid( "wrong or unexpected event message received".to_string(), )), @@ -465,6 +455,7 @@ mod test { let (tx, rx) = test_utils::event::setup_event_channel(settings.clone().node); let builder = WorkerBuilder::new(settings.node).with_event_sender(tx); + let fetch_fn = builder.fetch_fn(); let db = builder.db(); let worker = builder.build().await; let workflow_cid = worker.workflow_info.cid; @@ -484,7 +475,7 @@ mod test { let running_tasks = Arc::new(RunningTaskSet::new()); let worker_workflow_cid = worker.workflow_info.cid; - worker.run(running_tasks.clone()).await.unwrap(); + worker.run(running_tasks.clone(), fetch_fn).await.unwrap(); assert_eq!(running_tasks.len(), 1); assert!(running_tasks.contains_key(&worker_workflow_cid)); assert_eq!(running_tasks.get(&worker_workflow_cid).unwrap().len(), 2); @@ -582,6 +573,7 @@ mod test { let builder = WorkerBuilder::new(settings.node) .with_event_sender(tx) .with_tasks(vec![task1, task2]); + let fetch_fn = builder.fetch_fn(); let db = builder.db(); let workflow_cid = builder.workflow_cid(); @@ -626,7 +618,7 @@ mod test { let running_tasks = Arc::new(RunningTaskSet::new()); let worker_workflow_cid = worker.workflow_info.cid; - worker.run(running_tasks.clone()).await.unwrap(); + worker.run(running_tasks.clone(), fetch_fn).await.unwrap(); assert_eq!(running_tasks.len(), 1); assert!(running_tasks.contains_key(&worker_workflow_cid)); assert_eq!(running_tasks.get(&worker_workflow_cid).unwrap().len(), 1); diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index dd83d1b3..34d908fd 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -21,7 +21,7 @@ impl Default for Settings { retries: 10, retry_max_delay: Duration::new(60, 0), retry_initial_delay: Duration::from_millis(500), - p2p_timeout: Duration::new(60, 0), + p2p_timeout: Duration::new(5, 0), timeout: Duration::new(3600, 0), } } diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 272e158b..28bc669a 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -64,7 +64,7 @@ fn test_workflow_run_serial() -> Result<()> { .arg("tests/fixtures/test_workflow2.toml") .arg("--db") .arg("homestar_test_workflow_run_serial.db") - .stdout(Stdio::piped()) + //.stdout(Stdio::piped()) .spawn() .unwrap(); @@ -111,11 +111,8 @@ fn test_workflow_run_serial() -> Result<()> { .for_each(|msg| async move { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected1 = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); - let expected2 = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); - if check != &expected1 && check != &expected2 { - panic!("JSONRPC response is not expected"); - } + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + assert_eq!(check, &expected); }) .await; @@ -129,7 +126,12 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); - assert!(sub2.next().await.is_some()); + let msg = sub2.next().await.unwrap(); + let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + assert_eq!(check, &expected); + assert!(sub2.next().await.is_some()); assert!(sub2.next().await.is_some()); @@ -148,9 +150,10 @@ fn test_workflow_run_serial() -> Result<()> { .with_timeout(std::time::Duration::from_millis(500)) .await .is_err(); + assert!(sub3.next().await.is_some()); - assert!(sub3.next().await.is_some()); - assert!(sub3.next().await.is_some()); + assert!(sub2.next().await.is_some()); + assert!(sub2.next().await.is_some()); let another_run_str = format!(r#"{{"name": "another_test","workflow": {}}}"#, json_string); let another_run: serde_json::Value = serde_json::from_str(&another_run_str).unwrap(); From ff4ebc8ee3737137645e2e3196138e1e3b4562bc Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 13:39:30 -0500 Subject: [PATCH 14/42] chore: updates, new wasms --- Cargo.lock | 1 + examples/websocket-relay/example_test.wasm | Bin 285147 -> 295627 bytes .../relay-app/src/lib/workflow.ts | 12 ++--- homestar-functions/test/Cargo.toml | 1 + homestar-functions/test/src/lib.rs | 51 +++++++++++++++++- homestar-functions/test/wit/host.wit | 4 ++ homestar-runtime/src/event_handler/event.rs | 9 ++-- homestar-runtime/src/network/webserver/rpc.rs | 11 +++- homestar-runtime/src/settings.rs | 2 +- homestar-runtime/src/worker.rs | 13 ++++- homestar-runtime/src/workflow.rs | 2 + .../test-workflow-image-pipeline.json | 10 ++-- homestar-runtime/tests/webserver.rs | 4 +- homestar-wasm/fixtures/example_test.wasm | Bin 285147 -> 295627 bytes .../fixtures/example_test_component.wasm | Bin 285548 -> 296304 bytes 15 files changed, 97 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23a5e284..c63b475a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2476,6 +2476,7 @@ dependencies = [ name = "homestar-functions-test" version = "0.1.0" dependencies = [ + "base64 0.21.5", "image", "wit-bindgen", ] diff --git a/examples/websocket-relay/example_test.wasm b/examples/websocket-relay/example_test.wasm index b382b252b6466241d86a0e378e0019a8fd910b6c..0d0e7528d835ff49d349f962434efb46988b1458 100755 GIT binary patch delta 102357 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 E|4DrPx&QzG delta 92097 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Ϝo33 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 diff --git a/examples/websocket-relay/relay-app/src/lib/workflow.ts b/examples/websocket-relay/relay-app/src/lib/workflow.ts index 19aaf894..d28d6b1c 100644 --- a/examples/websocket-relay/relay-app/src/lib/workflow.ts +++ b/examples/websocket-relay/relay-app/src/lib/workflow.ts @@ -259,7 +259,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, { @@ -282,7 +282,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, { @@ -306,7 +306,7 @@ export const workflowOneJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, ], @@ -336,7 +336,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, { @@ -359,7 +359,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, { @@ -382,7 +382,7 @@ export const workflowTwoJson = { }, nnc: "", op: "wasm/run", - rsc: "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4", + rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", }, }, ], diff --git a/homestar-functions/test/Cargo.toml b/homestar-functions/test/Cargo.toml index 5b01ff9a..6770b04a 100644 --- a/homestar-functions/test/Cargo.toml +++ b/homestar-functions/test/Cargo.toml @@ -6,6 +6,7 @@ edition = { workspace = true } rust-version = { workspace = true } [dependencies] +base64 = "0.21" image = { version = "0.24", default-features = false, features = ["png"] } wit-bindgen = "0.13" diff --git a/homestar-functions/test/src/lib.rs b/homestar-functions/test/src/lib.rs index 6abe64d6..4c5ca38c 100644 --- a/homestar-functions/test/src/lib.rs +++ b/homestar-functions/test/src/lib.rs @@ -5,6 +5,7 @@ wit_bindgen::generate!({ } }); +use base64::{engine::general_purpose, Engine}; use std::io::Cursor; pub struct Component; @@ -46,6 +47,14 @@ impl Guest for Component { buffer } + fn blur_base64(data: String, sigma: f32) -> Vec { + let base64_encoded_png = data.replace("data:image/png;base64,", ""); + let decoded = general_purpose::STANDARD + .decode(base64_encoded_png) + .unwrap(); + Self::blur(decoded, sigma) + } + fn crop(data: Vec, x: u32, y: u32, target_width: u32, target_height: u32) -> Vec { let mut img = image::load_from_memory_with_format(&data, image::ImageFormat::Png).unwrap(); @@ -60,6 +69,14 @@ impl Guest for Component { buffer } + fn crop_base64(data: String, x: u32, y: u32, target_width: u32, target_height: u32) -> Vec { + let base64_encoded_png = data.replace("data:image/png;base64,", ""); + let decoded = general_purpose::STANDARD + .decode(base64_encoded_png) + .unwrap(); + Self::crop(decoded, x, y, target_width, target_height) + } + fn grayscale(data: Vec) -> Vec { let img = image::load_from_memory_with_format(&data, image::ImageFormat::Png).unwrap(); let gray = img.grayscale(); @@ -71,9 +88,16 @@ impl Guest for Component { buffer } + fn grayscale_base64(data: String) -> Vec { + let base64_encoded_png = data.replace("data:image/png;base64,", ""); + let decoded = general_purpose::STANDARD + .decode(base64_encoded_png) + .unwrap(); + Self::grayscale(decoded) + } + fn rotate90(data: Vec) -> Vec { let img = image::load_from_memory_with_format(&data, image::ImageFormat::Png).unwrap(); - let rotated = img.rotate90(); let mut buffer: Vec = Vec::new(); @@ -83,6 +107,14 @@ impl Guest for Component { buffer } + + fn rotate90_base64(data: String) -> Vec { + let base64_encoded_png = data.replace("data:image/png;base64,", ""); + let decoded = general_purpose::STANDARD + .decode(base64_encoded_png) + .unwrap(); + Self::rotate90(decoded) + } } #[cfg(test)] @@ -214,4 +246,21 @@ mod test_mod { let cropped = Component::crop(gray, 150, 350, 400, 400); Component::blur(cropped, 0.1); } + + #[cfg(feature = "run-image-tests")] + #[test] + fn mixed_base64() { + let img_uri = r#"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAG9SURBVHgBrVRLTgJBEK3qaYhLtiZGxxMoN8CdISTiCYSEmLgSTkA8AXFFAgv0BkNMDDu4gXAC2vhhy9IIU2X1CGb4T5CXzCRd3f26Xv0QBOna+ykiVJjBha2A3XhMlbz8vsFsdeCOtP/CAAn4H4bAcKbGMarsgMwiYVUqIj6FHYERXBU2oHV7BYI95HsH6VKk5eUzkw0TPqfDCwK400iGWDXmw+BrJ9mSoE/X59VBZ2/vazjy4xIyzk3tat6Tp8Kh54+d5J8HgRZuhsksWjf7xssfD5npNaxsXvLV9PDz9cGxlSaB7sopA0uQbfQlEeoorAalBvvC5E4IO1KLj0L2ABGQqb+lCLAd8sgsSI5KFtxHXii3GUJxPZWuf5QhIgici7WEwavAKSsFNsB2mCQru5HQFqfW2sAGSLveLuuwBULR7X77fluSlYMVyNQ+LVlx2Z6ec8+TXzOunY5XmK07C1smo3GsTEDFFW/Nls2vBYwtH/G0R9I1gYlUAh04kSzk1g4SuasXjCJZLuWCfVbTg8AEkaAQl3fBViDuKemM0ropExWWg2K6iHYhk8NVMmhF2FazUUiMhKQkXdb9AfsesrssluqmAAAAAElFTkSuQmCC"#; + // Call component to rotate the image 90 deg clockwise + let rotated = Component::rotate90_base64(img_uri.into()); + let gray = Component::grayscale(rotated); + let cropped = Component::crop(gray, 10, 10, 50, 50); + let result = Component::blur(cropped, 0.1); + + let png_img = image::io::Reader::new(Cursor::new(&result)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + } } diff --git a/homestar-functions/test/wit/host.wit b/homestar-functions/test/wit/host.wit index e2fe58bc..cab94571 100644 --- a/homestar-functions/test/wit/host.wit +++ b/homestar-functions/test/wit/host.wit @@ -6,7 +6,11 @@ world test { export join-strings: func(a: string, b: string) -> string export transpose: func(matrix: list>) -> list> export blur: func(data: list, sigma: float32) -> list + export blur-base64: func(data: string, sigma: float32) -> list export crop: func(data: list, x: u32, y: u32, target-width: u32, target-height: u32) -> list + export crop-base64: func(data: string, x: u32, y: u32, target-width: u32, target-height: u32) -> list export grayscale: func(data: list) -> list + export grayscale-base64: func(data: string) -> list export rotate90: func(data: list) -> list + export rotate90-base64: func(data: string) -> list } diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 360d339d..9b99dcf5 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -362,8 +362,8 @@ impl Replay { }); // gossiping replayed receipts - receipts.into_iter().for_each(|receipt| { - if event_handler.pubsub_enabled { + if event_handler.pubsub_enabled { + receipts.into_iter().for_each(|receipt| { let receipt_cid = receipt.cid().to_string(); let _ = event_handler .swarm @@ -380,9 +380,8 @@ impl Replay { warn!(err=?err, cid=receipt_cid, "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC), ); - } - }); - + }); + } Ok(()) } } diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index 509e1243..2a8971d6 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -12,6 +12,7 @@ use dashmap::DashMap; use faststr::FastStr; #[cfg(feature = "websocket-notify")] use futures::StreamExt; +use homestar_core::ipld::DagCbor; use jsonrpsee::{ server::RpcModule, types::error::{ErrorCode, ErrorObject}, @@ -211,7 +212,10 @@ impl JsonRpc { Ok(listener::Run { name, workflow }) => { let (tx, rx) = oneshot::channel(); ctx.runner_sender - .send((Message::RunWorkflow((name.clone(), workflow)), Some(tx))) + .send(( + Message::RunWorkflow((name.clone(), workflow.clone())), + Some(tx), + )) .await?; if let Ok(Ok(Message::AckWorkflow((cid, name)))) = @@ -230,7 +234,10 @@ impl JsonRpc { "did not acknowledge message in time" ); let _ = pending - .reject(busy_err("workflow not able to run workflow: {cid}")) + .reject(busy_err(format!( + "not able to run workflow {}", + workflow.to_cid()? + ))) .await; } } diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index ed301134..5fe9d24f 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -278,7 +278,7 @@ impl Default for Network { webserver_port: 1337, webserver_timeout: Duration::new(120, 0), websocket_capacity: 1024, - websocket_receiver_timeout: Duration::from_millis(200), + websocket_receiver_timeout: Duration::from_millis(500), workflow_quorum: 3, keypair_config: PubkeyConfig::Random, node_addresses: Vec::new(), diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index e14042c7..25e9bc0c 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -376,7 +376,18 @@ where running_tasks.append_or_insert(self.workflow_info.cid(), handles); while let Some(res) = task_set.join_next().await { - let (executed, instruction_ptr, invocation_ptr, receipt_meta, add_meta) = res??; + let (executed, instruction_ptr, invocation_ptr, receipt_meta, add_meta) = match res + { + Ok(Ok(data)) => data, + Ok(Err(err)) => { + error!(err=?err, "error in running task"); + break; + } + Err(err) => { + error!(err=?err, "error in running task"); + break; + } + }; let output_to_store = Ipld::try_from(executed)?; let invocation_receipt = InvocationReceipt::new( diff --git a/homestar-runtime/src/workflow.rs b/homestar-runtime/src/workflow.rs index fd2b40c7..db56ee32 100644 --- a/homestar-runtime/src/workflow.rs +++ b/homestar-runtime/src/workflow.rs @@ -29,6 +29,7 @@ use itertools::Itertools; use libipld::{cbor::DagCborCodec, cid::Cid, prelude::Codec, serde::from_ipld, Ipld}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::Path}; +use tracing::debug; use url::Url; mod info; @@ -155,6 +156,7 @@ impl<'a> Builder<'a> { (Dag::default(), IndexMap::new()), |(mut dag, mut resources), (i, task)| { let instr_cid = task.instruction_cid()?; + debug!("instruction cid: {}", instr_cid); // Clone as we're owning the struct going backward. let ptr: Pointer = Invocation::::from(task.clone()).try_into()?; diff --git a/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json index bf46aab7..5d252963 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json +++ b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json @@ -22,7 +22,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } }, { @@ -37,7 +37,7 @@ "args": [ { "await/ok": { - "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy" + "/": "bafyrmid35kzcxbn5xhsewyzzja5gngm54peve5i45vdj3bxgokmuvj2wwy" } } ], @@ -45,7 +45,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } }, { @@ -60,7 +60,7 @@ "args": [ { "await/ok": { - "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4" + "/": "bafyrmicubn76iynz6hjice6x47p7hgdlhht6qoizhuu746uea6e674jap4" } } ], @@ -68,7 +68,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeichafzlolnoamugvfuyynjnj2gse7avstiqkeiuwuv2gyztap4qm4" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } } ] diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 28bc669a..0fcba0fa 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -111,7 +111,7 @@ fn test_workflow_run_serial() -> Result<()> { .for_each(|msg| async move { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); assert_eq!(check, &expected); }) .await; @@ -129,7 +129,7 @@ fn test_workflow_run_serial() -> Result<()> { let msg = sub2.next().await.unwrap(); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmicvwgispoezdciv5z6w3coutfjjtnhtmbegpcrrocqd76y7dvtknq"}}); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); assert_eq!(check, &expected); assert!(sub2.next().await.is_some()); diff --git a/homestar-wasm/fixtures/example_test.wasm b/homestar-wasm/fixtures/example_test.wasm index b382b252b6466241d86a0e378e0019a8fd910b6c..0d0e7528d835ff49d349f962434efb46988b1458 100755 GIT binary patch delta 102357 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 E|4DrPx&QzG delta 92097 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Ϝo33 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 diff --git a/homestar-wasm/fixtures/example_test_component.wasm b/homestar-wasm/fixtures/example_test_component.wasm index 914a7b504c272a5820a29a939d6f1acbdce062ef..41b38a4f694e0b6e383b389f9ee14a7b1e9cc10c 100644 GIT binary patch delta 102328 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 delta 91914 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 Date: Wed, 8 Nov 2023 14:25:56 -0500 Subject: [PATCH 15/42] chore: add one cleanup --- homestar-runtime/tests/cli.rs | 8 +++--- .../tests/fixtures/test-workflow-add-one.json | 6 ++--- homestar-runtime/tests/webserver.rs | 2 +- homestar-wasm/tests/execute_wasm.rs | 27 +++++++++++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 65a4c80b..2c597a10 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -206,10 +206,10 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "bafyrmibcfltf6vhtfdson5z4av4r4wg3rccpt4hxajt54msacojeecazqy", + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", )) .stdout(predicate::str::contains( - "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); @@ -224,10 +224,10 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "bafyrmibcfltf6vhtfdson5z4av4r4wg3rccpt4hxajt54msacojeecazqy", + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", )) .stdout(predicate::str::contains( - "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4", + "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); diff --git a/homestar-runtime/tests/fixtures/test-workflow-add-one.json b/homestar-runtime/tests/fixtures/test-workflow-add-one.json index 42dc847d..a218c06c 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-add-one.json +++ b/homestar-runtime/tests/fixtures/test-workflow-add-one.json @@ -17,7 +17,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4" + "rsc": "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm" } }, { @@ -33,7 +33,7 @@ "args": [ { "await/ok": { - "/": "bafyrmig5jivpubiljl26w5qc4om2rxbya6h43ljanotrvp2b2opux6gtbe" + "/": "bafyrmibiw5at3dsnpqqi36c2ped3lwsdaijpkwntiouou7yhztjdjfhd4e" } } ], @@ -41,7 +41,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiabbxwf2vn4j3zm7bbojr6rt6k7o6cg6xcbhqkllubmsnvocpv7y4" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } } ] diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 0fcba0fa..dbcd9432 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -64,7 +64,7 @@ fn test_workflow_run_serial() -> Result<()> { .arg("tests/fixtures/test_workflow2.toml") .arg("--db") .arg("homestar_test_workflow_run_serial.db") - //.stdout(Stdio::piped()) + .stdout(Stdio::piped()) .spawn() .unwrap(); diff --git a/homestar-wasm/tests/execute_wasm.rs b/homestar-wasm/tests/execute_wasm.rs index f387fc06..e8574a47 100644 --- a/homestar-wasm/tests/execute_wasm.rs +++ b/homestar-wasm/tests/execute_wasm.rs @@ -133,6 +133,33 @@ async fn test_append_string() { ); } +#[tokio::test] +async fn test_rotate_base64() { + let img_uri = r#"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAG9SURBVHgBrVRLTgJBEK3qaYhLtiZGxxMoN8CdISTiCYSEmLgSTkA8AXFFAgv0BkNMDDu4gXAC2vhhy9IIU2X1CGb4T5CXzCRd3f26Xv0QBOna+ykiVJjBha2A3XhMlbz8vsFsdeCOtP/CAAn4H4bAcKbGMarsgMwiYVUqIj6FHYERXBU2oHV7BYI95HsH6VKk5eUzkw0TPqfDCwK400iGWDXmw+BrJ9mSoE/X59VBZ2/vazjy4xIyzk3tat6Tp8Kh54+d5J8HgRZuhsksWjf7xssfD5npNaxsXvLV9PDz9cGxlSaB7sopA0uQbfQlEeoorAalBvvC5E4IO1KLj0L2ABGQqb+lCLAd8sgsSI5KFtxHXii3GUJxPZWuf5QhIgici7WEwavAKSsFNsB2mCQru5HQFqfW2sAGSLveLuuwBULR7X77fluSlYMVyNQ+LVlx2Z6ec8+TXzOunY5XmK07C1smo3GsTEDFFW/Nls2vBYwtH/G0R9I1gYlUAh04kSzk1g4SuasXjCJZLuWCfVbTg8AEkaAQl3fBViDuKemM0ropExWWg2K6iHYhk8NVMmhF2FazUUiMhKQkXdb9AfsesrssluqmAAAAAElFTkSuQmCC"#; + let ipld = Input::Ipld(Ipld::Map(BTreeMap::from([ + ("func".into(), Ipld::String("crop-base64".to_string())), + ( + "args".into(), + Ipld::List(vec![ + Ipld::String(img_uri.to_string()), + Ipld::Integer(10), + Ipld::Integer(10), + Ipld::Integer(50), + Ipld::Integer(50), + ]), + ), + ]))); + + let wasm = fs::read(fixtures("example_test.wasm")).unwrap(); + let mut env = World::instantiate(wasm, "crop-base64", State::default()) + .await + .unwrap(); + + let res = env.execute(ipld.parse().unwrap().try_into().unwrap()).await; + + assert!(res.is_ok()); +} + #[tokio::test] async fn test_matrix_transpose() { let ipld_inner = Ipld::List(vec![ From 14d7dc2378f00ebfe927cfd2902cc3eeb22aa177 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 15:45:11 -0500 Subject: [PATCH 16/42] chore: wasms update is life --- homestar-runtime/src/worker.rs | 4 ++++ homestar-runtime/src/workflow/settings.rs | 2 +- homestar-runtime/tests/cli.rs | 11 ++--------- .../tests/fixtures/test-workflow-add-one.json | 6 +++--- homestar-wasm/fixtures/example_add.wasm | Bin 8780 -> 8780 bytes .../fixtures/example_add_component.wasm | Bin 8803 -> 8803 bytes .../fixtures/example_add_component.wat | 4 ++-- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 25e9bc0c..ec98a254 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -417,6 +417,10 @@ where let stored_receipt = Db::commit_receipt(self.workflow_info.cid, receipt, &mut self.db.conn()?)?; + debug!( + cid = self.workflow_info.cid.to_string(), + "commited to database" + ); let _ = self .event_sender diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index 34d908fd..895f6609 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -35,7 +35,7 @@ impl Default for Settings { retry_max_delay: Duration::new(1, 0), retry_initial_delay: Duration::from_millis(50), p2p_timeout: Duration::from_millis(10), - timeout: Duration::from_secs(90), + timeout: Duration::from_secs(3600), } } } diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 2c597a10..de6659d9 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -171,7 +171,6 @@ fn test_server_serial() -> Result<()> { Ok(()) } -#[cfg(feature = "test-utils")] #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { @@ -206,10 +205,7 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - )) - .stdout(predicate::str::contains( - "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm", + "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); @@ -224,10 +220,7 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - )) - .stdout(predicate::str::contains( - "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm", + "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); diff --git a/homestar-runtime/tests/fixtures/test-workflow-add-one.json b/homestar-runtime/tests/fixtures/test-workflow-add-one.json index a218c06c..8ddbdf92 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-add-one.json +++ b/homestar-runtime/tests/fixtures/test-workflow-add-one.json @@ -17,7 +17,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm" + "rsc": "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm" } }, { @@ -33,7 +33,7 @@ "args": [ { "await/ok": { - "/": "bafyrmibiw5at3dsnpqqi36c2ped3lwsdaijpkwntiouou7yhztjdjfhd4e" + "/": "bafyrmigpbec3fpc64hxkehxkszctb5f5roylk5ej2t2qgfylf64gpdn54m" } } ], @@ -41,7 +41,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm" } } ] diff --git a/homestar-wasm/fixtures/example_add.wasm b/homestar-wasm/fixtures/example_add.wasm index 23dced58dd0d910978b3d8211839be9b54a53bb5..2f3d643ad13ee8ea7279d27590360382da2245da 100755 GIT binary patch delta 35 qcmX@(a>iwYp^~(@o`FDlW{GZ6W?o8qYMyRUX>kdgfu5nUo)G}w)C*q# delta 35 qcmX@(a>iwYp^~(jo`FDlW{GZ6W?o8qYMyRUX>kdgfu5nUo&f;gw+mbV diff --git a/homestar-wasm/fixtures/example_add_component.wasm b/homestar-wasm/fixtures/example_add_component.wasm index 428f0514285e5d2d811d5ec9176a57673d6be557..b6094828d732dc005cefb4f67c96e2e098a6c7d6 100644 GIT binary patch delta 39 vcmaFt^4Mj=OhsvPJp+OA%o5$C%)FHJ)I8my(&7>}13g1yJ)_B66xRa)Cp!)c delta 39 vcmaFt^4Mj=OhsukJp+OA%o5$C%)FHJ)I8my(&7>}13g1yJ%h Date: Wed, 8 Nov 2023 17:51:49 -0500 Subject: [PATCH 17/42] chore: test fixins --- homestar-runtime/tests/network.rs | 2 +- homestar-runtime/tests/webserver.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index 0884b91c..9a148fdf 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -591,7 +591,7 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { ); assert!(two_disconnected_from_one); - assert_eq!(false, two_removed_from_dht_table); + assert!(!two_removed_from_dht_table); Ok(()) } diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index dbcd9432..dcc97100 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -44,7 +44,8 @@ fn test_workflow_run_serial() -> Result<()> { "../examples/websocket-relay/example_test.wasm", ]; - let _ = fs::remove_file("homestar_test_workflow_run_serial.db"); + const DB: &str = "ws_homestar_test_workflow_run.db"; + let _ = fs::remove_file(DB); let _ipfs_add_img = Command::new(IPFS) .args(add_image_args) @@ -63,7 +64,7 @@ fn test_workflow_run_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_workflow2.toml") .arg("--db") - .arg("homestar_test_workflow_run_serial.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -179,6 +180,7 @@ fn test_workflow_run_serial() -> Result<()> { let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); let _ = stop_all_bins(); + let _ = fs::remove_file(DB); Ok(()) } From f6a3b4f30871af0672a50709fb25d179ca418082 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 21:24:27 -0500 Subject: [PATCH 18/42] chore: minor updates --- homestar-functions/test/src/lib.rs | 8 +- homestar-runtime/src/network/webserver/rpc.rs | 1 + homestar-runtime/src/settings.rs | 2 +- homestar-runtime/src/worker.rs | 13 +- homestar-runtime/tests/webserver.rs | 112 ++++++++++++++---- 5 files changed, 100 insertions(+), 36 deletions(-) diff --git a/homestar-functions/test/src/lib.rs b/homestar-functions/test/src/lib.rs index 4c5ca38c..706aee9a 100644 --- a/homestar-functions/test/src/lib.rs +++ b/homestar-functions/test/src/lib.rs @@ -255,12 +255,6 @@ mod test_mod { let rotated = Component::rotate90_base64(img_uri.into()); let gray = Component::grayscale(rotated); let cropped = Component::crop(gray, 10, 10, 50, 50); - let result = Component::blur(cropped, 0.1); - - let png_img = image::io::Reader::new(Cursor::new(&result)) - .with_guessed_format() - .unwrap() - .decode() - .unwrap(); + Component::blur(cropped, 0.1); } } diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index 2a8971d6..a0ee87f6 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -12,6 +12,7 @@ use dashmap::DashMap; use faststr::FastStr; #[cfg(feature = "websocket-notify")] use futures::StreamExt; +#[cfg(feature = "websocket-notify")] use homestar_core::ipld::DagCbor; use jsonrpsee::{ server::RpcModule, diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 5fe9d24f..876d3bf1 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -278,7 +278,7 @@ impl Default for Network { webserver_port: 1337, webserver_timeout: Duration::new(120, 0), websocket_capacity: 1024, - websocket_receiver_timeout: Duration::from_millis(500), + websocket_receiver_timeout: Duration::from_millis(1000), workflow_quorum: 3, keypair_config: PubkeyConfig::Random, node_addresses: Vec::new(), diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index ec98a254..50c0e52f 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -21,7 +21,7 @@ use crate::{ workflow::{self, Resource}, Db, Receipt, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use chrono::NaiveDateTime; use faststr::FastStr; use fnv::FnvHashSet; @@ -351,7 +351,16 @@ where }); let handle = task_set.spawn(async move { - let resolved = resolved.await?; + let resolved = match resolved.await { + Ok(inst_result) => inst_result, + Err(err) => { + error!(err=?err, "error resolving cid"); + return Err(anyhow!("error resolving cid: {err}")) + .with_context(|| { + format!("could not spawn task for cid: {workflow_cid}") + }); + } + }; match wasm_ctx.run(wasm, &fun, resolved).await { Ok(output) => Ok(( output, diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index dcc97100..3c68e6cf 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -2,7 +2,6 @@ use crate::utils::startup_ipfs; use crate::utils::{kill_homestar, stop_all_bins, TimeoutFutureExt, BIN_NAME, IPFS}; use anyhow::Result; -use futures::StreamExt; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, rpc_params, @@ -83,10 +82,6 @@ fn test_workflow_run_serial() -> Result<()> { let ws_url = format!("ws://{}:{}", Ipv4Addr::LOCALHOST, ws_port); tokio_test::block_on(async { - tokio_tungstenite::connect_async(ws_url.clone()) - .await - .unwrap(); - let workflow_str = fs::read_to_string("tests/fixtures/test-workflow-image-pipeline.json").unwrap(); let json: serde_json::Value = serde_json::from_str(&workflow_str).unwrap(); @@ -98,7 +93,7 @@ fn test_workflow_run_serial() -> Result<()> { .build(ws_url.clone()) .await .unwrap(); - let sub1: Subscription> = client1 + let mut sub1: Subscription> = client1 .subscribe( SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, rpc_params![run.clone()], @@ -108,14 +103,35 @@ fn test_workflow_run_serial() -> Result<()> { .unwrap(); // we have 3 operations - sub1.take(3) - .for_each(|msg| async move { - let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); - let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); - assert_eq!(check, &expected); - }) - .await; + let one = sub1 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap(); + let json: serde_json::Value = serde_json::from_slice(&one.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + + let two = sub1 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap(); + let json: serde_json::Value = serde_json::from_slice(&two.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + + let three = sub1 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap(); + let json: serde_json::Value = serde_json::from_slice(&three.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); // separate subscription, only 3 events too let mut sub2: Subscription> = client1 @@ -127,14 +143,28 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); - let msg = sub2.next().await.unwrap(); - let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); + let msg = sub2 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap(); + let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); let check = json.get("metadata").unwrap(); let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); assert_eq!(check, &expected); - assert!(sub2.next().await.is_some()); - assert!(sub2.next().await.is_some()); + assert!(sub2 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap() + .is_some()); + assert!(sub2 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap() + .is_some()); let client2 = WsClientBuilder::default().build(ws_url).await.unwrap(); let mut sub3: Subscription> = client2 @@ -148,13 +178,28 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(std::time::Duration::from_millis(500)) + .with_timeout(std::time::Duration::from_millis(2500)) .await .is_err(); - assert!(sub3.next().await.is_some()); - assert!(sub2.next().await.is_some()); - assert!(sub2.next().await.is_some()); + assert!(sub3 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap() + .is_some()); + assert!(sub2 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap() + .is_some()); + assert!(sub2 + .next() + .with_timeout(std::time::Duration::from_millis(2500)) + .await + .unwrap() + .is_some()); let another_run_str = format!(r#"{{"name": "another_test","workflow": {}}}"#, json_string); let another_run: serde_json::Value = serde_json::from_str(&another_run_str).unwrap(); @@ -169,12 +214,27 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub3 .next() - .with_timeout(std::time::Duration::from_millis(500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .is_err(); - assert!(sub4.next().await.is_some()); - assert!(sub4.next().await.is_some()); - assert!(sub4.next().await.is_some()); + assert!(sub4 + .next() + .with_timeout(std::time::Duration::from_millis(5000)) + .await + .unwrap() + .is_some()); + assert!(sub4 + .next() + .with_timeout(std::time::Duration::from_millis(5000)) + .await + .unwrap() + .is_some()); + assert!(sub4 + .next() + .with_timeout(std::time::Duration::from_millis(5000)) + .await + .unwrap() + .is_some()); }); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); From b7ae3f9c3e01f5b5a20ded646398e1a3ff6abcc1 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 21:48:23 -0500 Subject: [PATCH 19/42] chore: updates --- homestar-runtime/src/workflow/settings.rs | 2 +- homestar-runtime/tests/webserver.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index 895f6609..2e778d6c 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -31,7 +31,7 @@ impl Default for Settings { impl Default for Settings { fn default() -> Self { Self { - retries: 1, + retries: 3, retry_max_delay: Duration::new(1, 0), retry_initial_delay: Duration::from_millis(50), p2p_timeout: Duration::from_millis(10), diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 3c68e6cf..9b62666e 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -21,6 +21,7 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; +#[cfg(feature = "test-utils")] #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { From 0aac079e53a740d25a89211fcda696c7e2b79163 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 22:01:10 -0500 Subject: [PATCH 20/42] chore: updates --- homestar-runtime/tests/webserver.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 9b62666e..56e8ea2e 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -106,7 +106,7 @@ fn test_workflow_run_serial() -> Result<()> { // we have 3 operations let one = sub1 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&one.unwrap().unwrap()).unwrap(); @@ -116,7 +116,7 @@ fn test_workflow_run_serial() -> Result<()> { let two = sub1 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&two.unwrap().unwrap()).unwrap(); @@ -126,7 +126,7 @@ fn test_workflow_run_serial() -> Result<()> { let three = sub1 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&three.unwrap().unwrap()).unwrap(); @@ -146,7 +146,7 @@ fn test_workflow_run_serial() -> Result<()> { let msg = sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); @@ -156,13 +156,13 @@ fn test_workflow_run_serial() -> Result<()> { assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap() .is_some()); @@ -179,25 +179,25 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .is_err(); assert!(sub3 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(2500)) + .with_timeout(std::time::Duration::from_millis(5000)) .await .unwrap() .is_some()); From b88d113d138b3e1d7c8fd7068016cfbdb6fd7397 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 22:15:23 -0500 Subject: [PATCH 21/42] chore: updates --- homestar-runtime/tests/webserver.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 56e8ea2e..70c1870b 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -94,6 +94,7 @@ fn test_workflow_run_serial() -> Result<()> { .build(ws_url.clone()) .await .unwrap(); + let mut sub1: Subscription> = client1 .subscribe( SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, @@ -106,7 +107,7 @@ fn test_workflow_run_serial() -> Result<()> { // we have 3 operations let one = sub1 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&one.unwrap().unwrap()).unwrap(); @@ -116,7 +117,7 @@ fn test_workflow_run_serial() -> Result<()> { let two = sub1 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&two.unwrap().unwrap()).unwrap(); @@ -126,7 +127,7 @@ fn test_workflow_run_serial() -> Result<()> { let three = sub1 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&three.unwrap().unwrap()).unwrap(); @@ -146,7 +147,7 @@ fn test_workflow_run_serial() -> Result<()> { let msg = sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); @@ -156,13 +157,13 @@ fn test_workflow_run_serial() -> Result<()> { assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); @@ -179,25 +180,25 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .is_err(); assert!(sub3 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); @@ -215,24 +216,24 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub3 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .is_err(); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(5000)) + .with_timeout(std::time::Duration::from_millis(10000)) .await .unwrap() .is_some()); From b233e1c56b1fd7899d94c0e988803efed17f0530 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 8 Nov 2023 22:36:14 -0500 Subject: [PATCH 22/42] chore: updates --- homestar-runtime/tests/webserver.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 70c1870b..bd085f73 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -107,7 +107,7 @@ fn test_workflow_run_serial() -> Result<()> { // we have 3 operations let one = sub1 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&one.unwrap().unwrap()).unwrap(); @@ -117,7 +117,7 @@ fn test_workflow_run_serial() -> Result<()> { let two = sub1 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&two.unwrap().unwrap()).unwrap(); @@ -127,7 +127,7 @@ fn test_workflow_run_serial() -> Result<()> { let three = sub1 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&three.unwrap().unwrap()).unwrap(); @@ -147,7 +147,7 @@ fn test_workflow_run_serial() -> Result<()> { let msg = sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap(); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); @@ -157,13 +157,13 @@ fn test_workflow_run_serial() -> Result<()> { assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); @@ -180,25 +180,25 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .is_err(); assert!(sub3 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); assert!(sub2 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); @@ -216,24 +216,24 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub3 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .is_err(); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); assert!(sub4 .next() - .with_timeout(std::time::Duration::from_millis(10000)) + .with_timeout(std::time::Duration::from_millis(25000)) .await .unwrap() .is_some()); From 522b42a62c0041988f56f330ebb0e6507662b1f7 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Thu, 9 Nov 2023 04:32:01 -0500 Subject: [PATCH 23/42] chore: testing --- .config/nextest.toml | 2 +- homestar-runtime/tests/utils.rs | 4 ++-- homestar-runtime/tests/webserver.rs | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index a4a1e566..ec97097d 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -4,7 +4,7 @@ test-threads = "num-cpus" threads-required = 1 [profile.ci] -retries = { backoff = "exponential", count = 3, delay = "30s", jitter = true, max-delay = "300s" } +retries = { backoff = "exponential", count = 3, delay = "5s", jitter = true, max-delay = "30s" } failure-output = "immediate-final" fail-fast = false diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 61d0aea0..2d763bca 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -34,8 +34,8 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) /// Start-up IPFS daemon for tests with the feature turned-on. #[allow(dead_code)] #[cfg(feature = "ipfs")] -pub(crate) fn startup_ipfs() -> Result<()> { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".ipfs"); +pub(crate) fn startup_ipfs(ext: &str) -> Result<()> { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!(".ipfs{}", ext)); println!("starting ipfs daemon...{}", path.to_str().unwrap()); let mut ipfs_daemon = Command::new(IPFS) .args(["--offline", "daemon", "--init"]) diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index bd085f73..98606003 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -25,10 +25,13 @@ const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { + const DB: &str = "ws_homestar_test_workflow_run.db"; + const IPFS_EXT: &str = "ws_homestar_test_workflow_run"; + let _ = stop_all_bins(); #[cfg(feature = "ipfs")] - let _ = startup_ipfs(); + let _ = startup_ipfs(IPFS_EXT); let add_image_args = vec![ "add", @@ -44,7 +47,6 @@ fn test_workflow_run_serial() -> Result<()> { "../examples/websocket-relay/example_test.wasm", ]; - const DB: &str = "ws_homestar_test_workflow_run.db"; let _ = fs::remove_file(DB); let _ipfs_add_img = Command::new(IPFS) From 476c47f774e0dfdd6483226f117ab44b75bd094d Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Fri, 10 Nov 2023 06:58:04 -0800 Subject: [PATCH 24/42] feat: Add pubsub receipt sharing notifications (#418) # Description This PR implements the following changes: - [x] Add receipt published and received notifications - [x] Update receipt sharing log messages - [x] Add receipt sharing integration test - [x] Add `remove_db` and `wait_for_socket_connection` test utilities - [x] Update JSON `peer_id` key to `peerId` ## Link to issue Implements #131 ## Type of change - [x] New feature (non-breaking change that adds functionality) - [x] Refactor (non-breaking change that updates existing functionality) ## Test plan (required) This PR includes an integration test that checks for gossiped receipts in websocket notifications and logs. --------- Co-authored-by: Zeeshan Lakhani --- .config/nextest.toml | 4 +- .github/workflows/tests_and_checks.yml | 1 - .ignore | 2 + homestar-core/src/test_utils/workflow.rs | 2 +- homestar-core/src/workflow/instruction.rs | 2 +- homestar-core/src/workflow/pointer.rs | 12 +- homestar-core/src/workflow/task.rs | 2 +- homestar-runtime/Cargo.toml | 1 + homestar-runtime/src/event_handler/event.rs | 59 ++++- .../src/event_handler/notification/swarm.rs | 10 + .../src/event_handler/swarm_event.rs | 31 ++- homestar-runtime/src/network/ipfs.rs | 3 +- homestar-runtime/src/network/rpc.rs | 12 +- homestar-runtime/src/network/webserver.rs | 13 +- homestar-runtime/src/network/webserver/rpc.rs | 15 +- homestar-runtime/src/runner.rs | 70 +++--- homestar-runtime/src/settings.rs | 2 +- homestar-runtime/src/tasks/fetch.rs | 42 +++- homestar-runtime/src/test_utils/event.rs | 12 +- .../src/test_utils/proc_macro/src/lib.rs | 1 + .../src/test_utils/worker_builder.rs | 7 +- homestar-runtime/src/worker.rs | 6 +- homestar-runtime/src/workflow/settings.rs | 2 +- homestar-runtime/tests/cli.rs | 105 ++------ .../tests/fixtures/test-workflow-add-one.json | 6 +- .../tests/fixtures/test_gossip1.toml | 20 ++ .../tests/fixtures/test_gossip2.toml | 20 ++ homestar-runtime/tests/fixtures/test_v4.toml | 2 +- .../tests/fixtures/test_windows_v4.toml | 12 + homestar-runtime/tests/metrics.rs | 12 +- homestar-runtime/tests/network.rs | 130 +++++++++- homestar-runtime/tests/network/gossip.rs | 213 +++++++++++++++++ .../tests/network/notification.rs | 21 +- homestar-runtime/tests/utils.rs | 93 +++----- homestar-runtime/tests/webserver.rs | 224 +++++++----------- 35 files changed, 747 insertions(+), 422 deletions(-) create mode 100644 homestar-runtime/tests/fixtures/test_gossip1.toml create mode 100644 homestar-runtime/tests/fixtures/test_gossip2.toml create mode 100644 homestar-runtime/tests/fixtures/test_windows_v4.toml create mode 100644 homestar-runtime/tests/network/gossip.rs diff --git a/.config/nextest.toml b/.config/nextest.toml index ec97097d..81536abc 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -4,8 +4,10 @@ test-threads = "num-cpus" threads-required = 1 [profile.ci] -retries = { backoff = "exponential", count = 3, delay = "5s", jitter = true, max-delay = "30s" } +retries = { backoff = "exponential", count = 3, delay = "30s", jitter = true, max-delay = "300s" } failure-output = "immediate-final" +leak-timeout = "800ms" +slow-timeout = { period = "120s", terminate-after = 2 } fail-fast = false [test-groups] diff --git a/.github/workflows/tests_and_checks.yml b/.github/workflows/tests_and_checks.yml index 512e1878..5bd2b930 100644 --- a/.github/workflows/tests_and_checks.yml +++ b/.github/workflows/tests_and_checks.yml @@ -246,7 +246,6 @@ jobs: - name: Run Doc Tests if: ${{ matrix.default-features == 'all' }} run: cargo test --doc --workspace - continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} run-docs: needs: changes diff --git a/.ignore b/.ignore index 0ea79a35..feba533b 100644 --- a/.ignore +++ b/.ignore @@ -17,6 +17,8 @@ LICENSE .pre-commit-config.yaml **/fixtures +*.ipfs* +*.db ## examples examples/websocket-relay/relay-app diff --git a/homestar-core/src/test_utils/workflow.rs b/homestar-core/src/test_utils/workflow.rs index 81d4b7d3..956cedc4 100644 --- a/homestar-core/src/test_utils/workflow.rs +++ b/homestar-core/src/test_utils/workflow.rs @@ -19,7 +19,7 @@ use std::collections::BTreeMap; use url::Url; const RAW: u64 = 0x55; -const WASM_CID: &str = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm"; +const WASM_CID: &str = "bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"; type NonceBytes = Vec; diff --git a/homestar-core/src/workflow/instruction.rs b/homestar-core/src/workflow/instruction.rs index fbc11d09..a0124274 100644 --- a/homestar-core/src/workflow/instruction.rs +++ b/homestar-core/src/workflow/instruction.rs @@ -330,7 +330,7 @@ mod test { ( RESOURCE_KEY.into(), Ipld::String( - "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".into() + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia".into() ) ), (OP_KEY.into(), Ipld::String("ipld/fun".to_string())), diff --git a/homestar-core/src/workflow/pointer.rs b/homestar-core/src/workflow/pointer.rs index 973d876a..33c227ed 100644 --- a/homestar-core/src/workflow/pointer.rs +++ b/homestar-core/src/workflow/pointer.rs @@ -18,11 +18,7 @@ use diesel::{ AsExpression, FromSqlRow, }; use enum_assoc::Assoc; -use libipld::{ - cid::{multibase::Base, Cid}, - serde::from_ipld, - Ipld, Link, -}; +use libipld::{cid::Cid, serde::from_ipld, Ipld, Link}; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, collections::btree_map::BTreeMap, fmt, str::FromStr}; @@ -183,11 +179,7 @@ pub struct Pointer(Cid); impl fmt::Display for Pointer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let cid_as_string = self - .0 - .to_string_of_base(Base::Base32Lower) - .map_err(|_| fmt::Error)?; - + let cid_as_string = self.0.to_string(); write!(f, "{cid_as_string}") } } diff --git a/homestar-core/src/workflow/task.rs b/homestar-core/src/workflow/task.rs index 77e6e9d5..8bd6d914 100644 --- a/homestar-core/src/workflow/task.rs +++ b/homestar-core/src/workflow/task.rs @@ -190,7 +190,7 @@ mod test { ( "rsc".into(), Ipld::String( - "ipfs://bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm".into(), + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia".into(), ), ), ("op".into(), Ipld::String("ipld/fun".to_string())), diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index c5453f9f..00686d5e 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -32,6 +32,7 @@ bench = false [[test]] name = "integration" path = "tests/main.rs" +required-features = ["test-utils"] [dependencies] # return to version.workspace = true after the following issue is fixed: diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 9b99dcf5..a1988585 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -2,12 +2,14 @@ use super::EventHandler; #[cfg(feature = "websocket-notify")] -use crate::event_handler::notification::emit_receipt; +use crate::event_handler::notification::{ + self, emit_receipt, EventNotificationTyp, SwarmNotification, +}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ db::Database, - event_handler::{Handler, P2PSender, ResponseEvent}, + event_handler::{channel::AsyncBoundedChannelSender, Handler, P2PSender, ResponseEvent}, network::{ pubsub, swarm::{CapsuleTag, RequestResponseKey, TopicMessage}, @@ -25,10 +27,11 @@ use libp2p::{ rendezvous::Namespace, PeerId, }; +#[cfg(feature = "websocket-notify")] +use maplit::btreemap; use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; #[cfg(feature = "ipfs")] use tokio::runtime::Handle; -use tokio::sync::oneshot; use tracing::{error, info, warn}; /// A [Receipt] captured (inner) event. @@ -92,7 +95,7 @@ pub(crate) enum Event { #[cfg(feature = "websocket-notify")] ReplayReceipts(Replay), /// General shutdown event. - Shutdown(oneshot::Sender<()>), + Shutdown(AsyncBoundedChannelSender<()>), /// Find a [Record] in the DHT, e.g. a [Receipt]. /// /// [Record]: libp2p::kad::Record @@ -255,13 +258,28 @@ impl Captured { if event_handler.pubsub_enabled { match event_handler.swarm.behaviour_mut().gossip_publish( pubsub::RECEIPTS_TOPIC, - TopicMessage::CapturedReceipt(receipt), + TopicMessage::CapturedReceipt(receipt.clone()), ) { - Ok(msg_id) => info!( - cid = receipt_cid.to_string(), - "message {msg_id} published on {} topic for receipt", - pubsub::RECEIPTS_TOPIC - ), + Ok(msg_id) => { + info!( + cid = receipt_cid.to_string(), + message_id = msg_id.to_string(), + "message published on {} topic for receipt with cid: {receipt_cid}", + pubsub::RECEIPTS_TOPIC + ); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification( + SwarmNotification::PublishedReceiptPubsub, + ), + btreemap! { + "cid" => receipt.cid().to_string(), + "ran" => receipt.ran().to_string() + }, + ); + } Err(err) => { warn!( err=?err, @@ -370,11 +388,26 @@ impl Replay { .behaviour_mut() .gossip_publish( pubsub::RECEIPTS_TOPIC, - TopicMessage::CapturedReceipt(receipt), + TopicMessage::CapturedReceipt(receipt.clone()), ) - .map(|msg_id| + .map(|msg_id| { info!(cid=receipt_cid, - "message {msg_id} published on {} topic for receipt", pubsub::RECEIPTS_TOPIC)) + message_id = msg_id.to_string(), + "message published on {} topic for receipt with cid: {receipt_cid}", + pubsub::RECEIPTS_TOPIC); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification( + SwarmNotification::PublishedReceiptPubsub, + ), + btreemap! { + "cid" => receipt.cid().to_string(), + "ran" => receipt.ran().to_string() + }, + ); + }) .map_err( |err| warn!(err=?err, cid=receipt_cid, diff --git a/homestar-runtime/src/event_handler/notification/swarm.rs b/homestar-runtime/src/event_handler/notification/swarm.rs index 8e98947b..c2745bd2 100644 --- a/homestar-runtime/src/event_handler/notification/swarm.rs +++ b/homestar-runtime/src/event_handler/notification/swarm.rs @@ -10,6 +10,8 @@ pub(crate) enum SwarmNotification { ListeningOn, OutgoingConnectionError, IncomingConnectionError, + PublishedReceiptPubsub, + ReceivedReceiptPubsub, } impl fmt::Display for SwarmNotification { @@ -24,6 +26,12 @@ impl fmt::Display for SwarmNotification { SwarmNotification::IncomingConnectionError => { write!(f, "incomingConnectionError") } + SwarmNotification::ReceivedReceiptPubsub => { + write!(f, "receivedReceiptPubsub") + } + SwarmNotification::PublishedReceiptPubsub => { + write!(f, "publishedReceiptPubsub") + } } } } @@ -38,6 +46,8 @@ impl FromStr for SwarmNotification { "listeningOn" => Ok(Self::ListeningOn), "outgoingConnectionError" => Ok(Self::OutgoingConnectionError), "incomingConnectionError" => Ok(Self::IncomingConnectionError), + "receivedReceiptPubsub" => Ok(Self::ReceivedReceiptPubsub), + "publishedReceiptPubsub" => Ok(Self::PublishedReceiptPubsub), _ => Err(anyhow!("Missing swarm notification type: {}", ty)), } } diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index 03379420..417b9581 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -406,16 +406,33 @@ async fn handle_swarm_event( } => match Receipt::try_from(message.data) { // TODO: dont fail blindly if we get a non receipt message Ok(receipt) => { - info!("got message: {receipt} from {propagation_source} with message id: {message_id}"); + info!( + peer_id = propagation_source.to_string(), + message_id = message_id.to_string(), + "message received on receipts topic: {receipt}" + ); // Store gossiped receipt. let _ = event_handler .db .conn() .as_mut() - .map(|conn| Db::store_receipt(receipt, conn)); + .map(|conn| Db::store_receipt(receipt.clone(), conn)); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification( + SwarmNotification::ReceivedReceiptPubsub, + ), + btreemap! { + "peerId" => propagation_source.to_string(), + "cid" => receipt.cid().to_string(), + "ran" => receipt.ran().to_string() + }, + ); } - Err(err) => info!(err=?err, "cannot handle incoming event message"), + Err(err) => info!(err=?err, "cannot handle incoming gossipsub message"), }, gossipsub::Event::Subscribed { peer_id, topic } => { debug!( @@ -728,7 +745,7 @@ async fn handle_swarm_event( event_handler.ws_evt_sender(), EventNotificationTyp::SwarmNotification(SwarmNotification::ListeningOn), btreemap! { - "peer_id" => local_peer.to_string(), + "peerId" => local_peer.to_string(), "address" => address.to_string() }, ); @@ -749,7 +766,7 @@ async fn handle_swarm_event( event_handler.ws_evt_sender(), EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionEstablished), btreemap! { - "peer_id" => peer_id.to_string(), + "peerId" => peer_id.to_string(), "address" => endpoint.get_remote_address().to_string() }, ); @@ -794,7 +811,7 @@ async fn handle_swarm_event( event_handler.ws_evt_sender(), EventNotificationTyp::SwarmNotification(SwarmNotification::ConnnectionClosed), btreemap! { - "peer_id" => peer_id.to_string(), + "peerId" => peer_id.to_string(), "address" => endpoint.get_remote_address().to_string() }, ); @@ -816,7 +833,7 @@ async fn handle_swarm_event( event_handler.ws_evt_sender(), EventNotificationTyp::SwarmNotification(SwarmNotification::OutgoingConnectionError), btreemap! { - "peer_id" => peer_id.map_or("Unknown peer".into(), |p| p.to_string()), + "peerId" => peer_id.map_or("Unknown peer".into(), |p| p.to_string()), "error" => error.to_string() }, ); diff --git a/homestar-runtime/src/network/ipfs.rs b/homestar-runtime/src/network/ipfs.rs index 7e3aada1..2322ca97 100644 --- a/homestar-runtime/src/network/ipfs.rs +++ b/homestar-runtime/src/network/ipfs.rs @@ -78,8 +78,7 @@ impl IpfsCli { let DagPutResponse { cid } = self .0 .dag_put_with_options(Cursor::new(receipt_bytes), dag_builder) - .await - .expect("a CID"); + .await?; Ok(cid.cid_string) } diff --git a/homestar-runtime/src/network/rpc.rs b/homestar-runtime/src/network/rpc.rs index 0452118e..fdef2b97 100644 --- a/homestar-runtime/src/network/rpc.rs +++ b/homestar-runtime/src/network/rpc.rs @@ -14,7 +14,7 @@ use tarpc::{ context, server::{self, incoming::Incoming, Channel}, }; -use tokio::{runtime::Handle, select, sync::oneshot, time}; +use tokio::{runtime::Handle, select, time}; use tokio_serde::formats::MessagePack; use tracing::{info, warn}; @@ -34,7 +34,7 @@ pub(crate) enum ServerMessage { /// Message sent by the [Runner] to start a graceful shutdown. /// /// [Runner]: crate::Runner - GracefulShutdown(oneshot::Sender<()>), + GracefulShutdown(AsyncBoundedChannelSender<()>), /// Message sent to start a [Workflow] run by reading a [Workflow] file. /// /// [Workflow]: homestar_core::Workflow @@ -119,15 +119,15 @@ impl Interface for ServerHandler { name: Option, workflow_file: ReadWorkflow, ) -> Result, Error> { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = AsyncBoundedChannel::oneshot(); self.runner_sender - .send((ServerMessage::Run((name, workflow_file)), Some(tx))) + .send_async((ServerMessage::Run((name, workflow_file)), Some(tx))) .await .map_err(|e| Error::FailureToSendOnChannel(e.to_string()))?; let now = time::Instant::now(); select! { - Ok(msg) = rx => { + Ok(msg) = rx.recv_async() => { match msg { ServerMessage::RunAck(response) => { Ok(response) @@ -149,7 +149,7 @@ impl Interface for ServerHandler { } async fn stop(self, _: context::Context) -> Result<(), Error> { self.runner_sender - .send((ServerMessage::ShutdownCmd, None)) + .send_async((ServerMessage::ShutdownCmd, None)) .await .map_err(|e| Error::FailureToSendOnChannel(e.to_string())) } diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index f37d052b..ab6aab4a 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -254,7 +254,7 @@ mod test { use super::*; #[cfg(feature = "websocket-notify")] use crate::event_handler::notification::ReceiptNotification; - use crate::{db::Database, settings::Settings}; + use crate::{channel::AsyncBoundedChannel, db::Database, settings::Settings}; #[cfg(feature = "websocket-notify")] use homestar_core::{ ipld::DagJson, @@ -268,7 +268,6 @@ mod test { use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; #[cfg(feature = "websocket-notify")] use notifier::{self, Header}; - use tokio::sync::mpsc; async fn metrics_handle(settings: Settings) -> PrometheusHandle { #[cfg(feature = "monitoring")] @@ -290,7 +289,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); + let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -332,7 +331,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); + let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -365,7 +364,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); + let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -442,7 +441,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); + let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -476,7 +475,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = mpsc::channel(1); + let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index a0ee87f6..65d04c3a 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -2,7 +2,7 @@ use super::notifier::{self, Header, Notifier, SubscriptionTyp}; #[allow(unused_imports)] use super::{listener, prom::PrometheusData, Message}; -use crate::runner::WsSender; +use crate::{channel::AsyncBoundedChannel, runner::WsSender}; #[cfg(feature = "websocket-notify")] use anyhow::anyhow; use anyhow::Result; @@ -139,14 +139,14 @@ impl JsonRpc { #[cfg(not(test))] module.register_async_method(HEALTH_ENDPOINT, |_, ctx| async move { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = AsyncBoundedChannel::oneshot(); ctx.runner_sender - .send((Message::GetNodeInfo, Some(tx))) + .send_async((Message::GetNodeInfo, Some(tx))) .await .map_err(|err| internal_err(err.to_string()))?; if let Ok(Ok(Message::AckNodeInfo(info))) = - time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await + time::timeout_at(Instant::now() + ctx.receiver_timeout, rx.recv_async()).await { Ok(serde_json::json!({ "healthy": true, "nodeInfo": info})) } else { @@ -211,16 +211,17 @@ impl JsonRpc { |params, pending, ctx| async move { match params.one::>() { Ok(listener::Run { name, workflow }) => { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = AsyncBoundedChannel::oneshot(); ctx.runner_sender - .send(( + .send_async(( Message::RunWorkflow((name.clone(), workflow.clone())), Some(tx), )) .await?; if let Ok(Ok(Message::AckWorkflow((cid, name)))) = - time::timeout_at(Instant::now() + ctx.receiver_timeout, rx).await + time::timeout_at(Instant::now() + ctx.receiver_timeout, rx.recv_async()) + .await { let sink = pending.accept().await?; ctx.workflow_listeners diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 06f59ff1..179bcea1 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -4,7 +4,7 @@ #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ - channel::AsyncBoundedChannelSender, + channel::{AsyncBoundedChannel, AsyncBoundedChannelReceiver, AsyncBoundedChannelSender}, db::Database, event_handler::{Event, EventHandler}, network::{rpc, swarm, webserver}, @@ -34,7 +34,6 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::windows; use tokio::{ runtime, select, - sync::{mpsc, oneshot}, task::{AbortHandle, JoinHandle}, time, }; @@ -63,28 +62,28 @@ pub(crate) trait ModifiedSet { fn append_or_insert(&self, cid: Cid, handles: Vec); } -/// [mpsc::Sender] for RPC server messages. -pub(crate) type RpcSender = mpsc::Sender<( +/// [AsyncBoundedChannelSender] for RPC server messages. +pub(crate) type RpcSender = AsyncBoundedChannelSender<( rpc::ServerMessage, - Option>, + Option>, )>; -/// [mpsc::Receiver] for RPC server messages. -pub(crate) type RpcReceiver = mpsc::Receiver<( +/// [AsyncBoundedChannelReceiver] for RPC server messages. +pub(crate) type RpcReceiver = AsyncBoundedChannelReceiver<( rpc::ServerMessage, - Option>, + Option>, )>; -/// [mpsc::Sender] for sending messages websocket server clients. -pub(crate) type WsSender = mpsc::Sender<( +/// [AsyncBoundedChannelSender] for sending messages websocket server clients. +pub(crate) type WsSender = AsyncBoundedChannelSender<( webserver::Message, - Option>, + Option>, )>; -/// [mpsc::Receiver] for receiving messages from websocket server clients. -pub(crate) type WsReceiver = mpsc::Receiver<( +/// [AsyncBoundedChannelReceiver] for receiving messages from websocket server clients. +pub(crate) type WsReceiver = AsyncBoundedChannelReceiver<( webserver::Message, - Option>, + Option>, )>; impl ModifiedSet for RunningTaskSet { @@ -116,20 +115,23 @@ pub struct Runner { impl Runner { /// Setup bounded, MPSC channel for top-level RPC communication. pub(crate) fn setup_rpc_channel(capacity: usize) -> (RpcSender, RpcReceiver) { - mpsc::channel(capacity) + AsyncBoundedChannel::with(capacity) } /// Setup bounded, MPSC channel for top-level Worker communication. pub(crate) fn setup_worker_channel( capacity: usize, - ) -> (mpsc::Sender, mpsc::Receiver) { - mpsc::channel(capacity) + ) -> ( + AsyncBoundedChannelSender, + AsyncBoundedChannelReceiver, + ) { + AsyncBoundedChannel::with(capacity) } /// MPSC channel for sending and receiving messages through to/from /// websocket server clients. pub(crate) fn setup_ws_mpsc_channel(capacity: usize) -> (WsSender, WsReceiver) { - mpsc::channel(capacity) + AsyncBoundedChannel::with(capacity) } /// Initialize and start the Homestar [Runner] / runtime. @@ -220,7 +222,7 @@ impl Runner { .runtime .block_on(crate::metrics::start(self.settings.node.network()))?; - let (mut ws_receiver, ws_hdl) = { + let (ws_receiver, ws_hdl) = { let (mpsc_ws_tx, mpsc_ws_rx) = Self::setup_ws_mpsc_channel(message_buffer_len); let ws_hdl = self .runtime @@ -228,9 +230,8 @@ impl Runner { (mpsc_ws_rx, ws_hdl) }; - let (rpc_tx, mut rpc_rx) = Self::setup_rpc_channel(message_buffer_len); - let (runner_worker_tx, mut runner_worker_rx) = - Self::setup_worker_channel(message_buffer_len); + let (rpc_tx, rpc_rx) = Self::setup_rpc_channel(message_buffer_len); + let (runner_worker_tx, runner_worker_rx) = Self::setup_worker_channel(message_buffer_len); let shutdown_timeout = self.settings.node.shutdown_timeout; let rpc_server = rpc::Server::new(self.settings.node.network(), rpc_tx.into()); @@ -241,9 +242,8 @@ impl Runner { let mut gc_interval = tokio::time::interval(self.settings.node.gc_interval); loop { select! { - biased; // Handle RPC messages. - Some((rpc_message, Some(oneshot_tx))) = rpc_rx.recv() => { + Ok((rpc_message, Some(oneshot_tx))) = rpc_rx.recv_async() => { let now = time::Instant::now(); let handle = self.handle_command_message( rpc_message, @@ -271,7 +271,7 @@ impl Runner { _ => {} } } - Some(msg) = ws_receiver.recv() => { + Ok(msg) = ws_receiver.recv_async() => { println!("ws message: {:?}", msg); match msg { (webserver::Message::RunWorkflow((name, workflow)), Some(oneshot_tx)) => { @@ -305,7 +305,7 @@ impl Runner { } // Handle messages from the worker. - Some(msg) = runner_worker_rx.recv() => { + Ok(msg) = runner_worker_rx.recv_async() => { match msg { WorkerMessage::Dropped(cid) => { let _ = self.abort_worker(cid); @@ -358,7 +358,7 @@ impl Runner { Ok(()) } - /// [mpsc::Sender] of the event-handler. + /// [AsyncBoundedChannelSender] of the event-handler. /// /// [EventHandler]: crate::EventHandler pub(crate) fn event_sender(&self) -> Arc> { @@ -512,20 +512,22 @@ impl Runner { rpc_sender: Arc>, ws_hdl: ServerHandle, ) -> Result<()> { - let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let _ = rpc_sender.try_send(rpc::ServerMessage::GracefulShutdown(shutdown_sender)); - let _ = shutdown_receiver.await; + let (shutdown_sender, shutdown_receiver) = AsyncBoundedChannel::oneshot(); + let _ = rpc_sender + .send_async(rpc::ServerMessage::GracefulShutdown(shutdown_sender)) + .await; + let _ = shutdown_receiver; info!("shutting down webserver"); let _ = ws_hdl.stop(); ws_hdl.clone().stopped().await; - let (shutdown_sender, shutdown_receiver) = oneshot::channel(); + let (shutdown_sender, shutdown_receiver) = AsyncBoundedChannel::oneshot(); let _ = self .event_sender .send_async(Event::Shutdown(shutdown_sender)) .await; - let _ = shutdown_receiver.await; + let _ = shutdown_receiver; // abort all workers self.abort_workers(); @@ -584,7 +586,7 @@ impl Runner { workflow: Workflow<'static, Arg>, workflow_settings: workflow::Settings, name: Option, - runner_sender: mpsc::Sender, + runner_sender: AsyncBoundedChannelSender, db: impl Database + 'static, ) -> Result { let worker = { @@ -669,7 +671,7 @@ struct WorkflowData { #[derive(Debug)] struct Channels { rpc: Arc>, - runner: mpsc::Sender, + runner: AsyncBoundedChannelSender, } #[cfg(test)] diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 876d3bf1..85236d30 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -278,7 +278,7 @@ impl Default for Network { webserver_port: 1337, webserver_timeout: Duration::new(120, 0), websocket_capacity: 1024, - websocket_receiver_timeout: Duration::from_millis(1000), + websocket_receiver_timeout: Duration::from_millis(30_000), workflow_quorum: 3, keypair_config: PubkeyConfig::Random, node_addresses: Vec::new(), diff --git a/homestar-runtime/src/tasks/fetch.rs b/homestar-runtime/src/tasks/fetch.rs index 6b1deecc..bf99f0be 100644 --- a/homestar-runtime/src/tasks/fetch.rs +++ b/homestar-runtime/src/tasks/fetch.rs @@ -13,12 +13,14 @@ use std::sync::Arc; pub(crate) struct Fetch; -#[cfg(test)] -const WASM_CID: &str = "bafkreihxcyjgyrz437ewzi7md55uqt2zf6yr3zn7xrfi4orc34xdc5jgrm"; +#[cfg(any(test, feature = "test-utils"))] +const WASM_CID: &str = "bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"; +#[cfg(any(test, feature = "test-utils"))] +const CAT_CID: &str = "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm"; impl Fetch { /// Gather resources from IPFS or elsewhere, leveraging an exponential backoff. - #[cfg(all(feature = "ipfs", not(test)))] + #[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] #[cfg_attr(docsrs, doc(cfg(feature = "ipfs")))] pub(crate) async fn get_resources( resources: FnvHashSet, @@ -51,7 +53,7 @@ impl Fetch { } /// Gather resources via URLs, leveraging an exponential backoff. - #[cfg(all(not(feature = "ipfs"), not(test)))] + #[cfg(all(not(feature = "ipfs"), not(test), not(feature = "test-utils")))] #[allow(dead_code)] pub(crate) async fn get_resources( _resources: FnvHashSet, @@ -60,7 +62,7 @@ impl Fetch { Ok(IndexMap::default()) } - #[cfg(all(not(feature = "ipfs"), test))] + #[cfg(all(not(feature = "ipfs"), any(test, feature = "test-utils")))] #[doc(hidden)] #[allow(dead_code)] pub(crate) async fn get_resources( @@ -69,20 +71,27 @@ impl Fetch { ) -> Result>> { println!("Running in test mode"); use crate::tasks::FileLoad; - let path = std::path::PathBuf::from(format!( - "{}/../homestar-wasm/fixtures/example_add.wasm", + let wasm_path = std::path::PathBuf::from(format!( + "{}/../homestar-wasm/fixtures/example_test.wasm", env!("CARGO_MANIFEST_DIR") )); - let bytes = crate::tasks::WasmContext::load(path).await.unwrap(); + let img_path = std::path::PathBuf::from(format!( + "{}/../examples/websocket-relay/synthcat.png", + env!("CARGO_MANIFEST_DIR") + )); + + let bytes = crate::tasks::WasmContext::load(wasm_path).await.unwrap(); + let buf = crate::tasks::WasmContext::load(img_path).await.unwrap(); let mut map = IndexMap::default(); map.insert( Resource::Url(url::Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap()), bytes, ); + map.insert(Resource::Cid(libipld::Cid::try_from(CAT_CID).unwrap()), buf); Ok(map) } - #[cfg(all(feature = "ipfs", test))] + #[cfg(all(feature = "ipfs", any(test, feature = "test-utils")))] #[doc(hidden)] #[allow(dead_code)] pub(crate) async fn get_resources( @@ -92,20 +101,27 @@ impl Fetch { ) -> Result>> { println!("Running in test mode"); use crate::tasks::FileLoad; - let path = std::path::PathBuf::from(format!( - "{}/../homestar-wasm/fixtures/example_add.wasm", + let wasm_path = std::path::PathBuf::from(format!( + "{}/../homestar-wasm/fixtures/example_test.wasm", env!("CARGO_MANIFEST_DIR") )); - let bytes = crate::tasks::WasmContext::load(path).await.unwrap(); + let img_path = std::path::PathBuf::from(format!( + "{}/../examples/websocket-relay/synthcat.png", + env!("CARGO_MANIFEST_DIR") + )); + + let bytes = crate::tasks::WasmContext::load(wasm_path).await.unwrap(); + let buf = crate::tasks::WasmContext::load(img_path).await.unwrap(); let mut map = IndexMap::default(); map.insert( Resource::Url(url::Url::parse(format!("ipfs://{WASM_CID}").as_str()).unwrap()), bytes, ); + map.insert(Resource::Cid(libipld::Cid::try_from(CAT_CID).unwrap()), buf); Ok(map) } - #[cfg(all(feature = "ipfs", not(test)))] + #[cfg(all(feature = "ipfs", not(test), not(feature = "test-utils")))] async fn fetch(rsc: Resource, client: IpfsCli) -> Result<(Resource, Result>)> { match rsc { Resource::Url(url) => { diff --git a/homestar-runtime/src/test_utils/event.rs b/homestar-runtime/src/test_utils/event.rs index ec1c3b11..d1e7ae51 100644 --- a/homestar-runtime/src/test_utils/event.rs +++ b/homestar-runtime/src/test_utils/event.rs @@ -4,9 +4,8 @@ use crate::{ settings, worker::WorkerMessage, }; -use tokio::sync::mpsc; -/// Create an [mpsc::Sender], [mpsc::Receiver] pair for [Event]s. +/// Create an [AsynBoundedChannelSender], [AsyncBoundedChannelReceiver] pair for [Event]s. pub(crate) fn setup_event_channel( settings: settings::Node, ) -> ( @@ -16,9 +15,12 @@ pub(crate) fn setup_event_channel( AsyncBoundedChannel::with(settings.network.events_buffer_len) } -/// Create an [mpsc::Sender], [mpsc::Receiver] pair for worker messages. +/// Create an [AsyncBoundedChannelSender], [AsyncBoundedChannelReceiver] pair for worker messages. pub(crate) fn setup_worker_channel( settings: settings::Node, -) -> (mpsc::Sender, mpsc::Receiver) { - mpsc::channel(settings.network.events_buffer_len) +) -> ( + AsyncBoundedChannelSender, + AsyncBoundedChannelReceiver, +) { + AsyncBoundedChannel::with(settings.network.events_buffer_len) } diff --git a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs index 928e3b49..8334fb5f 100644 --- a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs +++ b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs @@ -102,6 +102,7 @@ pub fn runner_test(_attr: TokenStream, item: TokenStream) -> TokenStream { settings.node.network.rpc_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.network.metrics_port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.db.url = Some(format!("{}.db", #func_name_as_string)); + settings.node.network.websocket_receiver_timeout = std::time::Duration::from_millis(500); let db = crate::test_utils::db::MemoryDb::setup_connection_pool(&settings.node, None).unwrap(); let runner = crate::Runner::start(settings.clone(), db).unwrap(); TestRunner { runner, settings } diff --git a/homestar-runtime/src/test_utils/worker_builder.rs b/homestar-runtime/src/test_utils/worker_builder.rs index 587800cc..5029310c 100644 --- a/homestar-runtime/src/test_utils/worker_builder.rs +++ b/homestar-runtime/src/test_utils/worker_builder.rs @@ -24,7 +24,6 @@ use homestar_core::{ use homestar_wasm::io::Arg; use indexmap::IndexMap; use libipld::Cid; -use tokio::sync::mpsc; /// TODO #[cfg(feature = "ipfs")] @@ -32,7 +31,7 @@ pub(crate) struct WorkerBuilder<'a> { db: MemoryDb, event_sender: AsyncBoundedChannelSender, ipfs: IpfsCli, - runner_sender: mpsc::Sender, + runner_sender: AsyncBoundedChannelSender, name: Option, workflow: Workflow<'a, Arg>, workflow_settings: workflow::Settings, @@ -43,7 +42,7 @@ pub(crate) struct WorkerBuilder<'a> { pub(crate) struct WorkerBuilder<'a> { db: MemoryDb, event_sender: AsyncBoundedChannelSender, - runner_sender: mpsc::Sender, + runner_sender: AsyncBoundedChannelSender, name: Option, workflow: Workflow<'a, Arg>, workflow_settings: workflow::Settings, @@ -174,7 +173,7 @@ impl<'a> WorkerBuilder<'a> { self } - /// Build a [Worker] with a specific Event [mpsc::Sender]. + /// Build a [Worker] with a specific Event [AsyncBoundedChannelSender]. #[allow(dead_code)] pub(crate) fn with_event_sender( mut self, diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 50c0e52f..0f4b26e7 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -45,7 +45,7 @@ use indexmap::IndexMap; use libipld::{Cid, Ipld}; use std::{collections::BTreeMap, sync::Arc}; use tokio::{ - sync::{mpsc, RwLock}, + sync::RwLock, task::JoinSet, time::{self, Instant}, }; @@ -66,7 +66,7 @@ pub(crate) enum WorkerMessage { pub(crate) struct Worker<'a, DB: Database> { pub(crate) graph: Arc>, pub(crate) event_sender: Arc>, - pub(crate) runner_sender: mpsc::Sender, + pub(crate) runner_sender: AsyncBoundedChannelSender, pub(crate) db: DB, pub(crate) workflow_name: FastStr, pub(crate) workflow_info: Arc, @@ -88,7 +88,7 @@ where // Name would be runner specific, separated from core workflow spec. name: Option, event_sender: Arc>, - runner_sender: mpsc::Sender, + runner_sender: AsyncBoundedChannelSender, db: DB, ) -> Result> { let p2p_timeout = settings.p2p_timeout; diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index 2e778d6c..cba18188 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -31,7 +31,7 @@ impl Default for Settings { impl Default for Settings { fn default() -> Self { Self { - retries: 3, + retries: 0, retry_max_delay: Duration::new(1, 0), retry_initial_delay: Duration::from_millis(50), p2p_timeout: Duration::from_millis(10), diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index de6659d9..72005dca 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -1,14 +1,15 @@ #[cfg(not(windows))] use crate::utils::kill_homestar_daemon; -use crate::utils::{kill_homestar, stop_homestar, BIN_NAME}; +use crate::utils::{ + kill_homestar, remove_db, stop_homestar, wait_for_socket_connection, + wait_for_socket_connection_v6, BIN_NAME, +}; use anyhow::Result; use assert_cmd::prelude::*; use once_cell::sync::Lazy; use predicates::prelude::*; -use retry::{delay::Exponential, retry}; use serial_test::file_serial; use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, TcpStream}, path::PathBuf, process::{Command, Stdio}, }; @@ -119,7 +120,7 @@ fn test_server_serial() -> Result<()> { .assert() .failure(); - let mut homestar_proc = Command::new(BIN.as_os_str()) + let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") .arg("tests/fixtures/test_v6.toml") @@ -129,13 +130,8 @@ fn test_server_serial() -> Result<()> { .spawn() .unwrap(); - let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 9837); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - homestar_proc.kill().unwrap(); + if wait_for_socket_connection_v6(9837, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); panic!("Homestar server/runtime failed to start in time"); } @@ -174,25 +170,22 @@ fn test_server_serial() -> Result<()> { #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { + const DB: &str = "homestar_test_cli_test_workflow_run_serial.db"; + let _ = stop_homestar(); - let mut homestar_proc = Command::new(BIN.as_os_str()) + let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") .arg("tests/fixtures/test_workflow1.toml") .arg("--db") - .arg("homestar.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); - let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 9840); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - homestar_proc.kill().unwrap(); + if wait_for_socket_connection_v6(9840, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); panic!("Homestar server/runtime failed to start in time"); } @@ -205,7 +198,7 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm", + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); @@ -220,15 +213,15 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm", + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); - let _ = kill_homestar(homestar_proc, None); let _ = stop_homestar(); + remove_db(DB); Ok(()) } @@ -242,19 +235,14 @@ fn test_daemon_serial() -> Result<()> { Command::new(BIN.as_os_str()) .arg("start") .arg("-c") - .arg("tests/fixtures/test_v4_alt.toml") + .arg("tests/fixtures/test_v4.toml") .arg("-d") .env("DATABASE_URL", "homestar.db") .stdout(Stdio::piped()) .assert() .success(); - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9836); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { + if wait_for_socket_connection(9000, 1000).is_err() { panic!("Homestar server/runtime failed to start in time"); } @@ -263,7 +251,7 @@ fn test_daemon_serial() -> Result<()> { .arg("--host") .arg("127.0.0.1") .arg("-p") - .arg("9836") + .arg("9000") .assert() .success() .stdout(predicate::str::contains("127.0.0.1")) @@ -277,51 +265,10 @@ fn test_daemon_serial() -> Result<()> { #[test] #[file_serial] -#[cfg(windows)] -fn test_signal_kill_serial() -> Result<()> { - let _ = stop_homestar(); - - let homestar_proc = Command::new(BIN.as_os_str()) - .arg("start") - .arg("--db") - .arg("homestar.db") - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - - let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 3030); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - panic!("Homestar server/runtime failed to start in time"); - } - - Command::new(BIN.as_os_str()) - .arg("ping") - .assert() - .success() - .stdout(predicate::str::contains("::1")) - .stdout(predicate::str::contains("pong")); - - let _ = Command::new(BIN.as_os_str()).arg("stop").output(); - let _ = kill_homestar(homestar_proc, None); - - Command::new(BIN.as_os_str()).arg("ping").assert().failure(); - - let _ = stop_homestar(); - - Ok(()) -} - -#[test] -#[file_serial] -#[cfg(windows)] fn test_server_v4_serial() -> Result<()> { let _ = stop_homestar(); - let mut homestar_proc = Command::new(BIN.as_os_str()) + let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") .arg("tests/fixtures/test_v4.toml") @@ -331,13 +278,8 @@ fn test_server_v4_serial() -> Result<()> { .spawn() .unwrap(); - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9835); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - homestar_proc.kill().unwrap(); + if wait_for_socket_connection(9000, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); panic!("Homestar server/runtime failed to start in time"); } @@ -346,14 +288,13 @@ fn test_server_v4_serial() -> Result<()> { .arg("--host") .arg("127.0.0.1") .arg("-p") - .arg("9835") + .arg("9000") .assert() .success() .stdout(predicate::str::contains("127.0.0.1")) .stdout(predicate::str::contains("pong")); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); - let _ = kill_homestar(homestar_proc, None); let _ = stop_homestar(); diff --git a/homestar-runtime/tests/fixtures/test-workflow-add-one.json b/homestar-runtime/tests/fixtures/test-workflow-add-one.json index 8ddbdf92..05aa7c3c 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-add-one.json +++ b/homestar-runtime/tests/fixtures/test-workflow-add-one.json @@ -17,7 +17,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } }, { @@ -33,7 +33,7 @@ "args": [ { "await/ok": { - "/": "bafyrmigpbec3fpc64hxkehxkszctb5f5roylk5ej2t2qgfylf64gpdn54m" + "/": "bafyrmid5morwzj3lz436g44usvu35xcfyn4ommfe4ozulmnrsohybdez3a" } } ], @@ -41,7 +41,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafkreidgxzucs63ums2yhzs4unin5a3vjemapc373rypon63kdp5xoqlzm" + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" } } ] diff --git a/homestar-runtime/tests/fixtures/test_gossip1.toml b/homestar-runtime/tests/fixtures/test_gossip1.toml new file mode 100644 index 00000000..5c2b725c --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_gossip1.toml @@ -0,0 +1,20 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5550 + +[node] + +[node.network] +metrics_port = 3990 +rpc_port = 9790 +webserver_port = 7990 +listen_address = "/ip4/127.0.0.1/tcp/7020" +node_addresses = [ + "/ip4/127.0.0.1/tcp/7021/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", +] +enable_mdns = false +enable_rendezvous_client = false + +# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } diff --git a/homestar-runtime/tests/fixtures/test_gossip2.toml b/homestar-runtime/tests/fixtures/test_gossip2.toml new file mode 100644 index 00000000..0ac179a2 --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_gossip2.toml @@ -0,0 +1,20 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5551 + +[node] + +[node.network] +metrics_port = 3991 +rpc_port = 9791 +webserver_port = 7991 +listen_address = "/ip4/127.0.0.1/tcp/7021" +node_addresses = [ + "/ip4/127.0.0.1/tcp/7020/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", +] +enable_mdns = false +enable_rendezvous_client = false + +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } diff --git a/homestar-runtime/tests/fixtures/test_v4.toml b/homestar-runtime/tests/fixtures/test_v4.toml index b6e10d03..ab9ce320 100644 --- a/homestar-runtime/tests/fixtures/test_v4.toml +++ b/homestar-runtime/tests/fixtures/test_v4.toml @@ -7,6 +7,6 @@ console_subscriber_port = 5590 [node.network] metrics_port = 4045 events_buffer_len = 1000 -rpc_port = 9835 +rpc_port = 9000 rpc_host = "127.0.0.1" webserver_port = 8031 diff --git a/homestar-runtime/tests/fixtures/test_windows_v4.toml b/homestar-runtime/tests/fixtures/test_windows_v4.toml new file mode 100644 index 00000000..a50408f2 --- /dev/null +++ b/homestar-runtime/tests/fixtures/test_windows_v4.toml @@ -0,0 +1,12 @@ +[monitoring] +process_collector_interval = 500 +console_subscriber_port = 5591 + +[node] + +[node.network] +metrics_port = 4046 +events_buffer_len = 1000 +rpc_port = 9001 +rpc_host = "127.0.0.1" +webserver_port = 8032 diff --git a/homestar-runtime/tests/metrics.rs b/homestar-runtime/tests/metrics.rs index e764b888..0288bf9f 100644 --- a/homestar-runtime/tests/metrics.rs +++ b/homestar-runtime/tests/metrics.rs @@ -1,11 +1,10 @@ -use crate::utils::{kill_homestar, stop_homestar, BIN_NAME}; +use crate::utils::{kill_homestar, stop_homestar, wait_for_socket_connection, BIN_NAME}; use anyhow::Result; use once_cell::sync::Lazy; use reqwest::StatusCode; use retry::{delay::Exponential, retry, OperationResult}; use serial_test::file_serial; use std::{ - net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, path::PathBuf, process::{Command, Stdio}, }; @@ -54,13 +53,8 @@ fn test_metrics_serial() -> Result<()> { .spawn() .unwrap(); - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4020); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - homestar_proc.kill().unwrap(); + if wait_for_socket_connection(4020, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); panic!("Homestar server/runtime failed to start in time"); } diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index 9a148fdf..72bd6a8e 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -1,5 +1,6 @@ use crate::utils::{ - check_lines_for, count_lines_where, kill_homestar, retrieve_output, stop_homestar, BIN_NAME, + check_lines_for, count_lines_where, kill_homestar, retrieve_output, stop_homestar, + wait_for_socket_connection, wait_for_socket_connection_v6, BIN_NAME, }; use anyhow::Result; use once_cell::sync::Lazy; @@ -11,6 +12,8 @@ use std::{ time::Duration, }; +#[cfg(feature = "websocket-notify")] +mod gossip; #[cfg(feature = "websocket-notify")] mod notification; @@ -32,6 +35,11 @@ fn test_libp2p_generates_peer_id_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); + panic!("Homestar server/runtime failed to start in time"); + } + let dead_proc = kill_homestar(homestar_proc, None); let stdout = retrieve_output(dead_proc); let logs_expected = check_lines_for( @@ -62,6 +70,11 @@ fn test_libp2p_listens_on_address_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); + panic!("Homestar server/runtime failed to start in time"); + } + let dead_proc = kill_homestar(homestar_proc, None); let stdout = retrieve_output(dead_proc); let logs_expected = check_lines_for( @@ -93,6 +106,11 @@ fn test_rpc_listens_on_address_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); + panic!("Homestar server/runtime failed to start in time"); + } + let dead_proc = kill_homestar(homestar_proc, None); let stdout = retrieve_output(dead_proc); let logs_expected = check_lines_for(stdout, vec!["RPC server listening", "[::1]:9820"]); @@ -117,6 +135,11 @@ fn test_websocket_listens_on_address_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); + panic!("Homestar server/runtime failed to start in time"); + } + let dead_proc = kill_homestar(homestar_proc, None); let stdout = retrieve_output(dead_proc); let logs_expected = check_lines_for(stdout, vec!["webserver listening", "127.0.0.1:8020"]); @@ -147,6 +170,11 @@ fn test_libp2p_connect_known_peers_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + let homestar_proc2 = Command::new(BIN.as_os_str()) .env( "RUST_LOG", @@ -161,6 +189,11 @@ fn test_libp2p_connect_known_peers_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9821, 1000).is_err() { + let _ = kill_homestar(homestar_proc2, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for five seconds then kill proceses. let dead_proc1 = kill_homestar(homestar_proc1, Some(Duration::from_secs(5))); let dead_proc2 = kill_homestar(homestar_proc2, Some(Duration::from_secs(5))); @@ -255,6 +288,11 @@ fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9800, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + let homestar_proc2 = Command::new(BIN.as_os_str()) .env( "RUST_LOG", @@ -269,6 +307,11 @@ fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9801, 1000).is_err() { + let _ = kill_homestar(homestar_proc2, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for seven seconds then kill processes. let dead_proc1 = kill_homestar(homestar_proc1, Some(Duration::from_secs(7))); let dead_proc2 = kill_homestar(homestar_proc2, Some(Duration::from_secs(7))); @@ -362,6 +405,11 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8024, 1000).is_err() { + let _ = kill_homestar(rendezvous_server, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Start a peer that will register with the rendezvous server let rendezvous_client1 = Command::new(BIN.as_os_str()) .env( @@ -377,6 +425,11 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8026, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Wait for registration to complete // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep thread::sleep(Duration::from_secs(2)); @@ -396,6 +449,11 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8027, 1000).is_err() { + let _ = kill_homestar(rendezvous_client2, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for five seconds then kill proceses. let dead_server = kill_homestar(rendezvous_server, Some(Duration::from_secs(5))); let _ = kill_homestar(rendezvous_client1, Some(Duration::from_secs(5))); @@ -481,6 +539,11 @@ fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8000, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + let homestar_proc2 = Command::new(BIN.as_os_str()) .env( "RUST_LOG", @@ -495,6 +558,11 @@ fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8001, 1000).is_err() { + let _ = kill_homestar(homestar_proc2, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Kill node two after seven seconds. let _ = kill_homestar(homestar_proc2, Some(Duration::from_secs(7))); @@ -549,6 +617,11 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9820, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + let homestar_proc2 = Command::new(BIN.as_os_str()) .env( "RUST_LOG", @@ -563,6 +636,11 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9821, 1000).is_err() { + let _ = kill_homestar(homestar_proc2, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Kill node two after seven seconds. let _ = kill_homestar(homestar_proc2, Some(Duration::from_secs(7))); @@ -616,6 +694,11 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8024, 1000).is_err() { + let _ = kill_homestar(rendezvous_server, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Start a peer that will register with the rendezvous server let rendezvous_client1 = Command::new(BIN.as_os_str()) .env( @@ -631,6 +714,11 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8026, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Wait for registration to complete. // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep. thread::sleep(Duration::from_secs(2)); @@ -650,6 +738,11 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8027, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Kill server and client one after five seconds let _ = kill_homestar(rendezvous_server, Some(Duration::from_secs(5))); let _ = kill_homestar(rendezvous_client1, Some(Duration::from_secs(5))); @@ -704,6 +797,11 @@ fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8024, 1000).is_err() { + let _ = kill_homestar(rendezvous_server, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Start a peer that will renew registrations with the rendezvous server once per second let rendezvous_client1 = Command::new(BIN.as_os_str()) .env( @@ -719,6 +817,11 @@ fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8028, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for five seconds then kill proceses. let dead_server = kill_homestar(rendezvous_server, Some(Duration::from_secs(5))); let dead_client = kill_homestar(rendezvous_client1, Some(Duration::from_secs(5))); @@ -771,6 +874,11 @@ fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8024, 1000).is_err() { + let _ = kill_homestar(rendezvous_server, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Start a peer that will discover with the rendezvous server once per second let rendezvous_client1 = Command::new(BIN.as_os_str()) .env( @@ -786,6 +894,11 @@ fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9829, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for five seconds then kill proceses. let dead_server = kill_homestar(rendezvous_server, Some(Duration::from_secs(5))); let dead_client = kill_homestar(rendezvous_client1, Some(Duration::from_secs(5))); @@ -838,6 +951,11 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8024, 1000).is_err() { + let _ = kill_homestar(rendezvous_server, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Start a peer that will renew registrations with the rendezvous server every five seconds let rendezvous_client1 = Command::new(BIN.as_os_str()) .env( @@ -853,6 +971,11 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection_v6(9830, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Wait for registration to complete. // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep. thread::sleep(Duration::from_secs(2)); @@ -875,6 +998,11 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8027, 1000).is_err() { + let _ = kill_homestar(rendezvous_client1, None); + panic!("Homestar server/runtime failed to start in time"); + } + // Collect logs for seven seconds then kill proceses. let dead_server = kill_homestar(rendezvous_server, Some(Duration::from_secs(7))); let _ = kill_homestar(rendezvous_client1, Some(Duration::from_secs(7))); diff --git a/homestar-runtime/tests/network/gossip.rs b/homestar-runtime/tests/network/gossip.rs new file mode 100644 index 00000000..337f9627 --- /dev/null +++ b/homestar-runtime/tests/network/gossip.rs @@ -0,0 +1,213 @@ +use crate::utils::{ + check_lines_for, kill_homestar, remove_db, retrieve_output, stop_homestar, + wait_for_socket_connection, TimeoutFutureExt, BIN_NAME, +}; +use anyhow::Result; +use homestar_runtime::{db::Database, Db, Settings}; +use itertools::Itertools; +use jsonrpsee::{ + core::client::{Subscription, SubscriptionClientT}, + rpc_params, + ws_client::WsClientBuilder, +}; +use libipld::Cid; +use once_cell::sync::Lazy; +use serial_test::file_serial; +use std::{ + net::Ipv4Addr, + path::PathBuf, + process::{Command, Stdio}, + str::FromStr, + time::Duration, +}; + +static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); +const SUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "subscribe_network_events"; +const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_network_events"; + +#[test] +#[file_serial] +fn test_libp2p_receipt_gossip_serial() -> Result<()> { + const DB1: &str = "homestar_test_libp2p_receipt_gossip_serial1.db"; + const DB2: &str = "homestar_test_libp2p_receipt_gossip_serial2.db"; + let _ = stop_homestar(); + let homestar_proc1 = Command::new(BIN.as_os_str()) + .env( + "RUST_LOG", + "homestar=debug,homestar_runtime=debug,libp2p=debug,libp2p_gossipsub::behaviour=debug", + ) + .arg("start") + .arg("-c") + .arg("tests/fixtures/test_gossip1.toml") + .arg("--db") + .arg(DB1) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let ws_port = 7990; + if wait_for_socket_connection(ws_port, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + + tokio_test::block_on(async { + let ws_url = format!("ws://{}:{}", Ipv4Addr::LOCALHOST, ws_port); + let client = WsClientBuilder::default() + .build(ws_url.clone()) + .await + .unwrap(); + + let mut sub1: Subscription> = client + .subscribe( + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + rpc_params![], + UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + ) + .await + .unwrap(); + + let homestar_proc2 = Command::new(BIN.as_os_str()) + .env( + "RUST_LOG", + "homestar=debug,homestar_runtime=debug,libp2p=debug,libp2p_gossipsub::behaviour=debug", + ) + .arg("start") + .arg("-c") + .arg("tests/fixtures/test_gossip2.toml") + .arg("--db") + .arg(DB2) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let ws_port2 = 7991; + if wait_for_socket_connection(ws_port2, 1000).is_err() { + let _ = kill_homestar(homestar_proc1, None); + panic!("Homestar server/runtime failed to start in time"); + } + + // Poll for connection established message + loop { + if let Ok(msg) = sub1.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + + if json["type"].as_str().unwrap() == "network:connectionEstablished" { + break; + } + } else { + panic!("Node one did not establish a connection with node two in time.") + } + } + + let ws_url2 = format!("ws://{}:{}", Ipv4Addr::LOCALHOST, ws_port2); + let client2 = WsClientBuilder::default() + .build(ws_url2.clone()) + .await + .unwrap(); + + let mut sub2: Subscription> = client2 + .subscribe( + SUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + rpc_params![], + UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT, + ) + .await + .unwrap(); + + // Run test workflow + let _ = Command::new(BIN.as_os_str()) + .arg("run") + .arg("-p") + .arg("9790") + .arg("-w") + .arg("tests/fixtures/test-workflow-add-one.json") + .output(); + + // Poll for published and received receipt messages + let mut published_cids: Vec = vec![]; + let mut received_cids: Vec = vec![]; + loop { + if let Ok(msg) = sub1.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + + if json["type"].as_str().unwrap() == "network:publishedReceiptPubsub" { + published_cids.push( + Cid::from_str(json["data"]["cid"].as_str().unwrap()) + .expect("Unable to parse published receipt CID."), + ); + } + } else { + panic!("Node one did not publish receipt in time.") + } + + if let Ok(msg) = sub2.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + + if json["type"].as_str().unwrap() == "network:receivedReceiptPubsub" { + received_cids.push( + Cid::from_str(json["data"]["cid"].as_str().unwrap()) + .expect("Unable to parse received receipt CID."), + ); + } + } else { + panic!("Node two did not receive receipt in time.") + } + + if published_cids.len() == 2 && received_cids.len() == 2 { + break; + } + } + + // Collect logs then kill proceses. + let dead_proc1 = kill_homestar(homestar_proc1, None); + let dead_proc2 = kill_homestar(homestar_proc2, None); + + // Retrieve logs. + let stdout1 = retrieve_output(dead_proc1); + let stdout2 = retrieve_output(dead_proc2); + + // Check node one published a receipt + let message_published = + check_lines_for(stdout1, vec!["message published on receipts topic"]); + + // Check node two received a receipt from node one + let message_received = check_lines_for( + stdout2, + vec![ + "message received on receipts topic", + "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", + ], + ); + + assert!(message_published); + assert!(message_received); + + let settings = + Settings::load_from_file(PathBuf::from("tests/fixtures/test_gossip2.toml")).unwrap(); + let db = Db::setup_connection_pool(settings.node(), Some(DB2.to_string())) + .expect("Failed to connect to node two database"); + + // Check database for stored receipts + let stored_receipts: Vec<_> = received_cids + .iter() + .map(|cid| { + Db::find_receipt_by_cid(*cid, &mut db.conn().unwrap()).unwrap_or_else(|_| { + panic!("Failed to find receipt with CID {} in database", *cid) + }) + }) + .collect_vec(); + + assert_eq!(stored_receipts.len(), 2) + }); + + remove_db(DB1); + remove_db(DB2); + + let _ = stop_homestar(); + + Ok(()) +} diff --git a/homestar-runtime/tests/network/notification.rs b/homestar-runtime/tests/network/notification.rs index ea2d9b19..a7a460bf 100644 --- a/homestar-runtime/tests/network/notification.rs +++ b/homestar-runtime/tests/network/notification.rs @@ -1,4 +1,4 @@ -use crate::utils::{kill_homestar, stop_homestar, BIN_NAME}; +use crate::utils::{kill_homestar, stop_homestar, wait_for_socket_connection, BIN_NAME}; use anyhow::Result; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, @@ -6,10 +6,9 @@ use jsonrpsee::{ ws_client::WsClientBuilder, }; use once_cell::sync::Lazy; -use retry::{delay::Exponential, retry}; use serial_test::file_serial; use std::{ - net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, + net::Ipv4Addr, path::PathBuf, process::{Command, Stdio}, time::Duration, @@ -39,12 +38,7 @@ fn test_connection_notifications_serial() -> Result<()> { .unwrap(); let ws_port = 8022; - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ws_port); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { + if wait_for_socket_connection(8022, 1000).is_err() { let _ = kill_homestar(homestar_proc1, None); panic!("Homestar server/runtime failed to start in time"); } @@ -84,6 +78,11 @@ fn test_connection_notifications_serial() -> Result<()> { .spawn() .unwrap(); + if wait_for_socket_connection(8023, 1000).is_err() { + let _ = kill_homestar(homestar_proc2, None); + panic!("Homestar server/runtime failed to start in time"); + } + let _ = kill_homestar(homestar_proc2, None); tokio::time::sleep(Duration::from_secs(2)).await; @@ -95,7 +94,7 @@ fn test_connection_notifications_serial() -> Result<()> { .expect("Subscription did not receive a connection established message"); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let typ = json["type"].as_str().unwrap(); - let peer_id = json["data"]["peer_id"].as_str().unwrap(); + let peer_id = json["data"]["peerId"].as_str().unwrap(); assert_eq!(typ, "network:connectionEstablished"); assert_eq!( @@ -111,7 +110,7 @@ fn test_connection_notifications_serial() -> Result<()> { .expect("Subscription did not receive a connection closed message"); let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); let typ = json["type"].as_str().unwrap(); - let peer_id = json["data"]["peer_id"].as_str().unwrap(); + let peer_id = json["data"]["peerId"].as_str().unwrap(); assert_eq!(typ, "network:connectionClosed"); assert_eq!( diff --git a/homestar-runtime/tests/utils.rs b/homestar-runtime/tests/utils.rs index 2d763bca..d04762ab 100644 --- a/homestar-runtime/tests/utils.rs +++ b/homestar-runtime/tests/utils.rs @@ -9,11 +9,13 @@ use nix::{ }; use once_cell::sync::Lazy; use predicates::prelude::*; -use retry::{delay::Fixed, retry}; -#[cfg(feature = "ipfs")] -use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}; +#[cfg(not(windows))] +use retry::delay::Fixed; +use retry::{delay::Exponential, retry}; use std::{ + fs, future::Future, + net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, TcpStream}, path::PathBuf, process::{Child, Command, Stdio}, time::Duration, @@ -26,36 +28,9 @@ use wait_timeout::ChildExt; /// Binary name, which is different than the crate name. pub(crate) const BIN_NAME: &str = "homestar"; -/// TODO -pub(crate) const IPFS: &str = "ipfs"; static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); -/// Start-up IPFS daemon for tests with the feature turned-on. -#[allow(dead_code)] -#[cfg(feature = "ipfs")] -pub(crate) fn startup_ipfs(ext: &str) -> Result<()> { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!(".ipfs{}", ext)); - println!("starting ipfs daemon...{}", path.to_str().unwrap()); - let mut ipfs_daemon = Command::new(IPFS) - .args(["--offline", "daemon", "--init"]) - .stdout(Stdio::piped()) - .spawn()?; - - // wait for ipfs daemon to start by testing for a connection - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001); - let result = retry(Fixed::from_millis(500), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if let Err(err) = result { - ipfs_daemon.kill().unwrap(); - panic!("`ipfs daemon` failed to start: {:?}", err); - } else { - Ok(()) - } -} - /// Stop the Homestar server/binary. pub(crate) fn stop_homestar() -> Result<()> { Command::new(BIN.as_os_str()) @@ -68,28 +43,6 @@ pub(crate) fn stop_homestar() -> Result<()> { Ok(()) } -/// Stop the IPFS binary. -pub(crate) fn stop_ipfs() -> Result<()> { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".ipfs"); - Command::new(IPFS) - .args(["--repo-dir", path.to_str().unwrap(), "shutdown"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .context("failed to stop IPFS daemon")?; - rm_rf::ensure_removed(path).unwrap(); - - Ok(()) -} - -/// Stop all binaries. -#[allow(dead_code)] -pub(crate) fn stop_all_bins() -> Result<()> { - let _ = stop_ipfs(); - let _ = stop_homestar(); - Ok(()) -} - /// Retrieve process output. pub(crate) fn retrieve_output(proc: Child) -> String { let output = proc.wait_with_output().expect("failed to wait on child"); @@ -121,10 +74,9 @@ pub(crate) fn extract_timestamps_where( output: String, predicates: Vec<&str>, ) -> Vec> { - output.split("\n").fold(vec![], |mut timestamps, line| { + output.split('\n').fold(vec![], |mut timestamps, line| { if line_contains(line, &predicates) { - match extract_label(&line, "ts").and_then(|val| DateTime::parse_from_rfc3339(val).ok()) - { + match extract_label(line, "ts").and_then(|val| DateTime::parse_from_rfc3339(val).ok()) { Some(datetime) => { timestamps.push(datetime); timestamps @@ -141,11 +93,11 @@ pub(crate) fn extract_timestamps_where( } /// Check process output line for all predicates -fn line_contains(line: &str, predicates: &Vec<&str>) -> bool { +fn line_contains(line: &str, predicates: &[&str]) -> bool { predicates .iter() .map(|pred| predicate::str::contains(*pred).eval(line)) - .fold(true, |acc, curr| acc && curr) + .all(|curr| curr) } /// Extract label value from process output line @@ -221,6 +173,33 @@ pub(crate) fn kill_homestar_daemon() -> Result<()> { Ok(()) } +/// Remove sqlite database and associated temporary files +pub(crate) fn remove_db(name: &str) { + let _ = fs::remove_file(name); + let _ = fs::remove_file(format!("{name}-shm")); + let _ = fs::remove_file(format!("{name}-wal")); +} + +/// Wait for socket connection or timeout +pub(crate) fn wait_for_socket_connection(port: u16, exp_retry_base: u64) -> Result<(), ()> { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let result = retry(Exponential::from_millis(exp_retry_base).take(10), || { + TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) + }); + + result.map_or_else(|_| Err(()), |_| Ok(())) +} + +/// Wait for socket connection or timeout (ipv6) +pub(crate) fn wait_for_socket_connection_v6(port: u16, exp_retry_base: u64) -> Result<(), ()> { + let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), port); + let result = retry(Exponential::from_millis(exp_retry_base).take(10), || { + TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) + }); + + result.map_or_else(|_| Err(()), |_| Ok(())) +} + /// Helper extension trait which allows to limit execution time for the futures. /// It is helpful in tests to ensure that no future will ever get stuck forever. pub(crate) trait TimeoutFutureExt: Future + Sized { diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 98606003..87e0f449 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "ipfs")] -use crate::utils::startup_ipfs; -use crate::utils::{kill_homestar, stop_all_bins, TimeoutFutureExt, BIN_NAME, IPFS}; +use crate::utils::{ + kill_homestar, remove_db, stop_homestar, wait_for_socket_connection, TimeoutFutureExt, BIN_NAME, +}; use anyhow::Result; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, @@ -8,60 +8,27 @@ use jsonrpsee::{ ws_client::WsClientBuilder, }; use once_cell::sync::Lazy; -use retry::{delay::Exponential, retry}; use serial_test::file_serial; use std::{ fs, - net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpStream}, + net::Ipv4Addr, path::PathBuf, process::{Command, Stdio}, + time::Duration, }; static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; -#[cfg(feature = "test-utils")] #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { const DB: &str = "ws_homestar_test_workflow_run.db"; - const IPFS_EXT: &str = "ws_homestar_test_workflow_run"; - - let _ = stop_all_bins(); - - #[cfg(feature = "ipfs")] - let _ = startup_ipfs(IPFS_EXT); - - let add_image_args = vec![ - "add", - "--cid-version", - "1", - "../examples/websocket-relay/synthcat.png", - ]; - - let add_wasm_args = vec![ - "add", - "--cid-version", - "1", - "../examples/websocket-relay/example_test.wasm", - ]; - + let _ = stop_homestar(); let _ = fs::remove_file(DB); - let _ipfs_add_img = Command::new(IPFS) - .args(add_image_args) - .stdout(Stdio::piped()) - .output() - .expect("`ipfs add` of synthcat.png"); - - let _ipfs_add_wasm = Command::new(IPFS) - .args(add_wasm_args) - .stdout(Stdio::piped()) - .output() - .expect("`ipfs add` of wasm mod"); - - let mut homestar_proc = Command::new(BIN.as_os_str()) + let homestar_proc = Command::new(BIN.as_os_str()) .arg("start") .arg("-c") .arg("tests/fixtures/test_workflow2.toml") @@ -72,13 +39,8 @@ fn test_workflow_run_serial() -> Result<()> { .unwrap(); let ws_port = 8061; - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ws_port); - let result = retry(Exponential::from_millis(1000).take(10), || { - TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both)) - }); - - if result.is_err() { - homestar_proc.kill().unwrap(); + if wait_for_socket_connection(ws_port, 1000).is_err() { + let _ = kill_homestar(homestar_proc, None); panic!("Homestar server/runtime failed to start in time"); } @@ -107,35 +69,24 @@ fn test_workflow_run_serial() -> Result<()> { .unwrap(); // we have 3 operations - let one = sub1 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap(); - let json: serde_json::Value = serde_json::from_slice(&one.unwrap().unwrap()).unwrap(); - let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); - assert_eq!(check, &expected); - - let two = sub1 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap(); - let json: serde_json::Value = serde_json::from_slice(&two.unwrap().unwrap()).unwrap(); - let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); - assert_eq!(check, &expected); - - let three = sub1 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap(); - let json: serde_json::Value = serde_json::from_slice(&three.unwrap().unwrap()).unwrap(); - let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); - assert_eq!(check, &expected); + let mut received_cids = 0; + loop { + if let Ok(msg) = sub1.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + received_cids += 1; + } else { + panic!("Node one did not publish receipt in time.") + } + + if received_cids == 3 { + received_cids = 0; + break; + } + } // separate subscription, only 3 events too let mut sub2: Subscription> = client1 @@ -147,28 +98,23 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); - let msg = sub2 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap(); - let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); - let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); - assert_eq!(check, &expected); - - assert!(sub2 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); - assert!(sub2 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); + loop { + if let Ok(msg) = sub2.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + received_cids += 1; + } else { + panic!("Node one did not publish receipt in time.") + } + + if received_cids == 3 { + received_cids = 0; + break; + } + } let client2 = WsClientBuilder::default().build(ws_url).await.unwrap(); let mut sub3: Subscription> = client2 @@ -182,28 +128,33 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(std::time::Duration::from_millis(25000)) + .with_timeout(Duration::from_secs(30)) .await .is_err(); - assert!(sub3 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); - assert!(sub2 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); - assert!(sub2 + loop { + if let Ok(msg) = sub3.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + received_cids += 1; + } else { + panic!("Node one did not publish receipt in time.") + } + + if received_cids == 3 { + received_cids = 0; + break; + } + } + + let _ = sub3 .next() - .with_timeout(std::time::Duration::from_millis(25000)) + .with_timeout(Duration::from_secs(10)) .await - .unwrap() - .is_some()); + .is_err(); let another_run_str = format!(r#"{{"name": "another_test","workflow": {}}}"#, json_string); let another_run: serde_json::Value = serde_json::from_str(&another_run_str).unwrap(); @@ -216,35 +167,28 @@ fn test_workflow_run_serial() -> Result<()> { .await .unwrap(); - let _ = sub3 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .is_err(); - assert!(sub4 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); - assert!(sub4 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); - assert!(sub4 - .next() - .with_timeout(std::time::Duration::from_millis(25000)) - .await - .unwrap() - .is_some()); + loop { + if let Ok(msg) = sub4.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + let check = json.get("metadata").unwrap(); + let expected = serde_json::json!({"name": "another_test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + assert_eq!(check, &expected); + received_cids += 1; + } else { + panic!("Node one did not publish receipt in time.") + } + + if received_cids == 3 { + break; + } + } }); let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); - let _ = stop_all_bins(); - let _ = fs::remove_file(DB); + let _ = stop_homestar(); + remove_db(DB); Ok(()) } From 7ad88b5618d2d48085aa5ed714f20d83d59cfbca Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:01:49 -0800 Subject: [PATCH 25/42] refactor: Fix connection notification test (#424) # Description - [x] Fix the connection notification integration test - [x] Avoid event hooks to IPFS in tests ## Type of change - [x] Refactor (non-breaking change that updates existing functionality) ## Test plan (required) Improved connection notification test included to move away from arbitrary waits to event-driven notification tests. --------- Co-authored-by: Zeeshan Lakhani --- .github/workflows/tests_and_checks.yml | 3 +- homestar-runtime/src/event_handler/event.rs | 32 ++++++--- homestar-runtime/src/network/webserver/rpc.rs | 6 +- .../tests/network/notification.rs | 67 ++++++++----------- homestar-runtime/tests/webserver.rs | 2 +- 5 files changed, 56 insertions(+), 54 deletions(-) diff --git a/.github/workflows/tests_and_checks.yml b/.github/workflows/tests_and_checks.yml index 5bd2b930..a55a611c 100644 --- a/.github/workflows/tests_and_checks.yml +++ b/.github/workflows/tests_and_checks.yml @@ -199,7 +199,8 @@ jobs: needs: changes if: ${{ needs.changes.outputs.rust == 'true' }} env: - RUSTFLAGS: -Ctarget-feature=+crt-static + RUSTFLAGS: -Dwarnings -Ctarget-feature=+crt-static + CARGO_INCREMENTAL: 0 strategy: fail-fast: false matrix: diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index a1988585..de2828f4 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -30,7 +30,7 @@ use libp2p::{ #[cfg(feature = "websocket-notify")] use maplit::btreemap; use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; -#[cfg(feature = "ipfs")] +#[cfg(all(feature = "ipfs", not(feature = "test-utils")))] use tokio::runtime::Handle; use tracing::{error, info, warn}; @@ -119,12 +119,16 @@ pub(crate) enum Event { const RENDEZVOUS_NAMESPACE: &str = "homestar"; +#[allow(unreachable_patterns)] impl Event { async fn handle_info(self, event_handler: &mut EventHandler) -> Result<()> where DB: Database, { match self { + Event::CapturedReceipt(captured) => { + let _ = captured.store_and_notify(event_handler); + } Event::Shutdown(tx) => { info!("event_handler server shutting down"); event_handler.shutdown().await; @@ -530,16 +534,17 @@ where #[cfg(feature = "ipfs")] #[cfg_attr(docsrs, doc(cfg(feature = "ipfs")))] + #[allow(unused_variables)] async fn handle_event(self, event_handler: &mut EventHandler, ipfs: IpfsCli) { match self { Event::CapturedReceipt(captured) => { - let _ = captured - .store_and_notify(event_handler) - .map(|(cid, receipt)| { - // Spawn client call in the background, without awaiting. - let handle = Handle::current(); - let ipfs = ipfs.clone(); - handle.spawn(async move { + if let Ok((cid, receipt)) = captured.store_and_notify(event_handler) { + #[cfg(not(feature = "test-utils"))] + { + // Spawn client call in the background, without awaiting. + let handle = Handle::current(); + let ipfs = ipfs.clone(); + handle.spawn(async move { if let Ok(bytes) = receipt.try_into() { match ipfs.put_receipt_bytes(bytes).await { Ok(put_cid) => { @@ -548,14 +553,19 @@ where debug_assert_eq!(put_cid, cid.to_string()); } Err(err) => { - warn!(error=?err, cid=cid.to_string(), "failed to store IPLD DAG node"); + error!(error=?err, cid=cid.to_string(), "failed to store IPLD DAG node"); } } } else { warn!(cid=cid.to_string(), "failed to convert receipt to bytes"); } - }) - }); + }); + } + #[cfg(feature = "test-utils")] + info!(cid = cid.to_string(), "cid stored on the network"); + } else { + error!("failed to store receipt"); + } } #[cfg(feature = "websocket-notify")] Event::ReplayReceipts(replay) => { diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index 65d04c3a..afc8c647 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -2,7 +2,9 @@ use super::notifier::{self, Header, Notifier, SubscriptionTyp}; #[allow(unused_imports)] use super::{listener, prom::PrometheusData, Message}; -use crate::{channel::AsyncBoundedChannel, runner::WsSender}; +#[cfg(feature = "websocket-notify")] +use crate::channel::AsyncBoundedChannel; +use crate::runner::WsSender; #[cfg(feature = "websocket-notify")] use anyhow::anyhow; use anyhow::Result; @@ -139,7 +141,7 @@ impl JsonRpc { #[cfg(not(test))] module.register_async_method(HEALTH_ENDPOINT, |_, ctx| async move { - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = crate::channel::AsyncBoundedChannel::oneshot(); ctx.runner_sender .send_async((Message::GetNodeInfo, Some(tx))) .await diff --git a/homestar-runtime/tests/network/notification.rs b/homestar-runtime/tests/network/notification.rs index a7a460bf..41e6263b 100644 --- a/homestar-runtime/tests/network/notification.rs +++ b/homestar-runtime/tests/network/notification.rs @@ -1,4 +1,6 @@ -use crate::utils::{kill_homestar, stop_homestar, wait_for_socket_connection, BIN_NAME}; +use crate::utils::{ + kill_homestar, stop_homestar, wait_for_socket_connection, TimeoutFutureExt, BIN_NAME, +}; use anyhow::Result; use jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, @@ -62,8 +64,6 @@ fn test_connection_notifications_serial() -> Result<()> { .await .unwrap(); - tokio::time::sleep(Duration::from_millis(200)).await; - let homestar_proc2 = Command::new(BIN.as_os_str()) .env( "RUST_LOG", @@ -78,49 +78,38 @@ fn test_connection_notifications_serial() -> Result<()> { .spawn() .unwrap(); - if wait_for_socket_connection(8023, 1000).is_err() { - let _ = kill_homestar(homestar_proc2, None); - panic!("Homestar server/runtime failed to start in time"); + // Poll for connection established message + loop { + if let Ok(msg) = sub.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + + if json["type"].as_str().unwrap() == "network:connectionEstablished".to_string() { + break; + } + } else { + panic!("Node one did not receive a connection established message in time.") + } } let _ = kill_homestar(homestar_proc2, None); - tokio::time::sleep(Duration::from_secs(2)).await; - - { - let msg = sub - .next() - .await - .expect("Subscription did not receive a connection established message"); - let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); - let typ = json["type"].as_str().unwrap(); - let peer_id = json["data"]["peerId"].as_str().unwrap(); - - assert_eq!(typ, "network:connectionEstablished"); - assert_eq!( - peer_id, - "16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc" - ); + // Poll for connection closed message + loop { + if let Ok(msg) = sub.next().with_timeout(Duration::from_secs(30)).await { + let json: serde_json::Value = + serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); + + if json["type"].as_str().unwrap() == "network:connectionClosed".to_string() { + break; + } + } else { + panic!("Node one did not receive a connection closed message in time.") + } } - { - let msg = sub - .next() - .await - .expect("Subscription did not receive a connection closed message"); - let json: serde_json::Value = serde_json::from_slice(&msg.unwrap()).unwrap(); - let typ = json["type"].as_str().unwrap(); - let peer_id = json["data"]["peerId"].as_str().unwrap(); - - assert_eq!(typ, "network:connectionClosed"); - assert_eq!( - peer_id, - "16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc" - ); - } + let _ = kill_homestar(homestar_proc1, None); }); - let _ = kill_homestar(homestar_proc1, None); - Ok(()) } diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 87e0f449..1aebda3f 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -128,7 +128,7 @@ fn test_workflow_run_serial() -> Result<()> { let _ = sub2 .next() - .with_timeout(Duration::from_secs(30)) + .with_timeout(Duration::from_secs(10)) .await .is_err(); From b9cfa50608e98e0e63ddf84dc4b9352fcdcf0d8d Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Sat, 11 Nov 2023 12:15:24 -0500 Subject: [PATCH 26/42] chore: additional test --- homestar-core/src/workflow/instruction.rs | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/homestar-core/src/workflow/instruction.rs b/homestar-core/src/workflow/instruction.rs index a0124274..991a4134 100644 --- a/homestar-core/src/workflow/instruction.rs +++ b/homestar-core/src/workflow/instruction.rs @@ -317,7 +317,13 @@ impl<'a, T> DagCbor for Instruction<'a, T> where Ipld: From {} #[cfg(test)] mod test { use super::*; - use crate::{test_utils, Unit}; + use crate::{test_utils, Unit, DAG_CBOR}; + use libipld::{ + cbor::DagCborCodec, + multihash::{Code, MultihashDigest}, + prelude::Codec, + Cid, + }; #[test] fn ipld_roundtrip() { @@ -341,6 +347,28 @@ mod test { assert_eq!(instruction, ipld.try_into().unwrap()) } + #[test] + fn ipld_cid_trials() { + let a_cid = + Cid::try_from("bafyrmiev5j2jzjrqncbfqo6pbraiw7r2p527m4z3bbm6ir3o5kdz2zwcjy").unwrap(); + let ipld = libipld::ipld!({"input": + { + "args": [{"await/ok": a_cid}, "111111"], + "func": "join-strings" + }, + "nnc": "", "op": "wasm/run", + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"}); + + let instruction = Instruction::::try_from(ipld.clone()).unwrap(); + let instr_cid = instruction.to_cid().unwrap(); + + let bytes = DagCborCodec.encode(&ipld).unwrap(); + let hash = Code::Sha3_256.digest(&bytes); + let ipld_to_cid = Cid::new_v1(DAG_CBOR, hash); + + assert_eq!(ipld_to_cid, instr_cid); + } + #[test] fn ser_de() { let (instruction, _bytes) = test_utils::workflow::instruction_with_nonce::(); From 82f3348ecbd7cd23a5cf82e6f0efe83e136e2143 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Sat, 11 Nov 2023 18:03:07 -0500 Subject: [PATCH 27/42] fix: better usage, bin docs --- homestar-runtime/Cargo.toml | 1 + homestar-runtime/src/cli.rs | 58 +++++++++++++++++++------------- homestar-runtime/src/settings.rs | 2 +- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 00686d5e..92b49e87 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -48,6 +48,7 @@ clap = { version = "4.4", default-features = false, features = [ "help", "env", "std", + "usage", ] } config = { version = "0.13", default-features = false, features = ["toml"] } console-subscriber = { version = "0.2", default-features = false, features = [ diff --git a/homestar-runtime/src/cli.rs b/homestar-runtime/src/cli.rs index 0165e725..9032b374 100644 --- a/homestar-runtime/src/cli.rs +++ b/homestar-runtime/src/cli.rs @@ -5,7 +5,7 @@ use crate::{ runner::{file, response}, }; use anyhow::anyhow; -use clap::{Args, Parser}; +use clap::{Args, Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::{ net::{IpAddr, Ipv6Addr, SocketAddr}, @@ -19,19 +19,22 @@ pub use error::Error; pub(crate) mod show; pub(crate) use show::ConsoleTable; +const DEFAULT_SETTINGS_FILE: &str = "./config/settings.toml"; +const DEFAULT_DB_PATH: &str = "homestar.db"; const TMP_DIR: &str = "/tmp"; const HELP_TEMPLATE: &str = "{name} {version} +{about} -USAGE: - {usage} +Usage: {usage} {all-args} "; /// CLI arguments. -#[derive(Parser, Debug)] -#[command(bin_name = "homestar", name = "homestar", author, version, about, long_about = None, help_template = HELP_TEMPLATE)] +#[derive(Debug, Parser)] +#[command(bin_name = "homestar", name = "homestar", author, version, about, + long_about = None, help_template = HELP_TEMPLATE)] pub struct Cli { /// Homestar [Command]. #[clap(subcommand)] @@ -43,17 +46,17 @@ pub struct Cli { /// [Client]: crate::network::rpc::Client #[derive(Debug, Clone, PartialEq, Args, Serialize, Deserialize)] pub struct RpcArgs { - /// RPC Homestar runtime host to ping. + /// Homestar RPC host. #[clap( long = "host", default_value = "::1", value_hint = clap::ValueHint::Hostname )] host: IpAddr, - /// RPC Homestar runtime port to ping. + /// Homestar RPC port. #[clap(short = 'p', long = "port", default_value_t = 3030)] port: u16, - /// RPC Homestar runtime port to ping. + /// Homestar RPC timeout. #[clap(long = "timeout", default_value = "60s", value_parser = humantime::parse_duration)] timeout: Duration, } @@ -69,32 +72,37 @@ impl Default for RpcArgs { } /// CLI Argument types. -#[derive(Debug, Parser)] +#[derive(Debug, Subcommand)] pub enum Command { /// Start the Homestar runtime. Start { - /// Database url, defaults to sqlite://homestar.db. + /// Database URL, defaults to homestar.db. #[arg( long = "db", value_name = "DB", - env = "DATABASE_URL", - help = "SQLite database url" + env = "DATABASE_PATH", + value_hint = clap::ValueHint::AnyPath, + value_name = "DATABASE_PATH", + default_value = DEFAULT_DB_PATH, + help = "Database path (SQLite) [optional]" )] database_url: Option, - /// Optional runtime configuration file, otherwise use defaults. + /// Runtime configuration file (.toml), defaults to ./config/settings.toml. #[arg( short = 'c', long = "config", + value_hint = clap::ValueHint::FilePath, value_name = "CONFIG", - help = "runtime configuration file" + default_value = DEFAULT_SETTINGS_FILE, + help = "Runtime configuration file (.toml) [optional]" )] runtime_config: Option, /// Daemonize the runtime, false by default. #[arg( short = 'd', long = "daemonize", - default_value_t = false, - help = "daemonize the runtime" + default_value = "false", + help = "Daemonize the runtime" )] daemonize: bool, /// Directory to place daemon files, defaults to /tmp. @@ -103,35 +111,39 @@ pub enum Command { default_value = TMP_DIR, value_hint = clap::ValueHint::DirPath, value_name = "DIR", - help = "directory to place daemon files" + help = "Directory to place daemon file(s)" )] daemon_dir: PathBuf, }, /// Stop the Homestar runtime. Stop(RpcArgs), - /// Ping the Homestar runtime. + /// Ping the Homestar runtime to see if it's running. Ping(RpcArgs), - /// Run a workflow, given a workflow file. + /// Run an IPVM-configured workflow file on the Homestar runtime. Run { /// RPC host / port arguments. #[clap(flatten)] args: RpcArgs, - /// (optional) name given to a workflow. + /// Local name associated with a workflow (optional). #[arg( short = 'n', long = "name", value_name = "NAME", - help = "(optional) name given to a workflow" + help = "Local name given to a workflow (optional)" )] name: Option, - /// Workflow file to run. + /// IPVM-configured workflow file to run. + /// Supported: + /// - JSON (.json). #[arg( short='w', long = "workflow", value_hint = clap::ValueHint::FilePath, value_name = "FILE", value_parser = clap::value_parser!(file::ReadWorkflow), - help = "path to workflow file" + help = r#"IPVM-configured workflow file to run. +Supported: + - JSON (.json)"# )] workflow: file::ReadWorkflow, }, diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 85236d30..1e1c5a5d 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -330,7 +330,7 @@ impl Settings { #[cfg(test)] let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config/settings.toml"); #[cfg(not(test))] - let path = PathBuf::from("config/settings.toml"); + let path = PathBuf::from("./config/settings.toml"); Self::build(path) } From 0616a3395a475bdf2904e63462cbc32544096490 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 13 Nov 2023 12:57:16 -0500 Subject: [PATCH 28/42] chore: builds on for pr --- .github/workflows/builds.yml | 5 +++-- README.md | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index dd87191d..39bb1c5d 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -1,6 +1,7 @@ name: ⚃ Builds # TODO: brew formula (Macs), cargo-wix (Windows Installs), cargo-aur (Arch) +# TODO: bring back release hook (vs on PR) on: workflow_dispatch: @@ -14,8 +15,8 @@ on: types: [published] # for debugging - # pull_request: - # branches: [ '**' ] + pull_request: + branches: [ '**' ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/README.md b/README.md index ddf889d9..32e41165 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Homestar logo -

homestar

+

Homestar

@@ -61,18 +61,18 @@ ## Quickstart -If you're looking to help develop `homestar`, please dive right into our +If you're looking to help develop `Homestar`, please dive right into our [development](./DEVELOPMENT.md) guide. -Otherwise, the easiest way to get started and see `homestar` in action is to +Otherwise, the easiest way to get started and see `Homestar` in action is to follow-along and run our image-processing [websocket relay](./examples/websocket-relay) example, which integrates -`homestar` with a browser application to run a +`Homestar` with a browser application to run a statically-configured workflow. The associated `README.md` walks through what to install (i.e. `rust`, `node/npm`, `ipfs`), what commands to run, and embeds a video demonstrating its usage. -Throughout the `homestar` ecosystem and documentation, we'll draw a distinction +Throughout the `Homestar` ecosystem and documentation, we'll draw a distinction between the [host runtime][host-runtime] and the support for different [guest languages and bindings][guest]. @@ -83,7 +83,7 @@ our examples there. ## Packages -Each `homestar` release will also build packages for distribution across +Each `Homestar` release will also build packages for distribution across different platforms. - [homebrew][homebrew]: `brew install fission-codes/fission/homestar` @@ -95,7 +95,7 @@ All [examples](./examples) contain instructions for running them, including what to install and how to run them. Please clone this repo, and get started! -Each example showcases something specific and interesting about `homestar` +Each example showcases something specific and interesting about `Homestar` as a system. Our current list includes: @@ -108,7 +108,7 @@ Our current list includes: ## Workspace This repository is comprised of a few library packages and a library/binary that -represents the `homestar` runtime. We recommend diving into each package's own +represents the `Homestar` runtime. We recommend diving into each package's own `README.md` for more information when available. ### Core Crates @@ -144,8 +144,8 @@ represents the `homestar` runtime. We recommend diving into each package's own - [examples/*](./examples) - `examples` contains examples and demos showcasing `homestar` packages - and the `homestar runtime`. Each example is set up as its own crate, + `examples` contains examples and demos showcasing `Homestar` packages + and the `Homestar` runtime. Each example is set up as its own crate, demonstrating the necessary dependencies and setup(s). ## Contributing From 951abe6a7722bc106eba857783ae61277d31ab76 Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:04:02 -0800 Subject: [PATCH 29/42] refactor: (#429) # Description This PR includes the following changes: - [x] Set gossip `max_transmit_size` - [x] Handle Kademlia `InboundRequest` events ## Type of change - [x] Refactor (non-breaking change that updates existing functionality) --- homestar-runtime/src/event_handler/swarm_event.rs | 3 +++ homestar-runtime/src/network/pubsub.rs | 1 + homestar-runtime/src/settings.rs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index 417b9581..bc861e5f 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -583,6 +583,9 @@ async fn handle_swarm_event( _ => {} } } + SwarmEvent::Behaviour(ComposedEvent::Kademlia(kad::Event::InboundRequest { request })) => { + debug!("kademlia inbound request received {request:?}") + } SwarmEvent::Behaviour(ComposedEvent::Kademlia(kad::Event::RoutingUpdated { peer, .. })) => { diff --git a/homestar-runtime/src/network/pubsub.rs b/homestar-runtime/src/network/pubsub.rs index ae570cae..2928e245 100644 --- a/homestar-runtime/src/network/pubsub.rs +++ b/homestar-runtime/src/network/pubsub.rs @@ -34,6 +34,7 @@ pub(crate) fn new(keypair: Keypair, settings: &settings::Node) -> Result Date: Mon, 13 Nov 2023 14:20:39 -0500 Subject: [PATCH 30/42] chore: update debs for targets --- .github/workflows/builds.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 39bb1c5d..24f816f7 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -97,23 +97,26 @@ jobs: build-packages: runs-on: ubuntu-latest - - env: - LINUX_TARGET: x86_64-unknown-linux-musl - + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + - target: x86_64-unknown-linux-musl steps: - name: Checkout uses: actions/checkout@v4 - name: Install musl-tools run: sudo apt update && sudo apt install -y musl-dev musl-tools + if: matrix.target == 'x86_64-unknown-linux-musl' - name: Install Rust toolchain id: toolchain uses: dtolnay/rust-toolchain@master with: toolchain: stable - targets: ${{ env.LINUX_TARGET }} + targets: ${{ matrix.target }} - name: Override rust-toolchain.toml run: rustup override set ${{steps.toolchain.outputs.name}} @@ -132,18 +135,18 @@ jobs: uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - shared-key: check-${{ env.LINUX_TARGET }}-ubuntu-latest + shared-key: check-${{ matrix.target }}-ubuntu-latest - name: Create .deb - run: cargo deb -p homestar-runtime --target ${{ env.LINUX_TARGET }} --output homestar.deb + run: cargo deb -p homestar-runtime --target ${{ matrix.target }} --output homestar.deb - name: Create .rpm - run: cargo generate-rpm -p homestar-runtime --target ${{ env.LINUX_TARGET }} --output homestar.rpm + run: cargo generate-rpm -p homestar-runtime --target ${{ matrix.target }} --output homestar.rpm - name: Upload Release Artifacts uses: actions/upload-artifact@v3 with: - name: ${{ env.LINUX_TARGET }} + name: ${{ matrix.target }} path: | *.deb *.rpm From da344c197d27120903cae4337c1f590d23a0b3fb Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 13 Nov 2023 15:07:52 -0500 Subject: [PATCH 31/42] chore: builds trial1 --- .github/workflows/builds.yml | 4 ++-- homestar-runtime/Cargo.toml | 42 ++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 24f816f7..1ab38b7b 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -138,10 +138,10 @@ jobs: shared-key: check-${{ matrix.target }}-ubuntu-latest - name: Create .deb - run: cargo deb -p homestar-runtime --target ${{ matrix.target }} --output homestar.deb + run: cargo deb -p homestar-runtime --target ${{ matrix.target }} --variant ${{ matrix.target }} --output homestar.deb - name: Create .rpm - run: cargo generate-rpm -p homestar-runtime --target ${{ matrix.target }} --output homestar.rpm + run: cargo generate-rpm -p homestar-runtime --target ${{ matrix.target }} --variant ${{ matrix.target }} --output homestar.rpm - name: Upload Release Artifacts uses: actions/upload-artifact@v3 diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 92b49e87..41df70e5 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -233,7 +233,7 @@ all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] -[package.metadata.deb] +[package.metadata.deb.variants.x86_64-unknown-linux-musl] maintainer = "James Walker " license-file = ["LICENSE", "0"] extended-description-file = "README.md" @@ -263,10 +263,48 @@ assets = [ ], ] -[package.metadata.generate-rpm] +[package.metadata.deb.variants.x86_64-unknown-linux-gnu] +maintainer = "James Walker " +license-file = ["LICENSE", "0"] +extended-description-file = "README.md" +depends = "" +section = "network" +priority = "optional" +assets = [ + [ + "../target/x86_64-unknown-linux-gnu/release/homestar", + "usr/bin/", + "755", + ], + [ + "../CHANGELOG.md", + "usr/share/doc/homestar/", + "644", + ], + [ + "../LICENSE", + "usr/share/doc/homestar/", + "644", + ], + [ + "../README.md", + "usr/share/doc/homestar/", + "644", + ], +] + +[package.metadata.generate-rpm.variants.x86_64-unknown-linux-musl] assets = [ { source = "../target/x86_64-unknown-linux-musl/release/homestar", dest = "/usr/bin/homestar", mode = "755" }, { source = "../CHANGELOG.md", dest = "/usr/share/doc/homestar/CHANGELOG.md", mode = "644", doc = true }, { source = "../LICENSE", dest = "/usr/share/doc/homestar/LICENSE.md", mode = "644", doc = true }, { source = "../README.md", dest = "/usr/share/doc/homestar/README.md", mode = "644", doc = true }, ] + +[package.metadata.generate-rpm.variants.x86_64-unknown-linux-gnu] +assets = [ + { source = "../target/x86_64-unknown-linux-gnu/release/homestar", dest = "/usr/bin/homestar", mode = "755" }, + { source = "../CHANGELOG.md", dest = "/usr/share/doc/homestar/CHANGELOG.md", mode = "644", doc = true }, + { source = "../LICENSE", dest = "/usr/share/doc/homestar/LICENSE.md", mode = "644", doc = true }, + { source = "../README.md", dest = "/usr/share/doc/homestar/README.md", mode = "644", doc = true }, +] From 11a8f88b5a6f4ce83377ed60034357bb39c41257 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 14 Nov 2023 16:22:39 -0500 Subject: [PATCH 32/42] fix: make configuration run without -c (all defaults) (#433) Includes: - also, add starter functions for default .pem/settings initialization - error handling around retry, wasms - ups the transport timeout --- Cross.toml | 10 +- homestar-runtime/src/cli.rs | 4 +- homestar-runtime/src/scheduler.rs | 1 + homestar-runtime/src/settings.rs | 112 ++++++++++++++++------ homestar-runtime/src/tasks/fetch.rs | 50 +++++++--- homestar-runtime/src/worker.rs | 21 ++-- homestar-runtime/src/workflow/settings.rs | 2 +- homestar-wasm/src/wasmtime/error.rs | 2 +- homestar-wasm/src/wasmtime/world.rs | 2 +- 9 files changed, 150 insertions(+), 54 deletions(-) diff --git a/Cross.toml b/Cross.toml index 688d1ac7..ff92d71f 100644 --- a/Cross.toml +++ b/Cross.toml @@ -6,11 +6,19 @@ passthrough = [ "RUSTFLAGS", ] +# When running `cross` with nix, do this within `nix-shell -p gcc rustup`. +# +# Then, run +# +# `cross build -p homestar-runtime --target x86_64-unknown-linux-musl` +# or +# `cross build -p homestar-runtime --target aarch64-unknown-linux-musl` + [target.x86_64-unknown-linux-musl] image = "burntsushi/cross:x86_64-unknown-linux-musl" [target.aarch64-unknown-linux-musl] -image = "burntsushi/cross:aarch64-unknown-linux-gnu" +image = "burntsushi/cross:aarch64-unknown-linux-musl" [target.x86_64-apple-darwin] image = "freeznet/x86_64-apple-darwin-cross:11.3" diff --git a/homestar-runtime/src/cli.rs b/homestar-runtime/src/cli.rs index 9032b374..30353fa9 100644 --- a/homestar-runtime/src/cli.rs +++ b/homestar-runtime/src/cli.rs @@ -19,7 +19,6 @@ pub use error::Error; pub(crate) mod show; pub(crate) use show::ConsoleTable; -const DEFAULT_SETTINGS_FILE: &str = "./config/settings.toml"; const DEFAULT_DB_PATH: &str = "homestar.db"; const TMP_DIR: &str = "/tmp"; const HELP_TEMPLATE: &str = "{name} {version} @@ -87,13 +86,12 @@ pub enum Command { help = "Database path (SQLite) [optional]" )] database_url: Option, - /// Runtime configuration file (.toml), defaults to ./config/settings.toml. + /// Runtime configuration file (.toml). #[arg( short = 'c', long = "config", value_hint = clap::ValueHint::FilePath, value_name = "CONFIG", - default_value = DEFAULT_SETTINGS_FILE, help = "Runtime configuration file (.toml) [optional]" )] runtime_config: Option, diff --git a/homestar-runtime/src/scheduler.rs b/homestar-runtime/src/scheduler.rs index df3f48bf..0dcc2f1e 100644 --- a/homestar-runtime/src/scheduler.rs +++ b/homestar-runtime/src/scheduler.rs @@ -171,6 +171,7 @@ impl<'a> TaskScheduler<'a> { .into_iter() .map(|(_, rsc)| rsc.to_owned()) .collect(); + let fetched = fetch_fn(resources_to_fetch).await?; match resume { diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 483f3fe6..d29f8704 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -7,6 +7,7 @@ use serde_with::{serde_as, DisplayFromStr, DurationMilliSeconds, DurationSeconds #[cfg(feature = "ipfs")] use std::net::Ipv4Addr; use std::{ + env, net::{IpAddr, Ipv6Addr}, path::PathBuf, time::Duration, @@ -15,11 +16,17 @@ use std::{ mod pubkey_config; pub(crate) use pubkey_config::PubkeyConfig; +#[cfg(target_os = "windows")] +const HOME_VAR: &str = "USERPROFILE"; +#[cfg(not(target_os = "windows"))] +const HOME_VAR: &str = "HOME"; + /// Application settings. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Settings { #[serde(default)] pub(crate) monitoring: Monitoring, + #[serde(default)] pub(crate) node: Node, } @@ -38,6 +45,7 @@ impl Settings { /// Monitoring settings. #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] pub struct Monitoring { /// Tokio console port. pub console_subscriber_port: u16, @@ -49,7 +57,8 @@ pub struct Monitoring { /// Server settings. #[serde_as] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] pub struct Node { /// Network settings. #[serde(default)] @@ -59,11 +68,9 @@ pub struct Node { pub(crate) db: Database, /// Garbage collection interval. #[serde_as(as = "DurationSeconds")] - #[serde(default = "default_gc_interval")] pub(crate) gc_interval: Duration, /// Shutdown timeout. #[serde_as(as = "DurationSeconds")] - #[serde(default = "default_shutdown_timeout")] pub(crate) shutdown_timeout: Duration, } @@ -246,6 +253,17 @@ impl Default for Database { } } +impl Default for Node { + fn default() -> Self { + Self { + gc_interval: Duration::from_secs(1800), + shutdown_timeout: Duration::from_secs(20), + network: Default::default(), + db: Default::default(), + } + } +} + impl Default for Network { fn default() -> Self { Self { @@ -276,7 +294,7 @@ impl Default for Network { rpc_max_connections: 10, rpc_port: 3030, rpc_server_timeout: Duration::new(120, 0), - transport_connection_timeout: Duration::new(20, 0), + transport_connection_timeout: Duration::new(60, 0), webserver_host: Uri::from_static("127.0.0.1"), webserver_port: 1337, webserver_timeout: Duration::new(120, 0), @@ -314,14 +332,6 @@ impl Network { } } -fn default_shutdown_timeout() -> Duration { - Duration::new(20, 0) -} - -fn default_gc_interval() -> Duration { - Duration::new(1800, 0) -} - impl Settings { /// Load settings. /// @@ -331,34 +341,52 @@ impl Settings { /// Use two underscores as defined by the separator below pub fn load() -> Result { #[cfg(test)] - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config/settings.toml"); + { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config/settings.toml"); + Self::build(Some(path)) + } #[cfg(not(test))] - let path = PathBuf::from("./config/settings.toml"); - - Self::build(path) + Self::build(None) } /// Load settings from file string that must conform to a [PathBuf]. pub fn load_from_file(file: PathBuf) -> Result { - Self::build(file) + Self::build(Some(file)) } - fn build(path: PathBuf) -> Result { - let s = Config::builder() - .add_source(File::with_name( - &path - .canonicalize() + fn build(path: Option) -> Result { + let builder = if let Some(p) = path { + Config::builder().add_source(File::with_name( + &p.canonicalize() .map_err(|e| ConfigError::NotFound(e.to_string()))? .as_path() .display() .to_string(), )) + } else { + Config::builder() + }; + + let s = builder .add_source(Environment::with_prefix("HOMESTAR").separator("__")) .build()?; s.try_deserialize() } } +#[allow(dead_code)] +fn config_dir() -> PathBuf { + let config_dir = + env::var("XDG_CONFIG_HOME").map_or_else(|_| home_dir().join(".config"), PathBuf::from); + config_dir.join("homestar") +} + +#[allow(dead_code)] +fn home_dir() -> PathBuf { + let home = env::var(HOME_VAR).unwrap_or_else(|_| panic!("{} not found", HOME_VAR)); + PathBuf::from(home) +} + #[cfg(test)] mod test { use super::*; @@ -380,7 +408,7 @@ mod test { #[test] fn defaults_with_modification() { - let settings = Settings::build("fixtures/settings.toml".into()).unwrap(); + let settings = Settings::build(Some("fixtures/settings.toml".into())).unwrap(); let mut default_modded_settings = Node::default(); default_modded_settings.network.events_buffer_len = 1000; @@ -396,14 +424,14 @@ mod test { fn overriding_env() { std::env::set_var("HOMESTAR__NODE__NETWORK__RPC_PORT", "2046"); std::env::set_var("HOMESTAR__NODE__DB__MAX_POOL_SIZE", "1"); - let settings = Settings::build("fixtures/settings.toml".into()).unwrap(); + let settings = Settings::build(Some("fixtures/settings.toml".into())).unwrap(); assert_eq!(settings.node.network.rpc_port, 2046); assert_eq!(settings.node.db.max_pool_size, 1); } #[test] fn import_existing_key() { - let settings = Settings::build("fixtures/settings-import-ed25519.toml".into()) + let settings = Settings::build(Some("fixtures/settings-import-ed25519.toml".into())) .expect("setting file in test fixtures"); let msg = b"foo bar"; @@ -424,7 +452,7 @@ mod test { #[test] fn import_secp256k1_key() { - let settings = Settings::build("fixtures/settings-import-secp256k1.toml".into()) + let settings = Settings::build(Some("fixtures/settings-import-secp256k1.toml".into())) .expect("setting file in test fixtures"); settings @@ -437,7 +465,7 @@ mod test { #[test] fn seeded_secp256k1_key() { - let settings = Settings::build("fixtures/settings-random-secp256k1.toml".into()) + let settings = Settings::build(Some("fixtures/settings-random-secp256k1.toml".into())) .expect("setting file in test fixtures"); settings @@ -447,4 +475,34 @@ mod test { .keypair() .expect("generate a seeded secp256k1 key"); } + + #[test] + fn test_config_dir_xdg() { + env::remove_var("HOME"); + env::set_var("XDG_CONFIG_HOME", "/home/user/custom_config"); + assert_eq!( + config_dir(), + PathBuf::from("/home/user/custom_config/homestar") + ); + env::remove_var("XDG_CONFIG_HOME"); + } + + #[cfg(not(target_os = "windows"))] + #[test] + fn test_config_dir() { + env::set_var("HOME", "/home/user"); + env::remove_var("XDG_CONFIG_HOME"); + assert_eq!(config_dir(), PathBuf::from("/home/user/.config/homestar")); + env::remove_var("HOME"); + } + + #[cfg(target_os = "windows")] + #[test] + fn test_config_dir() { + env::remove_var("XDG_CONFIG_HOME"); + assert_eq!( + config_dir(), + PathBuf::from(format!(r"{}\.config\homestar", env!("USERPROFILE"))) + ); + } } diff --git a/homestar-runtime/src/tasks/fetch.rs b/homestar-runtime/src/tasks/fetch.rs index bf99f0be..87922afd 100644 --- a/homestar-runtime/src/tasks/fetch.rs +++ b/homestar-runtime/src/tasks/fetch.rs @@ -29,27 +29,49 @@ impl Fetch { ) -> Result>> { use futures::{stream::FuturesUnordered, TryStreamExt}; let settings = settings.as_ref(); + let retries = settings.retries; let tasks = FuturesUnordered::new(); for rsc in resources.iter() { - tracing::info!(rsc = rsc.to_string(), "Fetching resource"); - let task = tryhard::retry_fn(|| async { Self::fetch(rsc.clone(), ipfs.clone()).await }) - .with_config( - tryhard::RetryFutureConfig::new(settings.retries) - .exponential_backoff(settings.retry_initial_delay) - .max_delay(settings.retry_max_delay), + let task = tryhard::retry_fn(|| async { + tracing::info!( + rsc = rsc.to_string(), + "attempting to fetch resource from IPFS" ); + Self::fetch(rsc.clone(), ipfs.clone()).await + }) + .retries(retries) + .exponential_backoff(settings.retry_initial_delay) + .max_delay(settings.retry_max_delay) + .on_retry(|attempts, next_delay, error| { + let err = error.to_string(); + async move { + if attempts < retries { + tracing::warn!( + err = err, + attempts = attempts, + "retrying fetch after error @ {}ms", + next_delay.map(|d| d.as_millis()).unwrap_or(0) + ); + } else { + tracing::warn!(err = err, attempts = attempts, "maxed out # of retries"); + } + } + }); tasks.push(task); } - tasks.try_collect::>().await?.into_iter().try_fold( - IndexMap::default(), - |mut acc, res| { - let answer = res.1?; - acc.insert(res.0, answer); + tracing::info!("fetching necessary resources from IPFS"); + if let Ok(vec) = tasks.try_collect::>().await { + vec.into_iter() + .try_fold(IndexMap::default(), |mut acc, res| { + let answer = res.1?; + acc.insert(res.0, answer); - Ok::<_, anyhow::Error>(acc) - }, - ) + Ok::<_, anyhow::Error>(acc) + }) + } else { + Err(anyhow::anyhow!("Failed to fetch resources from IPFS")) + } } /// Gather resources via URLs, leveraging an exponential backoff. diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 0f4b26e7..15a73e1a 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -147,14 +147,19 @@ where where F: FnOnce(FnvHashSet) -> BoxFuture<'a, Result>>>, { - let scheduler_ctx = TaskScheduler::init( + match TaskScheduler::init( self.graph.clone(), // Arc'ed &mut self.db.conn()?, fetch_fn, ) - .await?; - - self.run_queue(scheduler_ctx.scheduler, running_tasks).await + .await + { + Ok(ctx) => self.run_queue(ctx.scheduler, running_tasks).await, + Err(err) => { + error!(err=?err, "error initializing scheduler"); + Err(anyhow!("error initializing scheduler")) + } + } } #[allow(unused_mut)] @@ -185,7 +190,7 @@ where info!( workflow_cid = workflow_cid.to_string(), cid = cid.to_string(), - "resolving cid" + "attempting to resolve cid in workflow" ); if let Some(result) = linkmap.read().await.get(&cid) { @@ -369,7 +374,11 @@ where receipt_meta, additional_meta, )), - Err(e) => Err(anyhow!("cannot execute wasm module: {e}")), + Err(err) => Err( + anyhow!("cannot execute wasm module: {err}")) + .with_context(|| { + format!("not able to run fn {fun} for promised cid: {instruction_ptr}, in workflow {workflow_cid}") + }), } }); handles.push(handle); diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index cba18188..b2dbd834 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -18,7 +18,7 @@ pub struct Settings { impl Default for Settings { fn default() -> Self { Self { - retries: 10, + retries: 3, retry_max_delay: Duration::new(60, 0), retry_initial_delay: Duration::from_millis(500), p2p_timeout: Duration::new(5, 0), diff --git a/homestar-wasm/src/wasmtime/error.rs b/homestar-wasm/src/wasmtime/error.rs index 7e327579..a11147d8 100644 --- a/homestar-wasm/src/wasmtime/error.rs +++ b/homestar-wasm/src/wasmtime/error.rs @@ -40,7 +40,7 @@ pub enum Error { #[error(transparent)] WasmRuntime(#[from] anyhow::Error), /// Failure to find Wasm function for execution. - #[error("Wasm function {0} not found")] + #[error("Wasm function {0} not found in given Wasm component/resource")] WasmFunctionNotFound(String), /// [Wat] as Wasm component error. /// diff --git a/homestar-wasm/src/wasmtime/world.rs b/homestar-wasm/src/wasmtime/world.rs index 18f95e41..6338ff12 100644 --- a/homestar-wasm/src/wasmtime/world.rs +++ b/homestar-wasm/src/wasmtime/world.rs @@ -121,7 +121,7 @@ impl Env { }, Input::Deferred(await_promise) => { bail!(Error::ResolvePromise(ResolveError::UnresolvedCid(format!( - "deferred task not yet resolved for {}: {}", + "deferred task/instruction not yet resolved or exists for promise: {}: {}", await_promise.result(), await_promise.instruction_cid() )))) From 0cd381dcca5462fda3149d1452a81d215e4e84f4 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 14 Nov 2023 18:11:37 -0500 Subject: [PATCH 33/42] refactor: static/dynamic nodeinfo and recv deadlines (#435) --- homestar-runtime/src/event_handler/event.rs | 5 ++++ .../src/event_handler/swarm_event.rs | 3 ++- homestar-runtime/src/network/webserver.rs | 11 ++++---- homestar-runtime/src/network/webserver/rpc.rs | 23 +++++++--------- homestar-runtime/src/runner.rs | 17 ++++++++---- homestar-runtime/src/runner/nodeinfo.rs | 19 +++++++++++--- homestar-runtime/src/worker.rs | 26 ++++++------------- homestar-runtime/src/workflow/info.rs | 19 +++++++------- 8 files changed, 69 insertions(+), 54 deletions(-) diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index de2828f4..de46ddd1 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -115,6 +115,8 @@ pub(crate) enum Event { RegisterPeer(PeerId), /// Discover peers from a rendezvous node. DiscoverPeers(PeerId), + /// TODO + GetListeners(AsyncBoundedChannelSender>), } const RENDEZVOUS_NAMESPACE: &str = "homestar"; @@ -134,6 +136,9 @@ impl Event { event_handler.shutdown().await; let _ = tx.send(()); } + Event::GetListeners(tx) => { + let _ = tx.send(event_handler.swarm.listeners().cloned().collect()); + } Event::FindRecord(record) => record.find(event_handler).await, Event::RemoveRecord(record) => record.remove(event_handler).await, Event::OutboundRequest(PeerRequest { diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index bc861e5f..72fc13a8 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -409,7 +409,8 @@ async fn handle_swarm_event( info!( peer_id = propagation_source.to_string(), message_id = message_id.to_string(), - "message received on receipts topic: {receipt}" + "message received on receipts topic: {}", + receipt.cid() ); // Store gossiped receipt. diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index ab6aab4a..64bcea61 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -3,7 +3,7 @@ use crate::{ runner, - runner::{NodeInfo, WsSender}, + runner::{DynamicNodeInfo, StaticNodeInfo, WsSender}, settings, }; use anyhow::{anyhow, Result}; @@ -63,7 +63,7 @@ pub(crate) enum Message { /// TODO GetNodeInfo, /// TODO - AckNodeInfo(NodeInfo), + AckNodeInfo((StaticNodeInfo, DynamicNodeInfo)), } /// WebSocket server fields. @@ -266,6 +266,7 @@ mod test { #[cfg(feature = "websocket-notify")] use jsonrpsee::types::error::ErrorCode; use jsonrpsee::{core::client::ClientT, rpc_params, ws_client::WsClientBuilder}; + use libp2p::Multiaddr; #[cfg(feature = "websocket-notify")] use notifier::{self, Header}; @@ -307,17 +308,17 @@ mod test { let peer_id = libp2p::PeerId::from_str("12D3KooWRNw2pJC9748Fmq4WNV27HoSTcX3r37132FLkQMrbKAiC") .unwrap(); - let nodeinfo = NodeInfo::new(peer_id); + let static_info = StaticNodeInfo::new(peer_id); assert_eq!( ws_resp, - serde_json::json!({"healthy": true, "nodeInfo": nodeinfo}) + serde_json::json!({"healthy": true, "nodeInfo": {"static": static_info, "dynamic": {"listeners": Vec::::new()}}}) ); let http_resp = reqwest::get(format!("{}/health", http_url)).await.unwrap(); assert_eq!(http_resp.status(), 200); let http_resp = http_resp.json::().await.unwrap(); assert_eq!( http_resp, - serde_json::json!({"healthy": true, "nodeInfo": nodeinfo}) + serde_json::json!({"healthy": true, "nodeInfo": {"static": static_info, "dynamic": {"listeners": Vec::::new()}}}) ); }); diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index afc8c647..d3e93a7f 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -28,13 +28,10 @@ use metrics_exporter_prometheus::PrometheusHandle; #[cfg(feature = "websocket-notify")] use std::sync::Arc; use std::time::Duration; +#[allow(unused_imports)] +use tokio::sync::oneshot; #[cfg(feature = "websocket-notify")] use tokio::{runtime::Handle, select}; -#[allow(unused_imports)] -use tokio::{ - sync::oneshot, - time::{self, Instant}, -}; #[cfg(feature = "websocket-notify")] use tokio_stream::wrappers::BroadcastStream; #[allow(unused_imports)] @@ -147,10 +144,11 @@ impl JsonRpc { .await .map_err(|err| internal_err(err.to_string()))?; - if let Ok(Ok(Message::AckNodeInfo(info))) = - time::timeout_at(Instant::now() + ctx.receiver_timeout, rx.recv_async()).await + if let Ok(Message::AckNodeInfo((static_info, dyn_info))) = + rx.recv_deadline(std::time::Instant::now() + ctx.receiver_timeout) { - Ok(serde_json::json!({ "healthy": true, "nodeInfo": info})) + Ok(serde_json::json!({ "healthy": true, "nodeInfo": { + "static": static_info, "dynamic": dyn_info}})) } else { warn!(sub = HEALTH_ENDPOINT, "did not acknowledge message in time"); Err(internal_err("failed to get node information".to_string())) @@ -159,13 +157,13 @@ impl JsonRpc { #[cfg(test)] module.register_async_method(HEALTH_ENDPOINT, |_, _| async move { - use crate::runner::NodeInfo; + use crate::runner::{DynamicNodeInfo, StaticNodeInfo}; use std::str::FromStr; let peer_id = libp2p::PeerId::from_str("12D3KooWRNw2pJC9748Fmq4WNV27HoSTcX3r37132FLkQMrbKAiC") .unwrap(); Ok::>(serde_json::json!({ - "healthy": true, "nodeInfo": NodeInfo::new(peer_id) + "healthy": true, "nodeInfo": {"static": StaticNodeInfo::new(peer_id), "dynamic": DynamicNodeInfo::new(vec![])}, })) })?; @@ -221,9 +219,8 @@ impl JsonRpc { )) .await?; - if let Ok(Ok(Message::AckWorkflow((cid, name)))) = - time::timeout_at(Instant::now() + ctx.receiver_timeout, rx.recv_async()) - .await + if let Ok(Message::AckWorkflow((cid, name))) = + rx.recv_deadline(std::time::Instant::now() + ctx.receiver_timeout) { let sink = pending.accept().await?; ctx.workflow_listeners diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 179bcea1..42bf19a9 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -27,7 +27,7 @@ use libipld::Cid; use metrics_exporter_prometheus::PrometheusHandle; #[cfg(not(test))] use std::sync::atomic::{AtomicUsize, Ordering}; -use std::{ops::ControlFlow, rc::Rc, sync::Arc, task::Poll}; +use std::{ops::ControlFlow, rc::Rc, sync::Arc, task::Poll, time::Instant}; #[cfg(not(windows))] use tokio::signal::unix::{signal, SignalKind}; #[cfg(windows)] @@ -45,7 +45,7 @@ pub(crate) mod file; mod nodeinfo; pub(crate) mod response; pub(crate) use error::Error; -pub(crate) use nodeinfo::NodeInfo; +pub(crate) use nodeinfo::{DynamicNodeInfo, StaticNodeInfo}; #[cfg(not(test))] const HOMESTAR_THREAD: &str = "homestar-runtime"; @@ -104,7 +104,7 @@ impl ModifiedSet for RunningTaskSet { pub struct Runner { event_sender: Arc>, expiration_queue: Rc>>, - node_info: NodeInfo, + node_info: StaticNodeInfo, running_tasks: Arc, running_workers: RunningWorkerSet, pub(crate) runtime: tokio::runtime::Runtime, @@ -197,7 +197,7 @@ impl Runner { Ok(Self { event_sender, expiration_queue: Rc::new(AtomicRefCell::new(DelayQueue::new())), - node_info: NodeInfo::new(peer_id), + node_info: StaticNodeInfo::new(peer_id), running_tasks: DashMap::new().into(), running_workers: DashMap::new(), runtime, @@ -298,7 +298,14 @@ impl Runner { } (webserver::Message::GetNodeInfo, Some(oneshot_tx)) => { info!("getting node info"); - let _ = oneshot_tx.send(webserver::Message::AckNodeInfo(self.node_info.clone())); + let (tx, rx) = AsyncBoundedChannel::oneshot(); + let _ = self.event_sender.send_async(Event::GetListeners(tx)).await; + let dyn_node_info = if let Ok(listeners) = rx.recv_deadline(Instant::now() + self.settings.node.network.webserver_timeout) { + DynamicNodeInfo::new(listeners) + } else { + DynamicNodeInfo::new(vec![]) + }; + let _ = oneshot_tx.send(webserver::Message::AckNodeInfo((self.node_info.clone(), dyn_node_info))); } _ => () } diff --git a/homestar-runtime/src/runner/nodeinfo.rs b/homestar-runtime/src/runner/nodeinfo.rs index fd916151..58e9484a 100644 --- a/homestar-runtime/src/runner/nodeinfo.rs +++ b/homestar-runtime/src/runner/nodeinfo.rs @@ -1,13 +1,13 @@ -use libp2p::PeerId; +use libp2p::{Multiaddr, PeerId}; use serde::{Deserialize, Serialize}; /// TODO #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct NodeInfo { +pub(crate) struct StaticNodeInfo { pub(crate) peer_id: PeerId, } -impl NodeInfo { +impl StaticNodeInfo { /// TODO pub(crate) fn new(peer_id: PeerId) -> Self { Self { peer_id } @@ -19,3 +19,16 @@ impl NodeInfo { &self.peer_id } } + +/// TODO +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct DynamicNodeInfo { + pub(crate) listeners: Vec, +} + +impl DynamicNodeInfo { + /// TODO + pub(crate) fn new(listeners: Vec) -> Self { + Self { listeners } + } +} diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 15a73e1a..c80a9b31 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -43,12 +43,8 @@ use homestar_wasm::{ }; use indexmap::IndexMap; use libipld::{Cid, Ipld}; -use std::{collections::BTreeMap, sync::Arc}; -use tokio::{ - sync::RwLock, - task::JoinSet, - time::{self, Instant}, -}; +use std::{collections::BTreeMap, sync::Arc, time::Instant}; +use tokio::{sync::RwLock, task::JoinSet}; use tracing::{debug, error, info}; /// [JoinSet] of tasks run by a [Worker]. @@ -215,29 +211,23 @@ where ))) .await; - let found = match time::timeout_at( - Instant::now() + workflow_settings.p2p_timeout, - rx.recv_async(), - ) - .await + let found = match rx + .recv_deadline(Instant::now() + workflow_settings.p2p_timeout) { - Ok(Ok(ResponseEvent::Found(Ok(FoundEvent::Receipt(found))))) => found, - Ok(Ok(ResponseEvent::Found(Err(err)))) => { + Ok(ResponseEvent::Found(Ok(FoundEvent::Receipt(found)))) => found, + Ok(ResponseEvent::Found(Err(err))) => { bail!(ResolveError::UnresolvedCid(format!( "failure in attempting to find event: {err}" ))) } - Ok(Ok(ResponseEvent::NoPeersAvailable)) => { + Ok(ResponseEvent::NoPeersAvailable) => { bail!(ResolveError::UnresolvedCid( "no peers available to communicate with".to_string() )) } - Ok(Ok(_)) => bail!(ResolveError::UnresolvedCid( + Ok(_) => bail!(ResolveError::UnresolvedCid( "wrong or unexpected event message received".to_string(), )), - Ok(Err(err)) => bail!(ResolveError::UnresolvedCid(format!( - "failure in attempting to find receipt: {err}" - ))), Err(err) => bail!(ResolveError::UnresolvedCid(format!( "timeout deadline reached for invocation receipt @ {cid}: {err}", ))), diff --git a/homestar-runtime/src/workflow/info.rs b/homestar-runtime/src/workflow/info.rs index 4222b9e0..19330e10 100644 --- a/homestar-runtime/src/workflow/info.rs +++ b/homestar-runtime/src/workflow/info.rs @@ -17,11 +17,13 @@ use faststr::FastStr; use homestar_core::{ipld::DagJson, workflow::Pointer}; use libipld::{cbor::DagCborCodec, prelude::Codec, serde::from_ipld, Cid, Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt, sync::Arc, time::Duration}; -use tokio::{ - runtime::Handle, - time::{self, Instant}, +use std::{ + collections::BTreeMap, + fmt, + sync::Arc, + time::{Duration, Instant}, }; +use tokio::runtime::Handle; use tracing::info; /// [Workflow] header tag, for sharing workflow information over libp2p. @@ -356,8 +358,8 @@ impl Info { ))) .await?; - match time::timeout_at(Instant::now() + p2p_timeout, rx.recv_async()).await { - Ok(Ok(ResponseEvent::Found(Ok(FoundEvent::Workflow(workflow_info))))) => { + match rx.recv_deadline(Instant::now() + p2p_timeout) { + Ok(ResponseEvent::Found(Ok(FoundEvent::Workflow(workflow_info)))) => { // store workflow receipts from info, as we've already stored // the static information. if let Some(mut conn) = conn { @@ -366,13 +368,12 @@ impl Info { Ok(workflow_info) } - Ok(Ok(ResponseEvent::Found(Err(err)))) => { + Ok(ResponseEvent::Found(Err(err))) => { bail!("failure in attempting to find event: {err}") } - Ok(Ok(event)) => { + Ok(event) => { bail!("received unexpected event {event:?} for workflow {workflow_cid}") } - Ok(Err(err)) => bail!("failure in attempting to find workflow: {err}"), Err(err) => handle_timeout_fn .map(|f| f(workflow_cid, conn).context(err)) .unwrap_or(Err(anyhow!( From 59383e5ece3e8262fd77f490d84fddca4911aee8 Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:55:11 -0800 Subject: [PATCH 34/42] refactor: Add Gossipsub Message wrapper (#436) # Description This PR implements the following changes: - [x] Add gosssipsub message wrapper - [x] Rename `store_and_notify` to `publish_and_notify` The message wrapper includes a header with a nonce to force the gossip of duplicate receipts. We will likely expand on the header in future work and make the nonce optional. ## Link to issue Implements #421. ## Type of change - [x] Refactor (non-breaking change that updates existing functionality) ## Test plan (required) We've added a unit test to roundtrip a gossiped message to bytes and back again. We also have a gossip notifications integration test to confirm messages are still sent. --------- Co-authored-by: Zeeshan Lakhani --- homestar-runtime/src/event_handler/event.rs | 10 +- .../src/event_handler/swarm_event.rs | 70 ++++---- homestar-runtime/src/network/pubsub.rs | 7 +- .../src/network/pubsub/message.rs | 153 ++++++++++++++++++ homestar-runtime/src/network/swarm.rs | 7 +- 5 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 homestar-runtime/src/network/pubsub/message.rs diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index de46ddd1..5ca10e11 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -129,7 +129,7 @@ impl Event { { match self { Event::CapturedReceipt(captured) => { - let _ = captured.store_and_notify(event_handler); + let _ = captured.publish_and_notify(event_handler); } Event::Shutdown(tx) => { info!("event_handler server shutting down"); @@ -243,7 +243,7 @@ impl Captured { } #[allow(dead_code)] - fn store_and_notify( + fn publish_and_notify( mut self, event_handler: &mut EventHandler, ) -> Result<(Cid, InvocationReceipt)> @@ -267,7 +267,7 @@ impl Captured { if event_handler.pubsub_enabled { match event_handler.swarm.behaviour_mut().gossip_publish( pubsub::RECEIPTS_TOPIC, - TopicMessage::CapturedReceipt(receipt.clone()), + TopicMessage::CapturedReceipt(pubsub::Message::new(receipt.clone())), ) { Ok(msg_id) => { info!( @@ -397,7 +397,7 @@ impl Replay { .behaviour_mut() .gossip_publish( pubsub::RECEIPTS_TOPIC, - TopicMessage::CapturedReceipt(receipt.clone()), + TopicMessage::CapturedReceipt(pubsub::Message::new(receipt.clone())), ) .map(|msg_id| { info!(cid=receipt_cid, @@ -543,7 +543,7 @@ where async fn handle_event(self, event_handler: &mut EventHandler, ipfs: IpfsCli) { match self { Event::CapturedReceipt(captured) => { - if let Ok((cid, receipt)) = captured.store_and_notify(event_handler) { + if let Ok((cid, receipt)) = captured.publish_and_notify(event_handler) { #[cfg(not(feature = "test-utils"))] { // Spawn client call in the background, without awaiting. diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index 72fc13a8..cadc2935 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -13,8 +13,11 @@ use crate::{ Event, Handler, RequestResponseError, }, libp2p::multiaddr::MultiaddrExt, - network::swarm::{ - CapsuleTag, ComposedEvent, PeerDiscoveryInfo, RequestResponseKey, HOMESTAR_PROTOCOL_VER, + network::{ + pubsub, + swarm::{ + CapsuleTag, ComposedEvent, PeerDiscoveryInfo, RequestResponseKey, HOMESTAR_PROTOCOL_VER, + }, }, receipt::{RECEIPT_TAG, VERSION_KEY}, workflow, @@ -403,38 +406,41 @@ async fn handle_swarm_event( message, propagation_source, message_id, - } => match Receipt::try_from(message.data) { - // TODO: dont fail blindly if we get a non receipt message - Ok(receipt) => { - info!( - peer_id = propagation_source.to_string(), - message_id = message_id.to_string(), - "message received on receipts topic: {}", - receipt.cid() - ); + } => { + let bytes: Vec = message.data; + match pubsub::Message::::try_from(bytes) { + // TODO: dont fail blindly if we get a non receipt message + Ok(msg) => { + let receipt = msg.payload; + info!( + peer_id = propagation_source.to_string(), + message_id = message_id.to_string(), + "message received on receipts topic: {receipt}" + ); - // Store gossiped receipt. - let _ = event_handler - .db - .conn() - .as_mut() - .map(|conn| Db::store_receipt(receipt.clone(), conn)); - - #[cfg(feature = "websocket-notify")] - notification::emit_event( - event_handler.ws_evt_sender(), - EventNotificationTyp::SwarmNotification( - SwarmNotification::ReceivedReceiptPubsub, - ), - btreemap! { - "peerId" => propagation_source.to_string(), - "cid" => receipt.cid().to_string(), - "ran" => receipt.ran().to_string() - }, - ); + // Store gossiped receipt. + let _ = event_handler + .db + .conn() + .as_mut() + .map(|conn| Db::store_receipt(receipt.clone(), conn)); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification( + SwarmNotification::ReceivedReceiptPubsub, + ), + btreemap! { + "peerId" => propagation_source.to_string(), + "cid" => receipt.cid().to_string(), + "ran" => receipt.ran().to_string() + }, + ); + } + Err(err) => info!(err=?err, "cannot handle incoming gossipsub message"), } - Err(err) => info!(err=?err, "cannot handle incoming gossipsub message"), - }, + } gossipsub::Event::Subscribed { peer_id, topic } => { debug!( peer_id = peer_id.to_string(), diff --git a/homestar-runtime/src/network/pubsub.rs b/homestar-runtime/src/network/pubsub.rs index 2928e245..051f76c7 100644 --- a/homestar-runtime/src/network/pubsub.rs +++ b/homestar-runtime/src/network/pubsub.rs @@ -5,7 +5,7 @@ use crate::settings; use anyhow::Result; use libp2p::{ - gossipsub::{self, ConfigBuilder, Message, MessageAuthenticity, MessageId, ValidationMode}, + gossipsub::{self, ConfigBuilder, MessageAuthenticity, MessageId, ValidationMode}, identity::Keypair, }; use std::{ @@ -13,6 +13,9 @@ use std::{ hash::{Hash, Hasher}, }; +pub(crate) mod message; +pub(crate) use message::Message; + /// [Receipt]-related topic for pub(gossip)sub. /// /// [Receipt]: homestar_core::workflow::receipt @@ -23,7 +26,7 @@ pub(crate) const RECEIPTS_TOPIC: &str = "receipts"; /// [gossipsub]: libp2p::gossipsub pub(crate) fn new(keypair: Keypair, settings: &settings::Node) -> Result { // To content-address message, we can take the hash of message and use it as an ID. - let message_id_fn = |message: &Message| { + let message_id_fn = |message: &gossipsub::Message| { let mut s = DefaultHasher::new(); message.data.hash(&mut s); MessageId::from(s.finish().to_string()) diff --git a/homestar-runtime/src/network/pubsub/message.rs b/homestar-runtime/src/network/pubsub/message.rs new file mode 100644 index 00000000..841212b8 --- /dev/null +++ b/homestar-runtime/src/network/pubsub/message.rs @@ -0,0 +1,153 @@ +use anyhow::{anyhow, Result}; +use homestar_core::workflow::Nonce; +use libipld::{self, cbor::DagCborCodec, prelude::Codec, serde::from_ipld, Ipld}; +use std::collections::BTreeMap; + +const HEADER_KEY: &str = "header"; +const PAYLOAD_KEY: &str = "payload"; +const NONCE_KEY: &str = "nonce"; + +#[derive(Debug)] +pub(crate) struct Message { + pub(crate) header: Header, + pub(crate) payload: T, +} + +impl Message { + pub(crate) fn new(payload: T) -> Self { + let header = Header { + nonce: Nonce::generate(), + }; + + Self { header, payload } + } +} + +impl TryFrom> for Vec +where + Ipld: From> + From, +{ + type Error = anyhow::Error; + + fn try_from(message: Message) -> Result { + let message_ipld = Ipld::from(message); + DagCborCodec.encode(&message_ipld) + } +} + +impl TryFrom> for Message +where + T: TryFrom, +{ + type Error = anyhow::Error; + + fn try_from(bytes: Vec) -> Result { + let ipld: Ipld = DagCborCodec.decode(&bytes)?; + ipld.try_into() + .map_err(|_| anyhow!("Could not convert IPLD to pubsub message.")) + } +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(message: Message) -> Self { + Ipld::Map(BTreeMap::from([ + (HEADER_KEY.into(), message.header.into()), + (PAYLOAD_KEY.into(), message.payload.into()), + ])) + } +} + +impl TryFrom for Message +where + T: TryFrom, +{ + type Error = anyhow::Error; + + fn try_from(ipld: Ipld) -> Result { + let map = from_ipld::>(ipld)?; + + let header = map + .get(HEADER_KEY) + .ok_or_else(|| anyhow!("missing {HEADER_KEY}"))? + .to_owned() + .try_into()?; + + let payload = map + .get(PAYLOAD_KEY) + .ok_or_else(|| anyhow!("missing {PAYLOAD_KEY}"))? + .to_owned() + .try_into()?; + + Ok(Message { header, payload }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct Header { + nonce: Nonce, +} + +impl From

for Ipld { + fn from(header: Header) -> Self { + Ipld::Map(BTreeMap::from([( + NONCE_KEY.into(), + header.nonce.to_owned().into(), + )])) + } +} + +impl TryFrom for Header { + type Error = anyhow::Error; + + fn try_from(ipld: Ipld) -> Result { + let map = from_ipld::>(ipld)?; + + let nonce = map + .get(NONCE_KEY) + .ok_or_else(|| anyhow!("Missing {NONCE_KEY}"))? + .try_into()?; + + Ok(Header { nonce }) + } +} + +impl TryFrom
for Vec { + type Error = anyhow::Error; + + fn try_from(header: Header) -> Result { + let header_ipld = Ipld::from(header); + DagCborCodec.encode(&header_ipld) + } +} + +impl TryFrom> for Header { + type Error = anyhow::Error; + + fn try_from(bytes: Vec) -> Result { + let ipld: Ipld = DagCborCodec.decode(&bytes)?; + ipld.try_into() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{test_utils, Receipt}; + + #[test] + fn pubsub_message_rountrip() { + let (_, receipt) = test_utils::receipt::receipts(); + let message = Message::new(receipt.clone()); + let bytes: Vec = message + .try_into() + .expect("Could not serialize message into bytes"); + + let parsed = + Message::::try_from(bytes).expect("Could not deserialize message from bytes"); + + assert_eq!(receipt, parsed.payload); + } +} diff --git a/homestar-runtime/src/network/swarm.rs b/homestar-runtime/src/network/swarm.rs index ff4f2392..642939a5 100644 --- a/homestar-runtime/src/network/swarm.rs +++ b/homestar-runtime/src/network/swarm.rs @@ -270,7 +270,7 @@ pub(crate) enum ComposedEvent { #[derive(Debug)] pub(crate) enum TopicMessage { /// Receipt topic, wrapping [Receipt]. - CapturedReceipt(Receipt), + CapturedReceipt(pubsub::Message), } /// Custom behaviours for [Swarm]. @@ -316,8 +316,9 @@ impl ComposedBehaviour { if let Some(gossipsub) = self.gossipsub.as_mut() { let id_topic = gossipsub::IdentTopic::new(topic); // Make this a match once we have other topics. - let TopicMessage::CapturedReceipt(receipt) = msg; - let msg_bytes: Vec = receipt.try_into()?; + let TopicMessage::CapturedReceipt(message) = msg; + let msg_bytes: Vec = message.try_into()?; + if gossipsub .mesh_peers(&TopicHash::from_raw(topic)) .peekable() From ce0c49f361a079cdb8b278501349627c2750aeb2 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 15 Nov 2023 17:34:06 -0500 Subject: [PATCH 35/42] fix: schedule + db conflict (#437) --- flake.nix | 1 + homestar-runtime/Cargo.toml | 2 +- homestar-runtime/src/db.rs | 49 +++-- homestar-runtime/src/runner/response.rs | 1 + homestar-runtime/src/scheduler.rs | 53 ++--- homestar-runtime/src/settings.rs | 4 +- homestar-runtime/src/worker.rs | 12 +- homestar-runtime/src/workflow.rs | 184 +++++++++++++----- homestar-runtime/src/workflow/info.rs | 18 +- homestar-runtime/src/workflow/settings.rs | 2 +- .../tests/fixtures/test-workflow-jco.json | 24 +++ .../fixtures/test-workflow-no-awaits1.json | 29 +++ .../fixtures/test-workflow-no-awaits2.json | 54 +++++ 13 files changed, 324 insertions(+), 109 deletions(-) create mode 100644 homestar-runtime/tests/fixtures/test-workflow-jco.json create mode 100644 homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json create mode 100644 homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json diff --git a/flake.nix b/flake.nix index 4bb6df8f..cbf11fa8 100644 --- a/flake.nix +++ b/flake.nix @@ -62,6 +62,7 @@ cargo-udeps cargo-watch rustup + tokio-console twiggy wasm-tools ]; diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index 41df70e5..e991ca26 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -218,7 +218,7 @@ wait-timeout = "0.2" [features] default = ["wasmtime-default", "ipfs", "monitoring", "websocket-notify"] -dev = ["ansi-logs", "ipfs", "monitoring", "websocket-notify"] +dev = ["ansi-logs", "console", "ipfs", "monitoring", "websocket-notify"] ansi-logs = ["tracing-logfmt/ansi_logs"] console = ["dep:console-subscriber"] ipfs = ["dep:ipfs-api", "dep:ipfs-api-backend-hyper"] diff --git a/homestar-runtime/src/db.rs b/homestar-runtime/src/db.rs index 5aeca7f1..02f14fc7 100644 --- a/homestar-runtime/src/db.rs +++ b/homestar-runtime/src/db.rs @@ -119,13 +119,15 @@ pub trait Database: Send + Sync + Clone { receipt: Receipt, conn: &mut Connection, ) -> Result { - let receipt = conn.transaction::<_, diesel::result::Error, _>(|conn| { - let returned = Self::store_receipt(receipt, conn)?; - Self::store_workflow_receipt(workflow_cid, returned.cid(), conn)?; - Ok(returned) - })?; - - Ok(receipt) + conn.transaction::<_, diesel::result::Error, _>(|conn| { + if let Some(returned) = Self::store_receipt(receipt.clone(), conn)? { + Self::store_workflow_receipt(workflow_cid, returned.cid(), conn)?; + Ok(returned) + } else { + Self::store_workflow_receipt(workflow_cid, receipt.cid(), conn)?; + Ok(receipt) + } + }) } /// Store receipt given a connection to the database pool. @@ -134,12 +136,13 @@ pub trait Database: Send + Sync + Clone { fn store_receipt( receipt: Receipt, conn: &mut Connection, - ) -> Result { + ) -> Result, diesel::result::Error> { diesel::insert_into(schema::receipts::table) .values(&receipt) .on_conflict(schema::receipts::cid) .do_nothing() .get_result(conn) + .optional() } /// Store receipts given a connection to the Database pool. @@ -148,13 +151,17 @@ pub trait Database: Send + Sync + Clone { conn: &mut Connection, ) -> Result { receipts.iter().try_fold(0, |acc, receipt| { - let res = diesel::insert_into(schema::receipts::table) + if let Some(res) = diesel::insert_into(schema::receipts::table) .values(receipt) .on_conflict(schema::receipts::cid) .do_nothing() - .execute(conn)?; - - Ok::<_, diesel::result::Error>(acc + res) + .execute(conn) + .optional()? + { + Ok::<_, diesel::result::Error>(acc + res) + } else { + Ok(acc) + } }) } @@ -208,11 +215,17 @@ pub trait Database: Send + Sync + Clone { workflow: workflow::Stored, conn: &mut Connection, ) -> Result { - diesel::insert_into(schema::workflows::table) + if let Some(stored) = diesel::insert_into(schema::workflows::table) .values(&workflow) .on_conflict(schema::workflows::cid) .do_nothing() .get_result(conn) + .optional()? + { + Ok(stored) + } else { + Ok(workflow) + } } /// Store workflow [Cid] and [Receipt] [Cid] in the database for inner join. @@ -220,7 +233,7 @@ pub trait Database: Send + Sync + Clone { workflow_cid: Cid, receipt_cid: Cid, conn: &mut Connection, - ) -> Result { + ) -> Result, diesel::result::Error> { let value = StoredReceipt::new(Pointer::new(workflow_cid), Pointer::new(receipt_cid)); diesel::insert_into(schema::workflows_receipts::table) .values(&value) @@ -230,6 +243,7 @@ pub trait Database: Send + Sync + Clone { )) .do_nothing() .execute(conn) + .optional() } /// Store series of receipts for a workflow [Cid] in the @@ -244,8 +258,11 @@ pub trait Database: Send + Sync + Clone { conn: &mut Connection, ) -> Result { receipts.iter().try_fold(0, |acc, receipt| { - let res = Self::store_workflow_receipt(workflow_cid, *receipt, conn)?; - Ok::<_, diesel::result::Error>(acc + res) + if let Some(res) = Self::store_workflow_receipt(workflow_cid, *receipt, conn)? { + Ok::<_, diesel::result::Error>(acc + res) + } else { + Ok(acc) + } }) } diff --git a/homestar-runtime/src/runner/response.rs b/homestar-runtime/src/runner/response.rs index ea08e08b..c6b8b781 100644 --- a/homestar-runtime/src/runner/response.rs +++ b/homestar-runtime/src/runner/response.rs @@ -67,6 +67,7 @@ impl show::ConsoleTable for AckWorkflow { fn echo_table(&self) -> Result<(), std::io::Error> { let table = self.table(); + let mut resource_table = Table::new( self.resources .iter() diff --git a/homestar-runtime/src/scheduler.rs b/homestar-runtime/src/scheduler.rs index 0dcc2f1e..1970bc0a 100644 --- a/homestar-runtime/src/scheduler.rs +++ b/homestar-runtime/src/scheduler.rs @@ -107,6 +107,7 @@ impl<'a> TaskScheduler<'a> { let schedule: &mut Schedule<'a> = mut_graph.schedule.as_mut(); let schedule_length = schedule.len(); let mut resources_to_fetch = vec![]; + let linkmap = LinkMap::>::new(); let resume = 'resume: { for (idx, vec) in schedule.iter().enumerate().rev() { @@ -128,29 +129,22 @@ impl<'a> TaskScheduler<'a> { if let Ok(pointers) = folded_pointers { match Db::find_instruction_pointers(&pointers, conn) { Ok(found) => { - let linkmap = found.iter().fold( - LinkMap::>::new(), - |mut map, receipt| { - if let Some(idx) = resources_to_fetch - .iter() - .position(|(cid, _rsc)| cid == &receipt.instruction().cid()) - { - resources_to_fetch.swap_remove(idx); - } - - let _ = map.insert( - receipt.instruction().cid(), - receipt.output_as_arg(), - ); - - map - }, - ); + let linkmap = found.iter().fold(linkmap.clone(), |mut map, receipt| { + if let Some(idx) = resources_to_fetch + .iter() + .position(|(cid, _rsc)| cid == &receipt.instruction().cid()) + { + resources_to_fetch.swap_remove(idx); + } + + let _ = map + .insert(receipt.instruction().cid(), receipt.output_as_arg()); + + map + }); if found.len() == vec.len() { break 'resume ControlFlow::Break((idx + 1, linkmap)); - } else if !found.is_empty() && found.len() < vec.len() { - break 'resume ControlFlow::Break((idx, linkmap)); } else { continue; } @@ -195,7 +189,7 @@ impl<'a> TaskScheduler<'a> { } _ => Ok(SchedulerContext { scheduler: Self { - linkmap: Arc::new(LinkMap::>::new().into()), + linkmap: Arc::new(linkmap.into()), ran: None, run: schedule.to_vec(), resume_step: None, @@ -204,6 +198,21 @@ impl<'a> TaskScheduler<'a> { }), } } + + /// TODO + #[allow(dead_code)] + pub(crate) fn ran_length(&self) -> usize { + self.ran + .as_ref() + .map(|ran| ran.iter().flatten().collect::>().len()) + .unwrap_or_default() + } + + /// TODO + #[allow(dead_code)] + pub(crate) fn run_length(&self) -> usize { + self.run.iter().flatten().collect::>().len() + } } #[cfg(test)] @@ -304,7 +313,7 @@ mod test { let mut conn = db.conn().unwrap(); let stored_receipt = MemoryDb::store_receipt(receipt.clone(), &mut conn).unwrap(); - assert_eq!(receipt, stored_receipt); + assert_eq!(receipt, stored_receipt.unwrap()); let workflow = Workflow::new(vec![task1.clone(), task2.clone()]); let fetch_fn = |_rscs: FnvHashSet| { diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index d29f8704..de0fffe2 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -220,7 +220,7 @@ impl Default for Monitoring { fn default() -> Self { Self { process_collector_interval: Duration::from_millis(5000), - console_subscriber_port: 5555, + console_subscriber_port: 6669, } } } @@ -229,7 +229,7 @@ impl Default for Monitoring { impl Default for Monitoring { fn default() -> Self { Self { - console_subscriber_port: 5555, + console_subscriber_port: 6669, } } } diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index c80a9b31..d3e528a4 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -193,6 +193,7 @@ where debug!(cid = cid.to_string(), "found in in-memory linkmap"); Ok(result.to_owned()) } else if let Some(bytes) = resources.read().await.get(&Resource::Cid(cid)) { + debug!(cid = cid.to_string(), "found in resources"); Ok(InstructionResult::Ok(Arg::Ipld(Ipld::Bytes( bytes.to_vec(), )))) @@ -248,11 +249,11 @@ where // Replay previous receipts if subscriptions are on. #[cfg(feature = "websocket-notify")] { - if scheduler.ran.as_ref().is_some_and(|ran| !ran.is_empty()) { + if scheduler.ran_length() > 0 { info!( workflow_cid = self.workflow_info.cid.to_string(), "{} tasks left to run, sending last batch for workflow", - scheduler.ran.as_ref().unwrap().len() + scheduler.run_length() ); let mut pointers = Vec::new(); for batch in scheduler @@ -362,8 +363,7 @@ where instruction_ptr, invocation_ptr, receipt_meta, - additional_meta, - )), + additional_meta)), Err(err) => Err( anyhow!("cannot execute wasm module: {err}")) .with_context(|| { @@ -382,7 +382,6 @@ where // Concurrently add handles to Runner's running set. running_tasks.append_or_insert(self.workflow_info.cid(), handles); - while let Some(res) = task_set.join_next().await { let (executed, instruction_ptr, invocation_ptr, receipt_meta, add_meta) = match res { @@ -396,8 +395,8 @@ where break; } }; - let output_to_store = Ipld::try_from(executed)?; + let output_to_store = Ipld::try_from(executed)?; let invocation_receipt = InvocationReceipt::new( invocation_ptr, InstructionResult::Ok(output_to_store), @@ -425,6 +424,7 @@ where let stored_receipt = Db::commit_receipt(self.workflow_info.cid, receipt, &mut self.db.conn()?)?; + debug!( cid = self.workflow_info.cid.to_string(), "commited to database" diff --git a/homestar-runtime/src/workflow.rs b/homestar-runtime/src/workflow.rs index db56ee32..a01b48ce 100644 --- a/homestar-runtime/src/workflow.rs +++ b/homestar-runtime/src/workflow.rs @@ -151,54 +151,69 @@ impl<'a> Builder<'a> { fn aot(self) -> anyhow::Result> { let lookup_table = self.lookup_table()?; + let (mut dag, unawaits, awaited, resources) = + self.into_inner().tasks().into_iter().enumerate().try_fold( + (Dag::default(), vec![], vec![], IndexMap::new()), + |(mut dag, mut unawaits, mut awaited, mut resources), (i, task)| { + let instr_cid = task.instruction_cid()?; + debug!("instruction cid: {}", instr_cid); - let (dag, resources) = self.into_inner().tasks().into_iter().enumerate().try_fold( - (Dag::default(), IndexMap::new()), - |(mut dag, mut resources), (i, task)| { - let instr_cid = task.instruction_cid()?; - debug!("instruction cid: {}", instr_cid); - - // Clone as we're owning the struct going backward. - let ptr: Pointer = Invocation::::from(task.clone()).try_into()?; - - let RunInstruction::Expanded(instr) = task.into_instruction() else { - bail!("workflow tasks/instructions must be expanded / inlined") - }; - - resources - .entry(instr_cid) - .or_insert_with(|| vec![Resource::Url(instr.resource().to_owned())]); - - let parsed = instr.input().parse()?; - let reads = parsed - .args() - .deferreds() - .fold(vec![], |mut in_flow_reads, cid| { - if let Some(v) = lookup_table.get(&cid) { - in_flow_reads.push(*v) - } - // TODO: else, it's a Promise from another task outside - // of the workflow. - in_flow_reads - }); + // Clone as we're owning the struct going backward. + let ptr: Pointer = Invocation::::from(task.clone()).try_into()?; + + let RunInstruction::Expanded(instr) = task.into_instruction() else { + bail!("workflow tasks/instructions must be expanded / inlined") + }; - parsed.args().links().for_each(|cid| { resources .entry(instr_cid) - .and_modify(|prev_rscs| { - prev_rscs.push(Resource::Cid(cid.to_owned())); - }) - .or_insert_with(|| vec![Resource::Cid(cid.to_owned())]); - }); + .or_insert_with(|| vec![Resource::Url(instr.resource().to_owned())]); + let parsed = instr.input().parse()?; + let reads = parsed + .args() + .deferreds() + .fold(vec![], |mut in_flow_reads, cid| { + if let Some(v) = lookup_table.get(&cid) { + in_flow_reads.push(*v) + } + // TODO: else, it's a Promise from another task outside + // of the workflow. + in_flow_reads + }); + + parsed.args().links().for_each(|cid| { + resources + .entry(instr_cid) + .and_modify(|prev_rscs| { + prev_rscs.push(Resource::Cid(cid.to_owned())); + }) + .or_insert_with(|| vec![Resource::Cid(cid.to_owned())]); + }); - let node = Node::new(Vertex::new(instr.to_owned(), parsed, ptr)) - .with_name(instr_cid.to_string()) - .with_result(i); + let node = Node::new(Vertex::new(instr.to_owned(), parsed, ptr)) + .with_name(instr_cid.to_string()) + .with_result(i); - dag.add_node(node.with_reads(reads)); - Ok::<_, anyhow::Error>((dag, resources)) - }, - )?; + if !reads.is_empty() { + dag.add_node(node.with_reads(reads.clone())); + awaited.extend(reads); + } else { + unawaits.push(node); + } + + Ok::<_, anyhow::Error>((dag, unawaits, awaited, resources)) + }, + )?; + + for mut node in unawaits.clone().into_iter() { + if node.get_results().any(|r| awaited.contains(r)) { + dag.add_node(node); + } else { + // set barrier for non-awaited nodes + node.set_barrier(1); + dag.add_node(node); + } + } Ok(AOTContext { dag, @@ -268,13 +283,13 @@ impl IndexedResources { /// Iterate over all [Resource]s as references. #[allow(dead_code)] pub(crate) fn iter(&self) -> impl Iterator { - self.0.values().flatten().dedup() + self.0.values().flatten().unique() } /// Iterate over all [Resource]s. #[allow(dead_code)] pub(crate) fn into_iter(self) -> impl Iterator { - self.0.into_values().flatten().dedup() + self.0.into_values().flatten().unique() } } @@ -367,8 +382,15 @@ where mod test { use super::*; use homestar_core::{ + ipld::DagCbor, test_utils, - workflow::{config::Resources, instruction::RunInstruction, prf::UcanPrf, Task}, + workflow::{ + config::Resources, + instruction::RunInstruction, + pointer::{Await, AwaitResult}, + prf::UcanPrf, + Ability, Input, Task, + }, }; use std::path::Path; @@ -459,7 +481,7 @@ mod test { let (instruction1, instruction2, instruction3) = test_utils::workflow::related_wasm_instructions::(); let task1 = Task::new( - RunInstruction::Expanded(instruction1), + RunInstruction::Expanded(instruction1.clone()), config.clone().into(), UcanPrf::default(), ); @@ -477,17 +499,56 @@ mod test { let (instruction4, _) = test_utils::workflow::wasm_instruction_with_nonce::(); let task4 = Task::new( RunInstruction::Expanded(instruction4), + config.clone().into(), + UcanPrf::default(), + ); + + let (instruction5, _) = test_utils::workflow::wasm_instruction_with_nonce::(); + let task5 = Task::new( + RunInstruction::Expanded(instruction5), + config.clone().into(), + UcanPrf::default(), + ); + + let promise1 = Await::new( + Pointer::new(instruction1.clone().to_cid().unwrap()), + AwaitResult::Ok, + ); + + let dep_instr = Instruction::new( + instruction1.resource().to_owned(), + Ability::from("wasm/run"), + Input::::Ipld(Ipld::Map(BTreeMap::from([ + ("func".into(), Ipld::String("add_two".to_string())), + ( + "args".into(), + Ipld::List(vec![Ipld::try_from(promise1.clone()).unwrap()]), + ), + ]))), + ); + + let task6 = Task::new( + RunInstruction::Expanded(dep_instr), config.into(), UcanPrf::default(), ); - let tasks = vec![task1.clone(), task2.clone(), task3.clone(), task4.clone()]; + let tasks = vec![ + task6.clone(), + task1.clone(), + task2.clone(), + task3.clone(), + task4.clone(), + task5.clone(), + ]; let workflow = Workflow::new(tasks); let instr1 = task1.instruction_cid().unwrap().to_string(); let instr2 = task2.instruction_cid().unwrap().to_string(); let instr3 = task3.instruction_cid().unwrap().to_string(); let instr4 = task4.instruction_cid().unwrap().to_string(); + let instr5 = task5.instruction_cid().unwrap().to_string(); + let instr6 = task6.instruction_cid().unwrap().to_string(); let builder = Builder::new(workflow); let schedule = builder.graph().unwrap().schedule; @@ -510,11 +571,32 @@ mod test { assert!( nodes == vec![ - format!("{instr1}, {instr4}"), - instr2.clone(), - instr3.clone() + format!("{instr1}"), + format!("{instr6}, {instr2}"), + format!("{instr3}"), + format!("{instr4}, {instr5}") ] - || nodes == vec![format!("{instr4}, {instr1}"), instr2, instr3] + || nodes + == vec![ + format!("{instr1}"), + format!("{instr6}, {instr2}"), + format!("{instr3}"), + format!("{instr5}, {instr4}") + ] + || nodes + == vec![ + format!("{instr1}"), + format!("{instr2}, {instr6}"), + format!("{instr3}"), + format!("{instr4}, {instr5}") + ] + || nodes + == vec![ + format!("{instr1}"), + format!("{instr2}, {instr6}"), + format!("{instr3}"), + format!("{instr5}, {instr4}") + ] ); } } diff --git a/homestar-runtime/src/workflow/info.rs b/homestar-runtime/src/workflow/info.rs index 19330e10..671cce7e 100644 --- a/homestar-runtime/src/workflow/info.rs +++ b/homestar-runtime/src/workflow/info.rs @@ -279,17 +279,15 @@ impl Info { "workflow information not available in the database" ); - let result = Db::store_workflow( - Stored::new( - Pointer::new(workflow_cid), - Some(name.into_string()), - workflow_len as i32, - resources, - timestamp, - ), - &mut conn, - )?; + let stored = Stored::new( + Pointer::new(workflow_cid), + Some(name.into_string()), + workflow_len as i32, + resources, + timestamp, + ); + let result = Db::store_workflow(stored.clone(), &mut conn)?; let workflow_info = Self::default(result); // spawn a task to retrieve the workflow info from the diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index b2dbd834..9ea3e682 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -21,7 +21,7 @@ impl Default for Settings { retries: 3, retry_max_delay: Duration::new(60, 0), retry_initial_delay: Duration::from_millis(500), - p2p_timeout: Duration::new(5, 0), + p2p_timeout: Duration::from_millis(500), timeout: Duration::new(3600, 0), } } diff --git a/homestar-runtime/tests/fixtures/test-workflow-jco.json b/homestar-runtime/tests/fixtures/test-workflow-jco.json new file mode 100644 index 00000000..877c991f --- /dev/null +++ b/homestar-runtime/tests/fixtures/test-workflow-jco.json @@ -0,0 +1,24 @@ +{ + "name": "componentize", + "workflow": { + "tasks": [ + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": ["hello", 10], + "func": "sum" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://" + } + } + ] + } +} diff --git a/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json b/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json new file mode 100644 index 00000000..cc0407fc --- /dev/null +++ b/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json @@ -0,0 +1,29 @@ +{ + "tasks": [ + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm" + }, + 150, + 150, + 100, + 100 + ], + "func": "crop" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + } + } + ] +} diff --git a/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json b/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json new file mode 100644 index 00000000..d7d6a2e2 --- /dev/null +++ b/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json @@ -0,0 +1,54 @@ +{ + "tasks": [ + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm" + }, + 10, + 10, + 101, + 100 + ], + "func": "crop" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + } + }, + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm" + }, + 150, + 150, + 100, + 100 + ], + "func": "crop" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + } + } + ] +} From 0b3787502f47074c5ee69566dd99f1698416763c Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Thu, 16 Nov 2023 12:44:01 -0500 Subject: [PATCH 36/42] fix: remove unnecessary decode (#438) Fix: remove unnecessary decode of already utf8-ed string from wasmval. --- .../tests/fixtures/test-workflow-jco.json | 39 +++++++++---------- homestar-wasm/src/wasmtime/ipld.rs | 28 ++++++------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/homestar-runtime/tests/fixtures/test-workflow-jco.json b/homestar-runtime/tests/fixtures/test-workflow-jco.json index 877c991f..85bbaeb3 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-jco.json +++ b/homestar-runtime/tests/fixtures/test-workflow-jco.json @@ -1,24 +1,21 @@ { - "name": "componentize", - "workflow": { - "tasks": [ - { - "cause": null, - "meta": { - "memory": 4294967296, - "time": 100000 - }, - "prf": [], - "run": { - "input": { - "args": ["hello", 10], - "func": "sum" - }, - "nnc": "", - "op": "wasm/run", - "rsc": "ipfs://" - } + "tasks": [ + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": ["hello", 10], + "func": "sum" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeibawnb3pytqmky4ph37hj7y7qosqcneofjextqq55zhxfniiletfu" } - ] - } + } + ] } diff --git a/homestar-wasm/src/wasmtime/ipld.rs b/homestar-wasm/src/wasmtime/ipld.rs index b0b1d237..3f6273c8 100644 --- a/homestar-wasm/src/wasmtime/ipld.rs +++ b/homestar-wasm/src/wasmtime/ipld.rs @@ -12,11 +12,7 @@ use crate::error::{InterpreterError, TagsError}; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use itertools::{FoldWhile::Done, Itertools}; use libipld::{ - cid::{ - self, - multibase::{self, Base}, - Cid, - }, + cid::{self, multibase::Base, Cid}, Ipld, }; use rust_decimal::{ @@ -345,9 +341,6 @@ impl TryFrom for Ipld { type Error = InterpreterError; fn try_from(val: RuntimeVal) -> Result { - fn base_64_bytes(s: &str) -> Result, multibase::Error> { - Base::Base64.decode(s) - } fn cid(s: &str) -> Result { Cid::try_from(s) } @@ -360,8 +353,6 @@ impl TryFrom for Ipld { s => { if let Ok(cid) = cid(&s) { Ipld::Link(cid) - } else if let Ok(decoded) = base_64_bytes(&s) { - Ipld::Bytes(decoded) } else { Ipld::String(s) } @@ -704,11 +695,22 @@ mod test { fn try_bytes_roundtrip() { let bytes = b"hell0".to_vec(); let ipld = Ipld::Bytes(bytes.clone()); - let encoded_cid = Base::Base64.encode(bytes); - let runtime = RuntimeVal::new(Val::String(Box::from(encoded_cid))); + + let ty = test_utils::component::setup_component("(list u8)".to_string(), 8); + let val_list = ty + .unwrap_list() + .new_val(Box::new([ + Val::U8(104), + Val::U8(101), + Val::U8(108), + Val::U8(108), + Val::U8(48), + ])) + .unwrap(); + let runtime = RuntimeVal::new(val_list); assert_eq!( - RuntimeVal::try_from(ipld.clone(), &InterfaceType::Any).unwrap(), + RuntimeVal::try_from(ipld.clone(), &InterfaceType::Type(ty)).unwrap(), runtime ); From 032427e76376ad893cfd86e7f591e6590a46db1a Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:04:49 -0800 Subject: [PATCH 37/42] chore: Remove databases after test runs (#444) # Description This PR implements the following changes: - [x] Remove temporary databases after test runs ## Type of change - [x] Refactor (non-breaking change that updates existing functionality) ## Test plan (required) The tests should pass and leave no databases behind. --- homestar-runtime/tests/cli.rs | 16 ++- homestar-runtime/tests/metrics.rs | 6 +- homestar-runtime/tests/network.rs | 115 ++++++++++++++---- .../tests/network/notification.rs | 10 +- 4 files changed, 112 insertions(+), 35 deletions(-) diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 72005dca..550b0263 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -111,12 +111,13 @@ fn test_server_not_running_serial() -> Result<()> { #[test] #[file_serial] fn test_server_serial() -> Result<()> { + const DB: &str = "test_server_serial.db"; let _ = stop_homestar(); Command::new(BIN.as_os_str()) .arg("start") .arg("-db") - .arg("homestar.db") + .arg(DB) .assert() .failure(); @@ -125,7 +126,7 @@ fn test_server_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_v6.toml") .arg("--db") - .arg("homestar.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -163,6 +164,7 @@ fn test_server_serial() -> Result<()> { let _ = kill_homestar(homestar_proc, None); let _ = stop_homestar(); + remove_db(DB); Ok(()) } @@ -170,7 +172,7 @@ fn test_server_serial() -> Result<()> { #[test] #[file_serial] fn test_workflow_run_serial() -> Result<()> { - const DB: &str = "homestar_test_cli_test_workflow_run_serial.db"; + const DB: &str = "test_workflow_run_serial.db"; let _ = stop_homestar(); @@ -230,6 +232,7 @@ fn test_workflow_run_serial() -> Result<()> { #[file_serial] #[cfg(not(windows))] fn test_daemon_serial() -> Result<()> { + const DB: &str = "test_daemon_serial.db"; let _ = stop_homestar(); Command::new(BIN.as_os_str()) @@ -237,7 +240,7 @@ fn test_daemon_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_v4.toml") .arg("-d") - .env("DATABASE_URL", "homestar.db") + .env("DATABASE_URL", DB) .stdout(Stdio::piped()) .assert() .success(); @@ -259,6 +262,7 @@ fn test_daemon_serial() -> Result<()> { let _ = stop_homestar(); let _ = kill_homestar_daemon(); + remove_db(DB); Ok(()) } @@ -266,6 +270,7 @@ fn test_daemon_serial() -> Result<()> { #[test] #[file_serial] fn test_server_v4_serial() -> Result<()> { + const DB: &str = "test_server_v4_serial.db"; let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) @@ -273,7 +278,7 @@ fn test_server_v4_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_v4.toml") .arg("--db") - .arg("homestar.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -297,6 +302,7 @@ fn test_server_v4_serial() -> Result<()> { let _ = Command::new(BIN.as_os_str()).arg("stop").output(); let _ = kill_homestar(homestar_proc, None); let _ = stop_homestar(); + remove_db(DB); Ok(()) } diff --git a/homestar-runtime/tests/metrics.rs b/homestar-runtime/tests/metrics.rs index 0288bf9f..5dbb19e2 100644 --- a/homestar-runtime/tests/metrics.rs +++ b/homestar-runtime/tests/metrics.rs @@ -16,6 +16,8 @@ const METRICS_URL: &str = "http://localhost:4020"; #[test] #[file_serial] fn test_metrics_serial() -> Result<()> { + use crate::utils::remove_db; + fn sample_metrics() -> Option { let body = retry( Exponential::from_millis(500).take(20), @@ -41,6 +43,7 @@ fn test_metrics_serial() -> Result<()> { .map(|sample| sample.value.to_owned()) } + const DB: &str = "test_metrics_serial.db"; let _ = stop_homestar(); let mut homestar_proc = Command::new(BIN.as_os_str()) @@ -48,7 +51,7 @@ fn test_metrics_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_metrics.toml") .arg("--db") - .arg("homestar.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -86,6 +89,7 @@ fn test_metrics_serial() -> Result<()> { let _ = kill_homestar(homestar_proc, None); let _ = stop_homestar(); + remove_db(DB); Ok(()) } diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index 72bd6a8e..c4d78acc 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -1,5 +1,5 @@ use crate::utils::{ - check_lines_for, count_lines_where, kill_homestar, retrieve_output, stop_homestar, + check_lines_for, count_lines_where, kill_homestar, remove_db, retrieve_output, stop_homestar, wait_for_socket_connection, wait_for_socket_connection_v6, BIN_NAME, }; use anyhow::Result; @@ -23,6 +23,7 @@ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)) #[test] #[file_serial] fn test_libp2p_generates_peer_id_serial() -> Result<()> { + const DB: &str = "test_libp2p_generates_peer_id_serial.db"; let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) @@ -30,7 +31,7 @@ fn test_libp2p_generates_peer_id_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -52,12 +53,15 @@ fn test_libp2p_generates_peer_id_serial() -> Result<()> { assert!(logs_expected); + remove_db(DB); + Ok(()) } #[test] #[file_serial] fn test_libp2p_listens_on_address_serial() -> Result<()> { + const DB: &str = "test_libp2p_listens_on_address_serial.db"; let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) @@ -65,7 +69,7 @@ fn test_libp2p_listens_on_address_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -88,12 +92,15 @@ fn test_libp2p_listens_on_address_serial() -> Result<()> { assert!(logs_expected); + remove_db(DB); + Ok(()) } #[test] #[file_serial] fn test_rpc_listens_on_address_serial() -> Result<()> { + const DB: &str = "test_rpc_listens_on_address_serial.db"; let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) @@ -101,7 +108,7 @@ fn test_rpc_listens_on_address_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -117,12 +124,15 @@ fn test_rpc_listens_on_address_serial() -> Result<()> { assert!(logs_expected); + remove_db(DB); + Ok(()) } #[test] #[file_serial] fn test_websocket_listens_on_address_serial() -> Result<()> { + const DB: &str = "test_websocket_listens_on_address_serial.db"; let _ = stop_homestar(); let homestar_proc = Command::new(BIN.as_os_str()) @@ -130,7 +140,7 @@ fn test_websocket_listens_on_address_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -146,12 +156,16 @@ fn test_websocket_listens_on_address_serial() -> Result<()> { assert!(logs_expected); + remove_db(DB); + Ok(()) } #[test] #[file_serial] fn test_libp2p_connect_known_peers_serial() -> Result<()> { + const DB1: &str = "test_libp2p_connect_known_peers_serial1.db"; + const DB2: &str = "test_libp2p_connect_known_peers_serial2.db"; let _ = stop_homestar(); // Start two nodes configured to listen at 127.0.0.1 each with their own port. @@ -165,7 +179,7 @@ fn test_libp2p_connect_known_peers_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -184,7 +198,7 @@ fn test_libp2p_connect_known_peers_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -264,12 +278,17 @@ fn test_libp2p_connect_known_peers_serial() -> Result<()> { assert!(one_in_dht_routing_table); assert!(two_connected_to_one); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { + const DB1: &str = "test_libp2p_connect_after_mdns_discovery_serial1.db"; + const DB2: &str = "test_libp2p_connect_after_mdns_discovery_serial2.db"; let _ = stop_homestar(); // Start two nodes each configured to listen at 0.0.0.0 with no known peers. @@ -283,7 +302,7 @@ fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_mdns1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -302,7 +321,7 @@ fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_mdns2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -382,12 +401,18 @@ fn test_libp2p_connect_after_mdns_discovery_serial() -> Result<()> { assert!(one_addded_to_dht); assert!(one_in_dht_routing_table); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { + const DB1: &str = "test_libp2p_connect_rendezvous_discovery_serial1.db"; + const DB2: &str = "test_libp2p_connect_rendezvous_discovery_serial2.db"; + const DB3: &str = "test_libp2p_connect_rendezvous_discovery_serial3.db"; let _ = stop_homestar(); // Start a rendezvous server @@ -400,7 +425,7 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -420,7 +445,7 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -444,7 +469,7 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous3.toml") .arg("--db") - .arg("homestar3.db") + .arg(DB3) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -515,12 +540,18 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { assert!(one_in_dht_routing_table); assert!(two_connected_to_one); + remove_db(DB1); + remove_db(DB2); + remove_db(DB3); + Ok(()) } #[test] #[file_serial] fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { + const DB1: &str = "test_libp2p_disconnect_mdns_discovery_serial1.db"; + const DB2: &str = "test_libp2p_disconnect_mdns_discovery_serial2.db"; let _ = stop_homestar(); // Start two nodes each configured to listen at 0.0.0.0 with no known peers. @@ -534,7 +565,7 @@ fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_mdns1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -553,7 +584,7 @@ fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_mdns2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -593,12 +624,17 @@ fn test_libp2p_disconnect_mdns_discovery_serial() -> Result<()> { assert!(two_disconnected_from_one); assert!(two_removed_from_dht_table); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { + const DB1: &str = "test_libp2p_disconnect_known_peers_serial1.db"; + const DB2: &str = "test_libp2p_disconnect_known_peers_serial2.db"; let _ = stop_homestar(); // Start two nodes configured to listen at 127.0.0.1 each with their own port. @@ -612,7 +648,7 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -631,7 +667,7 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_network2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -671,12 +707,18 @@ fn test_libp2p_disconnect_known_peers_serial() -> Result<()> { assert!(two_disconnected_from_one); assert!(!two_removed_from_dht_table); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { + const DB1: &str = "test_libp2p_disconnect_rendezvous_discovery_serial1.db"; + const DB2: &str = "test_libp2p_disconnect_rendezvous_discovery_serial2.db"; + const DB3: &str = "test_libp2p_disconnect_rendezvous_discovery_serial3.db"; let _ = stop_homestar(); // Start a rendezvous server @@ -689,7 +731,7 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -709,7 +751,7 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -733,7 +775,7 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous3.toml") .arg("--db") - .arg("homestar3.db") + .arg(DB3) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -774,12 +816,18 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { assert!(two_disconnected_from_one); assert!(two_removed_from_dht_table); + remove_db(DB1); + remove_db(DB2); + remove_db(DB3); + Ok(()) } #[test] #[file_serial] fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { + const DB1: &str = "test_libp2p_rendezvous_renew_registration_serial1.db"; + const DB2: &str = "test_libp2p_rendezvous_renew_registration_serial2.db"; let _ = stop_homestar(); // Start a rendezvous server @@ -792,7 +840,7 @@ fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -812,7 +860,7 @@ fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous4.toml") .arg("--db") - .arg("homestar4.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -851,12 +899,17 @@ fn test_libp2p_rendezvous_renew_registration_serial() -> Result<()> { assert!(server_registration_count > 1); assert!(client_registration_count > 1); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { + const DB1: &str = "test_libp2p_rendezvous_rediscovery_serial1.db"; + const DB2: &str = "test_libp2p_rendezvous_rediscovery_serial2.db"; let _ = stop_homestar(); // Start a rendezvous server @@ -869,7 +922,7 @@ fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -889,7 +942,7 @@ fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous5.toml") .arg("--db") - .arg("homestar5.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -928,12 +981,18 @@ fn test_libp2p_rendezvous_rediscovery_serial() -> Result<()> { assert!(server_discovery_count > 1); assert!(client_discovery_count > 1); + remove_db(DB1); + remove_db(DB2); + Ok(()) } #[test] #[file_serial] fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { + const DB1: &str = "test_libp2p_rendezvous_rediscover_on_expiration_serial1.db"; + const DB2: &str = "test_libp2p_rendezvous_rediscover_on_expiration_serial2.db"; + const DB3: &str = "test_libp2p_rendezvous_rediscover_on_expiration_serial3.db"; let _ = stop_homestar(); // Start a rendezvous server @@ -946,7 +1005,7 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -966,7 +1025,7 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous6.toml") .arg("--db") - .arg("homestar6.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -993,7 +1052,7 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_rendezvous3.toml") .arg("--db") - .arg("homestar3.db") + .arg(DB3) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -1033,5 +1092,9 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { assert!(server_discovery_count > 1); assert!(client_discovery_count > 1); + remove_db(DB1); + remove_db(DB2); + remove_db(DB3); + Ok(()) } diff --git a/homestar-runtime/tests/network/notification.rs b/homestar-runtime/tests/network/notification.rs index 41e6263b..273efe28 100644 --- a/homestar-runtime/tests/network/notification.rs +++ b/homestar-runtime/tests/network/notification.rs @@ -1,5 +1,5 @@ use crate::utils::{ - kill_homestar, stop_homestar, wait_for_socket_connection, TimeoutFutureExt, BIN_NAME, + kill_homestar, remove_db, stop_homestar, wait_for_socket_connection, TimeoutFutureExt, BIN_NAME, }; use anyhow::Result; use jsonrpsee::{ @@ -23,6 +23,8 @@ const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_network_events"; #[test] #[file_serial] fn test_connection_notifications_serial() -> Result<()> { + const DB1: &str = "test_connection_notifications_serial1.db"; + const DB2: &str = "test_connection_notifications_serial2.db"; let _ = stop_homestar(); let homestar_proc1 = Command::new(BIN.as_os_str()) @@ -34,7 +36,7 @@ fn test_connection_notifications_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_notification1.toml") .arg("--db") - .arg("homestar1.db") + .arg(DB1) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -73,7 +75,7 @@ fn test_connection_notifications_serial() -> Result<()> { .arg("-c") .arg("tests/fixtures/test_notification2.toml") .arg("--db") - .arg("homestar2.db") + .arg(DB2) .stdout(Stdio::piped()) .spawn() .unwrap(); @@ -109,6 +111,8 @@ fn test_connection_notifications_serial() -> Result<()> { } let _ = kill_homestar(homestar_proc1, None); + remove_db(DB1); + remove_db(DB2); }); Ok(()) From c858c68c240718ba2d47157e673559d641fa3a9d Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Thu, 16 Nov 2023 19:52:41 -0500 Subject: [PATCH 38/42] chore: send_async where possible (#445) Includes: - a rename of `asyncboundedchannel*` to `asyncchannel` to accomodate unbounded channels - remove receipt logging with too much output --- .github/workflows/builds.yml | 4 +- homestar-runtime/src/event_handler.rs | 18 ++--- homestar-runtime/src/event_handler/cache.rs | 2 +- homestar-runtime/src/event_handler/channel.rs | 30 +++++---- homestar-runtime/src/event_handler/event.rs | 11 +-- .../src/event_handler/swarm_event.rs | 3 +- homestar-runtime/src/network/rpc.rs | 16 ++--- homestar-runtime/src/network/webserver.rs | 12 ++-- homestar-runtime/src/network/webserver/rpc.rs | 6 +- homestar-runtime/src/runner.rs | 67 +++++++++---------- homestar-runtime/src/settings.rs | 2 +- homestar-runtime/src/test_utils/event.rs | 19 +++--- .../src/test_utils/worker_builder.rs | 19 +++--- homestar-runtime/src/worker.rs | 14 ++-- homestar-runtime/src/workflow/info.rs | 10 +-- 15 files changed, 117 insertions(+), 116 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 1ab38b7b..ae1ee665 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -15,8 +15,8 @@ on: types: [published] # for debugging - pull_request: - branches: [ '**' ] + # pull_request: + # branches: [ '**' ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index f1838afb..8d9ef988 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -32,7 +32,7 @@ pub(crate) use cache::{setup_cache, CacheValue}; pub(crate) use error::RequestResponseError; pub(crate) use event::Event; -type P2PSender = channel::AsyncBoundedChannelSender; +type P2PSender = channel::AsyncChannelSender; /// Handler trait for [EventHandler] events. #[async_trait] @@ -57,8 +57,8 @@ pub(crate) struct EventHandler { db: DB, swarm: Swarm, cache: Arc>, - sender: Arc>, - receiver: channel::AsyncBoundedChannelReceiver, + sender: Arc>, + receiver: channel::AsyncChannelReceiver, query_senders: FnvHashMap)>, connections: Connections, request_response_senders: FnvHashMap, @@ -82,8 +82,8 @@ pub(crate) struct EventHandler { db: DB, swarm: Swarm, cache: Arc>, - sender: Arc>, - receiver: channel::AsyncBoundedChannelReceiver, + sender: Arc>, + receiver: channel::AsyncChannelReceiver, query_senders: FnvHashMap)>, connections: Connections, request_response_senders: FnvHashMap, @@ -116,10 +116,10 @@ where fn setup_channel( settings: &settings::Node, ) -> ( - channel::AsyncBoundedChannelSender, - channel::AsyncBoundedChannelReceiver, + channel::AsyncChannelSender, + channel::AsyncChannelReceiver, ) { - channel::AsyncBoundedChannel::with(settings.network.events_buffer_len) + channel::AsyncChannel::with(settings.network.events_buffer_len) } /// Create an [EventHandler] with channel sender/receiver defaults. @@ -202,7 +202,7 @@ where pub(crate) async fn shutdown(&mut self) {} /// Get a [Arc]'ed copy of the [EventHandler] channel sender. - pub(crate) fn sender(&self) -> Arc> { + pub(crate) fn sender(&self) -> Arc> { self.sender.clone() } diff --git a/homestar-runtime/src/event_handler/cache.rs b/homestar-runtime/src/event_handler/cache.rs index c05386c0..a5227dad 100644 --- a/homestar-runtime/src/event_handler/cache.rs +++ b/homestar-runtime/src/event_handler/cache.rs @@ -49,7 +49,7 @@ pub(crate) enum DispatchEvent { } pub(crate) fn setup_cache( - sender: Arc>, + sender: Arc>, ) -> Cache { let eviction_listener = move |_key: Arc, val: CacheValue, cause: RemovalCause| { let tx = Arc::clone(&sender); diff --git a/homestar-runtime/src/event_handler/channel.rs b/homestar-runtime/src/event_handler/channel.rs index b8d798c3..4eab0e24 100644 --- a/homestar-runtime/src/event_handler/channel.rs +++ b/homestar-runtime/src/event_handler/channel.rs @@ -12,21 +12,21 @@ pub type BoundedChannelReceiver = channel::Receiver; /// A bounded [crossbeam::channel] with a sender and receiver. #[allow(dead_code)] #[derive(Debug, Clone)] -pub struct BoundedChannel { +pub struct Channel { /// Sender for the channel. tx: channel::Sender, /// REceiver for the channel. rx: channel::Receiver, } -impl BoundedChannel { - /// Create a new [BoundedChannel] with a given capacity. +impl Channel { + /// Create a new [Channel] with a given capacity. pub fn with(capacity: usize) -> (BoundedChannelSender, BoundedChannelReceiver) { let (tx, rx) = channel::bounded(capacity); (tx, rx) } - /// Create a oneshot (1) [BoundedChannel]. + /// Create a oneshot (1) [Channel]. pub fn oneshot() -> (BoundedChannelSender, BoundedChannelReceiver) { let (tx, rx) = channel::bounded(1); (tx, rx) @@ -34,30 +34,36 @@ impl BoundedChannel { } /// [flume::Sender] for a bounded [flume::bounded] channel. -pub type AsyncBoundedChannelSender = flume::Sender; +pub type AsyncChannelSender = flume::Sender; /// [flume::Receiver] for a bounded [flume::bounded] channel. -pub type AsyncBoundedChannelReceiver = flume::Receiver; +pub type AsyncChannelReceiver = flume::Receiver; /// A bounded [flume] channel with sender and receiver. #[allow(dead_code)] #[derive(Debug, Clone)] -pub struct AsyncBoundedChannel { +pub struct AsyncChannel { /// Sender for the channel. tx: flume::Sender, /// REceiver for the channel. rx: flume::Receiver, } -impl AsyncBoundedChannel { - /// Create a new [AsyncBoundedChannel] with a given capacity. - pub fn with(capacity: usize) -> (AsyncBoundedChannelSender, AsyncBoundedChannelReceiver) { +impl AsyncChannel { + /// Create a new [AsyncChannel] with a given capacity. + pub fn with(capacity: usize) -> (AsyncChannelSender, AsyncChannelReceiver) { let (tx, rx) = flume::bounded(capacity); (tx, rx) } - /// Create a oneshot (1) [BoundedChannel]. - pub fn oneshot() -> (AsyncBoundedChannelSender, AsyncBoundedChannelReceiver) { + /// Create an unbounded [AsyncChannel]. + pub fn unbounded() -> (AsyncChannelSender, AsyncChannelReceiver) { + let (tx, rx) = flume::unbounded(); + (tx, rx) + } + + /// Create a oneshot (1) [Channel]. + pub fn oneshot() -> (AsyncChannelSender, AsyncChannelReceiver) { let (tx, rx) = flume::bounded(1); (tx, rx) } diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 5ca10e11..8ab39f62 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -9,7 +9,7 @@ use crate::event_handler::notification::{ use crate::network::IpfsCli; use crate::{ db::Database, - event_handler::{channel::AsyncBoundedChannelSender, Handler, P2PSender, ResponseEvent}, + event_handler::{channel::AsyncChannelSender, Handler, P2PSender, ResponseEvent}, network::{ pubsub, swarm::{CapsuleTag, RequestResponseKey, TopicMessage}, @@ -95,7 +95,7 @@ pub(crate) enum Event { #[cfg(feature = "websocket-notify")] ReplayReceipts(Replay), /// General shutdown event. - Shutdown(AsyncBoundedChannelSender<()>), + Shutdown(AsyncChannelSender<()>), /// Find a [Record] in the DHT, e.g. a [Receipt]. /// /// [Record]: libp2p::kad::Record @@ -116,7 +116,7 @@ pub(crate) enum Event { /// Discover peers from a rendezvous node. DiscoverPeers(PeerId), /// TODO - GetListeners(AsyncBoundedChannelSender>), + GetListeners(AsyncChannelSender>), } const RENDEZVOUS_NAMESPACE: &str = "homestar"; @@ -134,10 +134,11 @@ impl Event { Event::Shutdown(tx) => { info!("event_handler server shutting down"); event_handler.shutdown().await; - let _ = tx.send(()); + let _ = tx.send_async(()).await; } Event::GetListeners(tx) => { - let _ = tx.send(event_handler.swarm.listeners().cloned().collect()); + let listeners = event_handler.swarm.listeners().cloned().collect(); + let _ = tx.send_async(listeners).await; } Event::FindRecord(record) => record.find(event_handler).await, Event::RemoveRecord(record) => record.remove(event_handler).await, diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index cadc2935..a9ced8d0 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -415,7 +415,8 @@ async fn handle_swarm_event( info!( peer_id = propagation_source.to_string(), message_id = message_id.to_string(), - "message received on receipts topic: {receipt}" + "message received on receipts topic: {}", + receipt.cid() ); // Store gossiped receipt. diff --git a/homestar-runtime/src/network/rpc.rs b/homestar-runtime/src/network/rpc.rs index fdef2b97..c9c046a2 100644 --- a/homestar-runtime/src/network/rpc.rs +++ b/homestar-runtime/src/network/rpc.rs @@ -1,7 +1,7 @@ //! RPC server implementation. use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelReceiver, AsyncBoundedChannelSender}, + channel::{AsyncChannel, AsyncChannelReceiver, AsyncChannelSender}, runner::{self, file::ReadWorkflow, response, RpcSender}, settings, }; @@ -34,7 +34,7 @@ pub(crate) enum ServerMessage { /// Message sent by the [Runner] to start a graceful shutdown. /// /// [Runner]: crate::Runner - GracefulShutdown(AsyncBoundedChannelSender<()>), + GracefulShutdown(AsyncChannelSender<()>), /// Message sent to start a [Workflow] run by reading a [Workflow] file. /// /// [Workflow]: homestar_core::Workflow @@ -71,9 +71,9 @@ pub(crate) struct Server { /// [SocketAddr] of the RPC server. pub(crate) addr: SocketAddr, /// Sender for messages to be sent to the RPC server. - pub(crate) sender: Arc>, + pub(crate) sender: Arc>, /// Receiver for messages sent to the RPC server. - pub(crate) receiver: AsyncBoundedChannelReceiver, + pub(crate) receiver: AsyncChannelReceiver, /// Sender for messages to be sent to the [Runner]. /// /// [Runner]: crate::Runner @@ -119,7 +119,7 @@ impl Interface for ServerHandler { name: Option, workflow_file: ReadWorkflow, ) -> Result, Error> { - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); self.runner_sender .send_async((ServerMessage::Run((name, workflow_file)), Some(tx))) .await @@ -158,7 +158,7 @@ impl Interface for ServerHandler { impl Server { /// Create a new instance of the RPC server. pub(crate) fn new(settings: &settings::Network, runner_sender: Arc) -> Self { - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); Self { addr: SocketAddr::new(settings.rpc_host, settings.rpc_port), sender: tx.into(), @@ -170,7 +170,7 @@ impl Server { } /// Return a RPC server channel sender. - pub(crate) fn sender(&self) -> Arc> { + pub(crate) fn sender(&self) -> Arc> { self.sender.clone() } @@ -205,7 +205,7 @@ impl Server { Ok(ServerMessage::GracefulShutdown(tx)) = self.receiver.recv_async() => { info!("RPC server shutting down"); drop(exit); - let _ = tx.send(()); + let _ = tx.send_async(()).await; } _ = fut => warn!("RPC server exited unexpectedly"), } diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index 64bcea61..9cfba640 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -254,7 +254,7 @@ mod test { use super::*; #[cfg(feature = "websocket-notify")] use crate::event_handler::notification::ReceiptNotification; - use crate::{channel::AsyncBoundedChannel, db::Database, settings::Settings}; + use crate::{channel::AsyncChannel, db::Database, settings::Settings}; #[cfg(feature = "websocket-notify")] use homestar_core::{ ipld::DagJson, @@ -290,7 +290,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); + let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -332,7 +332,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); + let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -365,7 +365,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); + let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -442,7 +442,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); + let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); @@ -476,7 +476,7 @@ mod test { runner.runtime.block_on(async { let server = Server::new(settings.node().network()).unwrap(); let metrics_hdl = metrics_handle(settings).await; - let (runner_tx, _runner_rx) = AsyncBoundedChannel::oneshot(); + let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); let ws_url = format!("ws://{}", server.addr); diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index d3e93a7f..c56b3c65 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -3,7 +3,7 @@ use super::notifier::{self, Header, Notifier, SubscriptionTyp}; #[allow(unused_imports)] use super::{listener, prom::PrometheusData, Message}; #[cfg(feature = "websocket-notify")] -use crate::channel::AsyncBoundedChannel; +use crate::channel::AsyncChannel; use crate::runner::WsSender; #[cfg(feature = "websocket-notify")] use anyhow::anyhow; @@ -138,7 +138,7 @@ impl JsonRpc { #[cfg(not(test))] module.register_async_method(HEALTH_ENDPOINT, |_, ctx| async move { - let (tx, rx) = crate::channel::AsyncBoundedChannel::oneshot(); + let (tx, rx) = crate::channel::AsyncChannel::oneshot(); ctx.runner_sender .send_async((Message::GetNodeInfo, Some(tx))) .await @@ -211,7 +211,7 @@ impl JsonRpc { |params, pending, ctx| async move { match params.one::>() { Ok(listener::Run { name, workflow }) => { - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); ctx.runner_sender .send_async(( Message::RunWorkflow((name.clone(), workflow.clone())), diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 42bf19a9..0cffccae 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -4,7 +4,7 @@ #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelReceiver, AsyncBoundedChannelSender}, + channel::{AsyncChannel, AsyncChannelReceiver, AsyncChannelSender}, db::Database, event_handler::{Event, EventHandler}, network::{rpc, swarm, webserver}, @@ -62,28 +62,28 @@ pub(crate) trait ModifiedSet { fn append_or_insert(&self, cid: Cid, handles: Vec); } -/// [AsyncBoundedChannelSender] for RPC server messages. -pub(crate) type RpcSender = AsyncBoundedChannelSender<( +/// [AsyncChannelSender] for RPC server messages. +pub(crate) type RpcSender = AsyncChannelSender<( rpc::ServerMessage, - Option>, + Option>, )>; -/// [AsyncBoundedChannelReceiver] for RPC server messages. -pub(crate) type RpcReceiver = AsyncBoundedChannelReceiver<( +/// [AsyncChannelReceiver] for RPC server messages. +pub(crate) type RpcReceiver = AsyncChannelReceiver<( rpc::ServerMessage, - Option>, + Option>, )>; -/// [AsyncBoundedChannelSender] for sending messages websocket server clients. -pub(crate) type WsSender = AsyncBoundedChannelSender<( +/// [AsyncChannelSender] for sending messages websocket server clients. +pub(crate) type WsSender = AsyncChannelSender<( webserver::Message, - Option>, + Option>, )>; -/// [AsyncBoundedChannelReceiver] for receiving messages from websocket server clients. -pub(crate) type WsReceiver = AsyncBoundedChannelReceiver<( +/// [AsyncChannelReceiver] for receiving messages from websocket server clients. +pub(crate) type WsReceiver = AsyncChannelReceiver<( webserver::Message, - Option>, + Option>, )>; impl ModifiedSet for RunningTaskSet { @@ -102,7 +102,7 @@ impl ModifiedSet for RunningTaskSet { /// [Workflows]: homestar_core::Workflow #[derive(Debug)] pub struct Runner { - event_sender: Arc>, + event_sender: Arc>, expiration_queue: Rc>>, node_info: StaticNodeInfo, running_tasks: Arc, @@ -115,23 +115,23 @@ pub struct Runner { impl Runner { /// Setup bounded, MPSC channel for top-level RPC communication. pub(crate) fn setup_rpc_channel(capacity: usize) -> (RpcSender, RpcReceiver) { - AsyncBoundedChannel::with(capacity) + AsyncChannel::with(capacity) } /// Setup bounded, MPSC channel for top-level Worker communication. pub(crate) fn setup_worker_channel( capacity: usize, ) -> ( - AsyncBoundedChannelSender, - AsyncBoundedChannelReceiver, + AsyncChannelSender, + AsyncChannelReceiver, ) { - AsyncBoundedChannel::with(capacity) + AsyncChannel::with(capacity) } /// MPSC channel for sending and receiving messages through to/from /// websocket server clients. pub(crate) fn setup_ws_mpsc_channel(capacity: usize) -> (WsSender, WsReceiver) { - AsyncBoundedChannel::with(capacity) + AsyncChannel::with(capacity) } /// Initialize and start the Homestar [Runner] / runtime. @@ -262,11 +262,11 @@ impl Runner { Ok(ControlFlow::Continue(rpc::ServerMessage::Skip)) => {}, Ok(ControlFlow::Continue(msg @ rpc::ServerMessage::RunAck(_))) => { info!("sending message to rpc server"); - let _ = oneshot_tx.send(msg); + let _ = oneshot_tx.send_async(msg).await; }, Err(err) => { error!(err=?err, "error handling rpc message"); - let _ = oneshot_tx.send(rpc::ServerMessage::RunErr(err.into())); + let _ = oneshot_tx.send_async(rpc::ServerMessage::RunErr(err.into())).await; }, _ => {} } @@ -287,25 +287,25 @@ impl Runner { ).await { Ok(data) => { info!("sending message to rpc server"); - let _ = oneshot_tx.send(webserver::Message::AckWorkflow((data.info.cid, data.name))); + let _ = oneshot_tx.send_async(webserver::Message::AckWorkflow((data.info.cid, data.name))).await; } Err(err) => { error!(err=?err, "error handling ws message"); - let _ = oneshot_tx.send(webserver::Message::RunErr(err.into())); + let _ = oneshot_tx.send_async(webserver::Message::RunErr(err.into())).await; } } } (webserver::Message::GetNodeInfo, Some(oneshot_tx)) => { info!("getting node info"); - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); let _ = self.event_sender.send_async(Event::GetListeners(tx)).await; let dyn_node_info = if let Ok(listeners) = rx.recv_deadline(Instant::now() + self.settings.node.network.webserver_timeout) { DynamicNodeInfo::new(listeners) } else { DynamicNodeInfo::new(vec![]) }; - let _ = oneshot_tx.send(webserver::Message::AckNodeInfo((self.node_info.clone(), dyn_node_info))); + let _ = oneshot_tx.send_async(webserver::Message::AckNodeInfo((self.node_info.clone(), dyn_node_info))).await; } _ => () } @@ -365,10 +365,10 @@ impl Runner { Ok(()) } - /// [AsyncBoundedChannelSender] of the event-handler. + /// [AsyncChannelSender] of the event-handler. /// /// [EventHandler]: crate::EventHandler - pub(crate) fn event_sender(&self) -> Arc> { + pub(crate) fn event_sender(&self) -> Arc> { self.event_sender.clone() } @@ -516,10 +516,10 @@ impl Runner { /// c) Running workers async fn shutdown( &self, - rpc_sender: Arc>, + rpc_sender: Arc>, ws_hdl: ServerHandle, ) -> Result<()> { - let (shutdown_sender, shutdown_receiver) = AsyncBoundedChannel::oneshot(); + let (shutdown_sender, shutdown_receiver) = AsyncChannel::oneshot(); let _ = rpc_sender .send_async(rpc::ServerMessage::GracefulShutdown(shutdown_sender)) .await; @@ -529,7 +529,7 @@ impl Runner { let _ = ws_hdl.stop(); ws_hdl.clone().stopped().await; - let (shutdown_sender, shutdown_receiver) = AsyncBoundedChannel::oneshot(); + let (shutdown_sender, shutdown_receiver) = AsyncChannel::oneshot(); let _ = self .event_sender .send_async(Event::Shutdown(shutdown_sender)) @@ -550,7 +550,6 @@ impl Runner { db: impl Database + 'static, now: time::Instant, ) -> Result> { - info!("received message: {:?}", msg); match msg { rpc::ServerMessage::ShutdownCmd => { info!("RPC shutdown signal received, shutting down runner"); @@ -593,7 +592,7 @@ impl Runner { workflow: Workflow<'static, Arg>, workflow_settings: workflow::Settings, name: Option, - runner_sender: AsyncBoundedChannelSender, + runner_sender: AsyncChannelSender, db: impl Database + 'static, ) -> Result { let worker = { @@ -677,8 +676,8 @@ struct WorkflowData { #[derive(Debug)] struct Channels { - rpc: Arc>, - runner: AsyncBoundedChannelSender, + rpc: Arc>, + runner: AsyncChannelSender, } #[cfg(test)] diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index de0fffe2..928a63f5 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -298,7 +298,7 @@ impl Default for Network { webserver_host: Uri::from_static("127.0.0.1"), webserver_port: 1337, webserver_timeout: Duration::new(120, 0), - websocket_capacity: 1024, + websocket_capacity: 2048, websocket_receiver_timeout: Duration::from_millis(30_000), workflow_quorum: 3, keypair_config: PubkeyConfig::Random, diff --git a/homestar-runtime/src/test_utils/event.rs b/homestar-runtime/src/test_utils/event.rs index d1e7ae51..4e8660ad 100644 --- a/homestar-runtime/src/test_utils/event.rs +++ b/homestar-runtime/src/test_utils/event.rs @@ -1,26 +1,23 @@ use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelReceiver, AsyncBoundedChannelSender}, + channel::{AsyncChannel, AsyncChannelReceiver, AsyncChannelSender}, event_handler::Event, settings, worker::WorkerMessage, }; -/// Create an [AsynBoundedChannelSender], [AsyncBoundedChannelReceiver] pair for [Event]s. +/// Create an [AsynBoundedChannelSender], [AsyncChannelReceiver] pair for [Event]s. pub(crate) fn setup_event_channel( settings: settings::Node, -) -> ( - AsyncBoundedChannelSender, - AsyncBoundedChannelReceiver, -) { - AsyncBoundedChannel::with(settings.network.events_buffer_len) +) -> (AsyncChannelSender, AsyncChannelReceiver) { + AsyncChannel::with(settings.network.events_buffer_len) } -/// Create an [AsyncBoundedChannelSender], [AsyncBoundedChannelReceiver] pair for worker messages. +/// Create an [AsyncChannelSender], [AsyncChannelReceiver] pair for worker messages. pub(crate) fn setup_worker_channel( settings: settings::Node, ) -> ( - AsyncBoundedChannelSender, - AsyncBoundedChannelReceiver, + AsyncChannelSender, + AsyncChannelReceiver, ) { - AsyncBoundedChannel::with(settings.network.events_buffer_len) + AsyncChannel::with(settings.network.events_buffer_len) } diff --git a/homestar-runtime/src/test_utils/worker_builder.rs b/homestar-runtime/src/test_utils/worker_builder.rs index 5029310c..f9c8e24c 100644 --- a/homestar-runtime/src/test_utils/worker_builder.rs +++ b/homestar-runtime/src/test_utils/worker_builder.rs @@ -4,7 +4,7 @@ use super::{db::MemoryDb, event}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ - channel::AsyncBoundedChannelSender, + channel::AsyncChannelSender, db::Database, event_handler::Event, settings, @@ -29,9 +29,9 @@ use libipld::Cid; #[cfg(feature = "ipfs")] pub(crate) struct WorkerBuilder<'a> { db: MemoryDb, - event_sender: AsyncBoundedChannelSender, + event_sender: AsyncChannelSender, ipfs: IpfsCli, - runner_sender: AsyncBoundedChannelSender, + runner_sender: AsyncChannelSender, name: Option, workflow: Workflow<'a, Arg>, workflow_settings: workflow::Settings, @@ -41,8 +41,8 @@ pub(crate) struct WorkerBuilder<'a> { #[cfg(not(feature = "ipfs"))] pub(crate) struct WorkerBuilder<'a> { db: MemoryDb, - event_sender: AsyncBoundedChannelSender, - runner_sender: AsyncBoundedChannelSender, + event_sender: AsyncChannelSender, + runner_sender: AsyncChannelSender, name: Option, workflow: Workflow<'a, Arg>, workflow_settings: workflow::Settings, @@ -173,13 +173,10 @@ impl<'a> WorkerBuilder<'a> { self } - /// Build a [Worker] with a specific Event [AsyncBoundedChannelSender]. + /// Build a [Worker] with a specific Event [AsyncChannelSender]. #[allow(dead_code)] - pub(crate) fn with_event_sender( - mut self, - event_sender: AsyncBoundedChannelSender, - ) -> Self { - self.event_sender = event_sender.into(); + pub(crate) fn with_event_sender(mut self, event_sender: AsyncChannelSender) -> Self { + self.event_sender = event_sender; self } diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index d3e528a4..05f55af3 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -7,7 +7,7 @@ #[cfg(feature = "websocket-notify")] use crate::event_handler::event::Replay; use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelSender}, + channel::{AsyncChannel, AsyncChannelSender}, db::Database, event_handler::{ event::{Captured, QueryRecord}, @@ -61,8 +61,8 @@ pub(crate) enum WorkerMessage { #[allow(missing_debug_implementations)] pub(crate) struct Worker<'a, DB: Database> { pub(crate) graph: Arc>, - pub(crate) event_sender: Arc>, - pub(crate) runner_sender: AsyncBoundedChannelSender, + pub(crate) event_sender: Arc>, + pub(crate) runner_sender: AsyncChannelSender, pub(crate) db: DB, pub(crate) workflow_name: FastStr, pub(crate) workflow_info: Arc, @@ -83,8 +83,8 @@ where settings: workflow::Settings, // Name would be runner specific, separated from core workflow spec. name: Option, - event_sender: Arc>, - runner_sender: AsyncBoundedChannelSender, + event_sender: Arc>, + runner_sender: AsyncChannelSender, db: DB, ) -> Result> { let p2p_timeout = settings.p2p_timeout; @@ -181,7 +181,7 @@ where linkmap: Arc>>>, resources: Arc>>>, db: impl Database, - event_sender: Arc>, + event_sender: Arc>, ) -> Result, ResolveError> { info!( workflow_cid = workflow_cid.to_string(), @@ -203,7 +203,7 @@ where Ok(found) => Ok(found.output_as_arg()), Err(_) => { debug!("no related instruction receipt found in the DB"); - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); let _ = event_sender .send_async(Event::FindRecord(QueryRecord::with( cid, diff --git a/homestar-runtime/src/workflow/info.rs b/homestar-runtime/src/workflow/info.rs index 671cce7e..7110a64d 100644 --- a/homestar-runtime/src/workflow/info.rs +++ b/homestar-runtime/src/workflow/info.rs @@ -1,6 +1,6 @@ use super::IndexedResources; use crate::{ - channel::{AsyncBoundedChannel, AsyncBoundedChannelSender}, + channel::{AsyncChannel, AsyncChannelSender}, db::{Connection, Database}, event_handler::{ event::QueryRecord, @@ -263,7 +263,7 @@ impl Info { name: FastStr, resources: IndexedResources, p2p_timeout: Duration, - event_sender: Arc>, + event_sender: Arc>, mut conn: Connection, ) -> Result<(Self, NaiveDateTime)> { let timestamp = Utc::now().naive_utc(); @@ -311,7 +311,7 @@ impl Info { pub(crate) async fn gather<'a>( workflow_cid: Cid, p2p_timeout: Duration, - event_sender: Arc>, + event_sender: Arc>, mut conn: Option, handle_timeout_fn: Option) -> Result>, ) -> Result { @@ -343,11 +343,11 @@ impl Info { async fn retrieve_from_query<'a>( workflow_cid: Cid, p2p_timeout: Duration, - event_sender: Arc>, + event_sender: Arc>, conn: Option, handle_timeout_fn: Option) -> Result>, ) -> Result { - let (tx, rx) = AsyncBoundedChannel::oneshot(); + let (tx, rx) = AsyncChannel::oneshot(); event_sender .send_async(Event::FindRecord(QueryRecord::with( workflow_cid, From 3168793ef3a5f776c484b2b0ee1111d718e6977e Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:20:41 -0800 Subject: [PATCH 39/42] refactor: Refactor and split-up settings (#451) # Description This PR implements the following changes: - [x] Break up `[node.network]` settings into smaller groups - [x] Move `[monitoring]` settings to `[node.monitoring]` - [x] Add `defaults.toml` with complete settings matching the defaults - [x] Reduce the settings passed to consumers to the minimum necessary - [x] Update test fixtures using the new settings - [x] Update and add comments A complete version of the updated settings will look something like: ```toml [node] gc_interval = 1800 shutdown_timeout = 20 [node.database] url = "homestar.db" max_pool_size = 100 [node.monitoring] process_collector_interval = 5000 console_subscriber_port = 6669 [node.network] events_buffer_len = 1024 poll_cache_interval = 1000 [node.network.ipfs] host = "127.0.0.1" port = 5001 [node.network.libp2p] listen_address = "/ip4/0.0.0.0/tcp/0" node_addresses = [] announce_addresses = [] transport_connection_timeout = 60 max_connected_peers = 32 max_announce_addresses = 10 [node.network.libp2p.mdns] enable = true enable_ipv6 = false query_interval = 300 ttl = 540 [node.network.libp2p.rendezvous] enable_client = true enable_server = false registration_ttl = 7200 discovery_interval = 600 [node.network.libp2p.pubsub] enable = false duplication_cache_time = 1 heartbeat = 60 idle_timeout = 86400 max_transmit_size = 10485760 mesh_n_low = 1 mesh_n_high = 10 mesh_n = 2 mesh_outbound_min = 1 [node.network.libp2p.dht] p2p_provider_timeout = 30 receipt_quorum = 2 workflow_quorum = 3 [node.network.keypair_config] random = { } [node.network.metrics] port = 4000 [node.network.rpc] host = "::1" port = 3030 max_connections = 10 server_timeout = 120 [node.network.webserver] host = "127.0.0.1" port = 1337 timeout = 120 websocket_capacity = 2048 websocket_receiver_timeout = 30000 ``` ## Link to issue Closes #442 ## Type of change - [x] Refactor (non-breaking change that updates existing functionality) - [ ] This change requires a documentation update. (It does. Will do.) - [x] Comments have been added/updated. ## Test plan (required) All existing tests should pass with the updated fixtures. --- docker/Dockerfile | 4 +- examples/websocket-relay/src/main.rs | 2 +- homestar-runtime/config/settings.toml | 10 +- homestar-runtime/fixtures/defaults.toml | 74 ++++ homestar-runtime/fixtures/settings.toml | 6 +- homestar-runtime/src/event_handler.rs | 56 +-- homestar-runtime/src/main.rs | 4 +- homestar-runtime/src/metrics/exporter.rs | 2 +- homestar-runtime/src/network/pubsub.rs | 18 +- homestar-runtime/src/network/rpc.rs | 6 +- homestar-runtime/src/network/swarm.rs | 35 +- homestar-runtime/src/network/webserver.rs | 33 +- homestar-runtime/src/runner.rs | 28 +- homestar-runtime/src/settings.rs | 391 +++++++++--------- .../src/settings/libp2p_config.rs | 190 +++++++++ .../src/test_utils/proc_macro/src/lib.rs | 12 +- .../tests/fixtures/test_gossip1.toml | 32 +- .../tests/fixtures/test_gossip2.toml | 32 +- .../tests/fixtures/test_mdns1.toml | 28 +- .../tests/fixtures/test_mdns2.toml | 28 +- .../tests/fixtures/test_metrics.toml | 20 +- .../tests/fixtures/test_network1.toml | 32 +- .../tests/fixtures/test_network2.toml | 32 +- .../tests/fixtures/test_notification1.toml | 32 +- .../tests/fixtures/test_notification2.toml | 32 +- .../tests/fixtures/test_rendezvous1.toml | 32 +- .../tests/fixtures/test_rendezvous2.toml | 28 +- .../tests/fixtures/test_rendezvous3.toml | 28 +- .../tests/fixtures/test_rendezvous4.toml | 32 +- .../tests/fixtures/test_rendezvous5.toml | 32 +- .../tests/fixtures/test_rendezvous6.toml | 32 +- homestar-runtime/tests/fixtures/test_v4.toml | 20 +- .../tests/fixtures/test_v4_alt.toml | 20 +- homestar-runtime/tests/fixtures/test_v6.toml | 20 +- .../tests/fixtures/test_windows_v4.toml | 20 +- .../tests/fixtures/test_workflow1.toml | 18 +- .../tests/fixtures/test_workflow2.toml | 18 +- 37 files changed, 933 insertions(+), 506 deletions(-) create mode 100644 homestar-runtime/fixtures/defaults.toml create mode 100644 homestar-runtime/src/settings/libp2p_config.rs diff --git a/docker/Dockerfile b/docker/Dockerfile index 53097d50..cbb504dc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -91,8 +91,8 @@ ARG rpc_port=3030 ARG ws_port=1337 ENV DATABASE_URL=${database_url} \ - HOMESTAR__NODE__NETWORK__RPC_HOST=${rpc_host} \ - HOMESTAR__NODE__NETWORK__RPC_PORT=${rpc_port} \ + HOMESTAR__NODE__NETWORK__RPC__HOST=${rpc_host} \ + HOMESTAR__NODE__NETWORK__RPC__PORT=${rpc_port} \ HOMESTAR__NODE__NETWORK__WS_PORT=${ws_port} EXPOSE ${rpc_port} ${ws_port} diff --git a/examples/websocket-relay/src/main.rs b/examples/websocket-relay/src/main.rs index 35b77ec0..d5b00051 100644 --- a/examples/websocket-relay/src/main.rs +++ b/examples/websocket-relay/src/main.rs @@ -10,7 +10,7 @@ use tracing::info; fn main() -> Result<()> { let settings = Settings::load().expect("runtime settings to be loaded"); - let _guard = Logger::init(settings.monitoring()); + let _guard = Logger::init(settings.node().monitoring()); // Just for example purposes, we're going to start the ipfs // daemon. Typically, these would be started separately. diff --git a/homestar-runtime/config/settings.toml b/homestar-runtime/config/settings.toml index 2be540be..291536a1 100644 --- a/homestar-runtime/config/settings.toml +++ b/homestar-runtime/config/settings.toml @@ -1,6 +1,8 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 5000 -metrics_port = 4000 -console_subscriber_port = 5555 +console_subscriber_port = 6669 -[node] +[node.network.metrics] +port = 4000 diff --git a/homestar-runtime/fixtures/defaults.toml b/homestar-runtime/fixtures/defaults.toml new file mode 100644 index 00000000..29aad8af --- /dev/null +++ b/homestar-runtime/fixtures/defaults.toml @@ -0,0 +1,74 @@ +[node] +gc_interval = 1800 +shutdown_timeout = 20 + +[node.database] +url = "homestar.db" +max_pool_size = 100 + +[node.monitoring] +process_collector_interval = 5000 +console_subscriber_port = 6669 + +[node.network] +events_buffer_len = 1024 +poll_cache_interval = 1000 + +[node.network.ipfs] +host = "127.0.0.1" +port = 5001 + +[node.network.libp2p] +listen_address = "/ip4/0.0.0.0/tcp/0" +node_addresses = [] +announce_addresses = [] +transport_connection_timeout = 60 +max_connected_peers = 32 +max_announce_addresses = 10 + +[node.network.libp2p.mdns] +enable = true +enable_ipv6 = false +query_interval = 300 +ttl = 540 + +[node.network.libp2p.rendezvous] +enable_client = true +enable_server = false +registration_ttl = 7200 +discovery_interval = 600 + +[node.network.libp2p.pubsub] +enable = true +duplication_cache_time = 1 +heartbeat = 60 +idle_timeout = 86400 +max_transmit_size = 10485760 +mesh_n_low = 1 +mesh_n_high = 10 +mesh_n = 2 +mesh_outbound_min = 1 + +[node.network.libp2p.dht] +p2p_provider_timeout = 30 +receipt_quorum = 2 +workflow_quorum = 3 + +[node.network.keypair_config] +random = {} + +[node.network.metrics] +port = 4000 + +[node.network.rpc] +host = "::1" +port = 3030 +max_connections = 10 +server_timeout = 120 + +[node.network.webserver] +host = "127.0.0.1" +port = 1337 +timeout = 120 +websocket_capacity = 2048 +websocket_receiver_timeout = 30000 diff --git a/homestar-runtime/fixtures/settings.toml b/homestar-runtime/fixtures/settings.toml index 9561abe7..5c11b12c 100644 --- a/homestar-runtime/fixtures/settings.toml +++ b/homestar-runtime/fixtures/settings.toml @@ -2,5 +2,9 @@ [node.network] events_buffer_len = 1000 -webserver_port = 9999 + +[node.network.webserver] +port = 9999 + +[node.network.libp2p] node_addresses = ["/ip4/127.0.0.1/tcp/9998/ws"] diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index 8d9ef988..fdc6e9d9 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -114,12 +114,12 @@ where DB: Database, { fn setup_channel( - settings: &settings::Node, + settings: &settings::Network, ) -> ( channel::AsyncChannelSender, channel::AsyncChannelReceiver, ) { - channel::AsyncChannel::with(settings.network.events_buffer_len) + channel::AsyncChannel::with(settings.events_buffer_len) } /// Create an [EventHandler] with channel sender/receiver defaults. @@ -127,16 +127,16 @@ where pub(crate) fn new( swarm: Swarm, db: DB, - settings: &settings::Node, + settings: &settings::Network, ws_evt_sender: webserver::Notifier, ws_workflow_sender: webserver::Notifier, ) -> Self { let (sender, receiver) = Self::setup_channel(settings); let sender = Arc::new(sender); Self { - receipt_quorum: settings.network.receipt_quorum, - workflow_quorum: settings.network.workflow_quorum, - p2p_provider_timeout: settings.network.p2p_provider_timeout, + receipt_quorum: settings.libp2p.dht.receipt_quorum, + workflow_quorum: settings.libp2p.dht.workflow_quorum, + p2p_provider_timeout: settings.libp2p.dht.p2p_provider_timeout, db, swarm, cache: Arc::new(setup_cache(sender.clone())), @@ -146,33 +146,37 @@ where request_response_senders: FnvHashMap::default(), connections: Connections { peers: FnvHashMap::default(), - max_peers: settings.network.max_connected_peers, + max_peers: settings.libp2p.max_connected_peers, }, rendezvous: Rendezvous { - registration_ttl: settings.network.rendezvous_registration_ttl, - discovery_interval: settings.network.rendezvous_discovery_interval, + registration_ttl: settings.libp2p.rendezvous.registration_ttl, + discovery_interval: settings.libp2p.rendezvous.discovery_interval, discovered_peers: FnvHashMap::default(), cookies: FnvHashMap::default(), }, - pubsub_enabled: settings.network.enable_pubsub, + pubsub_enabled: settings.libp2p.pubsub.enable, ws_evt_sender, ws_workflow_sender, - node_addresses: settings.network.node_addresses.clone(), - announce_addresses: settings.network.announce_addresses.clone(), - external_address_limit: settings.network.max_announce_addresses, - poll_cache_interval: settings.network.poll_cache_interval, + node_addresses: settings.libp2p.node_addresses.clone(), + announce_addresses: settings.libp2p.announce_addresses.clone(), + external_address_limit: settings.libp2p.max_announce_addresses, + poll_cache_interval: settings.poll_cache_interval, } } /// Create an [EventHandler] with channel sender/receiver defaults. #[cfg(not(feature = "websocket-notify"))] - pub(crate) fn new(swarm: Swarm, db: DB, settings: &settings::Node) -> Self { + pub(crate) fn new( + swarm: Swarm, + db: DB, + settings: &settings::Network, + ) -> Self { let (sender, receiver) = Self::setup_channel(settings); let sender = Arc::new(sender); Self { - receipt_quorum: settings.network.receipt_quorum, - workflow_quorum: settings.network.workflow_quorum, - p2p_provider_timeout: settings.network.p2p_provider_timeout, + receipt_quorum: settings.libp2p.dht.receipt_quorum, + workflow_quorum: settings.libp2p.dht.workflow_quorum, + p2p_provider_timeout: settings.libp2p.dht.p2p_provider_timeout, db, swarm, cache: Arc::new(setup_cache(sender.clone())), @@ -182,19 +186,19 @@ where request_response_senders: FnvHashMap::default(), connections: Connections { peers: FnvHashMap::default(), - max_peers: settings.network.max_connected_peers, + max_peers: settings.libp2p.max_connected_peers, }, rendezvous: Rendezvous { - registration_ttl: settings.network.rendezvous_registration_ttl, - discovery_interval: settings.network.rendezvous_discovery_interval, + registration_ttl: settings.libp2p.rendezvous.registration_ttl, + discovery_interval: settings.libp2p.rendezvous.discovery_interval, discovered_peers: FnvHashMap::default(), cookies: FnvHashMap::default(), }, - pubsub_enabled: settings.network.enable_pubsub, - node_addresses: settings.network.node_addresses.clone(), - announce_addresses: settings.network.announce_addresses.clone(), - external_address_limit: settings.network.max_announce_addresses, - poll_cache_interval: settings.network.poll_cache_interval, + pubsub_enabled: settings.libp2p.pubsub.enable, + node_addresses: settings.libp2p.node_addresses.clone(), + announce_addresses: settings.libp2p.announce_addresses.clone(), + external_address_limit: settings.libp2p.max_announce_addresses, + poll_cache_interval: settings.poll_cache_interval, } } diff --git a/homestar-runtime/src/main.rs b/homestar-runtime/src/main.rs index 4ba64d86..f9fce8f7 100644 --- a/homestar-runtime/src/main.rs +++ b/homestar-runtime/src/main.rs @@ -30,9 +30,9 @@ fn main() -> Result<()> { let _guard = if daemonize { daemon::start(daemon_dir.clone()) .expect("runner to be started as a daemon process"); - FileLogger::init(daemon_dir, settings.monitoring()) + FileLogger::init(daemon_dir, settings.node().monitoring()) } else { - Logger::init(settings.monitoring()) + Logger::init(settings.node().monitoring()) }; info!( diff --git a/homestar-runtime/src/metrics/exporter.rs b/homestar-runtime/src/metrics/exporter.rs index 52d52168..5c027aa4 100644 --- a/homestar-runtime/src/metrics/exporter.rs +++ b/homestar-runtime/src/metrics/exporter.rs @@ -16,7 +16,7 @@ pub(crate) fn setup_metrics_recorder( 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, ]; - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), settings.metrics_port); + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), settings.metrics.port); let (recorder, exporter) = PrometheusBuilder::new() .set_buckets_for_metric( diff --git a/homestar-runtime/src/network/pubsub.rs b/homestar-runtime/src/network/pubsub.rs index 051f76c7..fafe48a7 100644 --- a/homestar-runtime/src/network/pubsub.rs +++ b/homestar-runtime/src/network/pubsub.rs @@ -24,7 +24,7 @@ pub(crate) const RECEIPTS_TOPIC: &str = "receipts"; /// Setup [gossipsub] mesh protocol with default configuration. /// /// [gossipsub]: libp2p::gossipsub -pub(crate) fn new(keypair: Keypair, settings: &settings::Node) -> Result { +pub(crate) fn new(keypair: Keypair, settings: &settings::Pubsub) -> Result { // To content-address message, we can take the hash of message and use it as an ID. let message_id_fn = |message: &gossipsub::Message| { let mut s = DefaultHasher::new(); @@ -33,18 +33,18 @@ pub(crate) fn new(keypair: Keypair, settings: &settings::Node) -> Result) -> Self { let (tx, rx) = AsyncChannel::oneshot(); Self { - addr: SocketAddr::new(settings.rpc_host, settings.rpc_port), + addr: SocketAddr::new(settings.rpc.host, settings.rpc.port), sender: tx.into(), receiver: rx, runner_sender, - max_connections: settings.rpc_max_connections, - timeout: settings.rpc_server_timeout, + max_connections: settings.rpc.max_connections, + timeout: settings.rpc.server_timeout, } } diff --git a/homestar-runtime/src/network/swarm.rs b/homestar-runtime/src/network/swarm.rs index 642939a5..ce638854 100644 --- a/homestar-runtime/src/network/swarm.rs +++ b/homestar-runtime/src/network/swarm.rs @@ -35,9 +35,8 @@ use tracing::{info, warn}; pub(crate) const HOMESTAR_PROTOCOL_VER: &str = "homestar/0.0.1"; /// Build a new [Swarm] with a given transport and a tokio executor. -pub(crate) async fn new(settings: &settings::Node) -> Result> { +pub(crate) async fn new(settings: &settings::Network) -> Result> { let keypair = settings - .network .keypair_config .keypair() .with_context(|| "failed to generate/import keypair for libp2p".to_string())?; @@ -49,14 +48,14 @@ pub(crate) async fn new(settings: &settings::Node) -> Result Result Result Result<()> { // Listen-on given address - swarm.listen_on(settings.listen_address.to_string().parse()?)?; + swarm.listen_on(settings.libp2p.listen_address.to_string().parse()?)?; // Set Kademlia server mode swarm @@ -148,8 +147,8 @@ pub(crate) fn init( .set_mode(Some(kad::Mode::Server)); // add external addresses from settings - if !settings.announce_addresses.is_empty() { - for addr in settings.announce_addresses.iter() { + if !settings.libp2p.announce_addresses.is_empty() { + for addr in settings.libp2p.announce_addresses.iter() { swarm.add_external_address(addr.clone()); } } else { @@ -160,8 +159,8 @@ pub(crate) fn init( } // Dial nodes specified in settings. Failure here shouldn't halt node startup. - for (index, addr) in settings.node_addresses.iter().enumerate() { - if index < settings.max_connected_peers as usize { + for (index, addr) in settings.libp2p.node_addresses.iter().enumerate() { + if index < settings.libp2p.max_connected_peers as usize { let _ = swarm .dial(addr.clone()) // log dial failure and continue @@ -184,7 +183,7 @@ pub(crate) fn init( } } - if settings.enable_pubsub { + if settings.libp2p.pubsub.enable { // join `receipts` topic swarm .behaviour_mut() diff --git a/homestar-runtime/src/network/webserver.rs b/homestar-runtime/src/network/webserver.rs index 9cfba640..8a2e1c4f 100644 --- a/homestar-runtime/src/network/webserver.rs +++ b/homestar-runtime/src/network/webserver.rs @@ -112,11 +112,11 @@ impl Server { } #[cfg(feature = "websocket-notify")] - pub(crate) fn new(settings: &settings::Network) -> Result { + pub(crate) fn new(settings: &settings::Webserver) -> Result { let (evt_sender, _receiver) = Self::setup_channel(settings.websocket_capacity); let (msg_sender, _receiver) = Self::setup_channel(settings.websocket_capacity); - let host = IpAddr::from_str(&settings.webserver_host.to_string())?; - let port_setting = settings.webserver_port; + let host = IpAddr::from_str(&settings.host.to_string())?; + let port_setting = settings.port; let addr = if port_available(host, port_setting) { SocketAddr::from((host, port_setting)) } else { @@ -132,14 +132,14 @@ impl Server { evt_notifier: Notifier::new(evt_sender), workflow_msg_notifier: Notifier::new(msg_sender), receiver_timeout: settings.websocket_receiver_timeout, - webserver_timeout: settings.webserver_timeout, + webserver_timeout: settings.timeout, }) } #[cfg(not(feature = "websocket-notify"))] - pub(crate) fn new(settings: &settings::Network) -> Result { - let host = IpAddr::from_str(&settings.webserver_host.to_string())?; - let port_setting = settings.webserver_port; + pub(crate) fn new(settings: &settings::Webserver) -> Result { + let host = IpAddr::from_str(&settings.host.to_string())?; + let port_setting = settings.port; let addr = if port_available(host, port_setting) { SocketAddr::from((host, port_setting)) } else { @@ -153,7 +153,7 @@ impl Server { addr, capacity: settings.websocket_capacity, receiver_timeout: settings.websocket_receiver_timeout, - webserver_timeout: settings.webserver_timeout, + webserver_timeout: settings.timeout, }) } @@ -272,9 +272,10 @@ mod test { async fn metrics_handle(settings: Settings) -> PrometheusHandle { #[cfg(feature = "monitoring")] - let metrics_hdl = crate::metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); + let metrics_hdl = + crate::metrics::start(settings.node.monitoring(), settings.node.network()) + .await + .unwrap(); #[cfg(not(feature = "monitoring"))] let metrics_hdl = crate::metrics::start(settings.node.network()) @@ -288,7 +289,7 @@ mod test { fn ws_connect() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let server = Server::new(settings.node().network()).unwrap(); + let server = Server::new(settings.node().network().webserver()).unwrap(); let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -330,7 +331,7 @@ mod test { async fn ws_metrics_no_prefix() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let server = Server::new(settings.node().network()).unwrap(); + let server = Server::new(settings.node().network().webserver()).unwrap(); let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -363,7 +364,7 @@ mod test { async fn ws_subscribe_unsubscribe_network_events() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let server = Server::new(settings.node().network()).unwrap(); + let server = Server::new(settings.node().network().webserver()).unwrap(); let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -440,7 +441,7 @@ mod test { async fn ws_subscribe_workflow_incorrect_params() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let server = Server::new(settings.node().network()).unwrap(); + let server = Server::new(settings.node().network().webserver()).unwrap(); let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); @@ -474,7 +475,7 @@ mod test { async fn ws_subscribe_workflow_runner_timeout() { let TestRunner { runner, settings } = TestRunner::start(); runner.runtime.block_on(async { - let server = Server::new(settings.node().network()).unwrap(); + let server = Server::new(settings.node().network().webserver()).unwrap(); let metrics_hdl = metrics_handle(settings).await; let (runner_tx, _runner_rx) = AsyncChannel::oneshot(); server.start(runner_tx, metrics_hdl).await.unwrap(); diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index 0cffccae..ed6217a6 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -165,10 +165,10 @@ impl Runner { db: impl Database + 'static, runtime: tokio::runtime::Runtime, ) -> Result { - let swarm = runtime.block_on(swarm::new(settings.node()))?; + let swarm = runtime.block_on(swarm::new(settings.node().network()))?; let peer_id = *swarm.local_peer_id(); - let webserver = webserver::Server::new(settings.node().network())?; + let webserver = webserver::Server::new(settings.node().network().webserver())?; #[cfg(feature = "websocket-notify")] let (ws_msg_tx, ws_evt_tx) = { @@ -179,9 +179,10 @@ impl Runner { }; #[cfg(feature = "websocket-notify")] - let event_handler = EventHandler::new(swarm, db, settings.node(), ws_evt_tx, ws_msg_tx); + let event_handler = + EventHandler::new(swarm, db, settings.node().network(), ws_evt_tx, ws_msg_tx); #[cfg(not(feature = "websocket-notify"))] - let event_handler = EventHandler::new(swarm, db, settings.node()); + let event_handler = EventHandler::new(swarm, db, settings.node().network()); let event_sender = event_handler.sender(); @@ -213,7 +214,7 @@ impl Runner { #[cfg(feature = "monitoring")] let metrics_hdl: PrometheusHandle = self.runtime.block_on(crate::metrics::start( - self.settings.monitoring(), + self.settings.node.monitoring(), self.settings.node.network(), ))?; @@ -300,7 +301,7 @@ impl Runner { info!("getting node info"); let (tx, rx) = AsyncChannel::oneshot(); let _ = self.event_sender.send_async(Event::GetListeners(tx)).await; - let dyn_node_info = if let Ok(listeners) = rx.recv_deadline(Instant::now() + self.settings.node.network.webserver_timeout) { + let dyn_node_info = if let Ok(listeners) = rx.recv_deadline(Instant::now() + self.settings.node.network.webserver.timeout) { DynamicNodeInfo::new(listeners) } else { DynamicNodeInfo::new(vec![]) @@ -699,16 +700,17 @@ mod test { let rpc_sender = rpc_server.sender(); let addr = SocketAddr::new( - settings.node.network.rpc_host, - settings.node.network.rpc_port, + settings.node.network.rpc.host, + settings.node.network.rpc.port, ); let ws_hdl = runner.runtime.block_on(async { rpc_server.spawn().await.unwrap(); #[cfg(feature = "monitoring")] - let metrics_hdl = crate::metrics::start(settings.monitoring(), settings.node.network()) - .await - .unwrap(); + let metrics_hdl = + crate::metrics::start(settings.node.monitoring(), settings.node.network()) + .await + .unwrap(); #[cfg(not(feature = "monitoring"))] let metrics_hdl = crate::metrics::start(settings.node.network()) .await @@ -758,8 +760,8 @@ mod test { runner.runtime.spawn(async move { let addr = SocketAddr::new( - settings.node.network.rpc_host, - settings.node.network.rpc_port, + settings.node.network.rpc.host, + settings.node.network.rpc.port, ); let client = Client::new(addr, context::current()).await.unwrap(); diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index 928a63f5..f82c97ea 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -13,7 +13,9 @@ use std::{ time::Duration, }; +mod libp2p_config; mod pubkey_config; +pub(crate) use libp2p_config::{Libp2p, Pubsub}; pub(crate) use pubkey_config::PubkeyConfig; #[cfg(target_os = "windows")] @@ -24,42 +26,25 @@ const HOME_VAR: &str = "HOME"; /// Application settings. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Settings { - #[serde(default)] - pub(crate) monitoring: Monitoring, #[serde(default)] pub(crate) node: Node, } impl Settings { - /// Monitoring settings getter. - pub fn monitoring(&self) -> &Monitoring { - &self.monitoring - } - /// Node settings getter. pub fn node(&self) -> &Node { &self.node } } -/// Monitoring settings. -#[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(default)] -pub struct Monitoring { - /// Tokio console port. - pub console_subscriber_port: u16, - /// Monitoring collection interval in milliseconds. - #[cfg(feature = "monitoring")] - #[serde_as(as = "DurationMilliSeconds")] - pub process_collector_interval: Duration, -} - /// Server settings. #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct Node { + /// Monitoring settings. + #[serde(default)] + pub(crate) monitoring: Monitoring, /// Network settings. #[serde(default)] pub(crate) network: Network, @@ -74,145 +59,156 @@ pub struct Node { pub(crate) shutdown_timeout: Duration, } -/// Network-related settings for a homestar node. -/// TODO: Split-up and re-arrange. +/// Database-related settings for a homestar node. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Database { + /// Database Url provided within the configuration file. + /// + /// Note: This is not used if the `DATABASE_URL` environment variable + /// is set. + #[serde_as(as = "Option")] + pub(crate) url: Option, + /// Maximum number of connections managed by the [pool]. + /// + /// [pool]: crate::db::Pool + pub(crate) max_pool_size: u32, +} + +/// Monitoring settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Monitoring { + /// Tokio console port. + pub console_subscriber_port: u16, + /// Monitoring collection interval in milliseconds. + #[cfg(feature = "monitoring")] + #[serde_as(as = "DurationMilliSeconds")] + pub process_collector_interval: Duration, +} + +/// Network settings for a homestar node. #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct Network { - /// Metrics port for prometheus scraping. - pub metrics_port: u16, + /// libp2p Settings. + pub(crate) libp2p: Libp2p, + /// Metrics Settings. + pub(crate) metrics: Metrics, /// Buffer-length for event(s) / command(s) channels. pub(crate) events_buffer_len: usize, - /// Address for [Swarm] to listen on. - /// - /// [Swarm]: libp2p::swarm::Swarm - #[serde(with = "http_serde::uri")] - pub(crate) listen_address: Uri, - /// Enable Rendezvous protocol client. - pub(crate) enable_rendezvous_client: bool, - /// Enable Rendezvous protocol server. - pub(crate) enable_rendezvous_server: bool, - /// Rendezvous registration TTL. - #[serde_as(as = "DurationSeconds")] - pub(crate) rendezvous_registration_ttl: Duration, - /// Rendezvous discovery interval. - #[serde_as(as = "DurationSeconds")] - pub(crate) rendezvous_discovery_interval: Duration, - /// Enable mDNS. - pub(crate) enable_mdns: bool, - /// mDNS IPv6 enable flag - pub(crate) mdns_enable_ipv6: bool, - /// mDNS query interval. - #[serde_as(as = "DurationSeconds")] - pub(crate) mdns_query_interval: Duration, - /// mDNS TTL. - #[serde_as(as = "DurationSeconds")] - pub(crate) mdns_ttl: Duration, - /// Timeout for p2p requests for a provided record. - #[serde_as(as = "DurationSeconds")] - pub(crate) p2p_provider_timeout: Duration, - /// Enable pub/sub. - pub(crate) enable_pubsub: bool, - /// Pub/sub duplicate cache time. - #[serde_as(as = "DurationSeconds")] - pub(crate) pubsub_duplication_cache_time: Duration, - /// Pub/sub hearbeat interval for mesh configuration. - #[serde_as(as = "DurationSeconds")] - pub(crate) pubsub_heartbeat: Duration, - /// Pub/sub idle timeout - #[serde_as(as = "DurationSeconds")] - pub(crate) pubsub_idle_timeout: Duration, - /// TODO - pub(crate) pubsub_max_transmit_size: usize, - /// TODO - pub(crate) pubsub_mesh_n_low: usize, - /// TODO - pub(crate) pubsub_mesh_n_high: usize, - /// TODO - pub(crate) pubsub_mesh_n: usize, - /// TODO - pub(crate) pubsub_mesh_outbound_min: usize, - /// Quorum for receipt records on the DHT. - pub(crate) receipt_quorum: usize, - /// RPC-server port. - #[serde_as(as = "DisplayFromStr")] - pub(crate) rpc_host: IpAddr, - /// RPC-server max-concurrent connections. - pub(crate) rpc_max_connections: usize, - /// RPC-server port. - pub(crate) rpc_port: u16, - #[serde_as(as = "DurationSeconds")] - /// RPC-server timeout. - pub(crate) rpc_server_timeout: Duration, - /// Transport connection timeout. - #[serde_as(as = "DurationSeconds")] - pub(crate) transport_connection_timeout: Duration, - /// Webserver host address. - #[serde(with = "http_serde::uri")] - pub(crate) webserver_host: Uri, - /// Webserver-server port. - pub(crate) webserver_port: u16, - /// TODO - #[serde_as(as = "DurationSeconds")] - pub(crate) webserver_timeout: Duration, - /// Number of *bounded* clients to send messages to, used for a - /// [tokio::sync::broadcast::channel] - pub(crate) websocket_capacity: usize, - /// Websocket-server timeout for receiving messages from the runner. - #[serde_as(as = "DurationMilliSeconds")] - pub(crate) websocket_receiver_timeout: Duration, - /// Quorum for [workflow::Info] records on the DHT. - /// - /// [workflow::Info]: crate::workflow::Info - pub(crate) workflow_quorum: usize, + /// RPC server settings. + pub(crate) rpc: Rpc, /// Pubkey setup configuration. pub(crate) keypair_config: PubkeyConfig, - /// Multiaddrs of the trusted nodes to connect to on startup. - #[serde_as(as = "Vec")] - pub(crate) node_addresses: Vec, - /// Multiaddrs of the external addresses this node will announce to the - /// network. - #[serde_as(as = "Vec")] - pub(crate) announce_addresses: Vec, - /// Maximum number of peers we will dial. - pub(crate) max_connected_peers: u32, - /// Limit on the number of external addresses we announce to other peers. - pub(crate) max_announce_addresses: u32, /// Event handler poll cache interval in milliseconds. #[serde_as(as = "DurationMilliSeconds")] pub(crate) poll_cache_interval: Duration, - /// TODO + /// IPFS settings. #[cfg(feature = "ipfs")] pub(crate) ipfs: Ipfs, + /// Webserver settings + pub(crate) webserver: Webserver, } +/// IPFS Settings #[cfg(feature = "ipfs")] #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] pub(crate) struct Ipfs { - /// TODO + /// The host where Homestar expects IPFS. pub(crate) host: String, - /// TODO + /// The port where Homestar expects IPFS. pub(crate) port: u16, } -/// Database-related settings for a homestar node. +/// Metrics settings. #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] -pub(crate) struct Database { - /// Database Url provided within the configuration file. - /// - /// Note: This is not used if the `DATABASE_URL` environment variable - /// is set. - #[serde_as(as = "Option")] - pub(crate) url: Option, - /// Maximum number of connections managed by the [pool]. - /// - /// [pool]: crate::db::Pool - pub(crate) max_pool_size: u32, +pub(crate) struct Metrics { + /// Metrics port for prometheus scraping. + pub(crate) port: u16, +} + +/// RPC server settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Rpc { + /// RPC-server port. + #[serde_as(as = "DisplayFromStr")] + pub(crate) host: IpAddr, + /// RPC-server max-concurrent connections. + pub(crate) max_connections: usize, + /// RPC-server port. + pub(crate) port: u16, + #[serde_as(as = "DurationSeconds")] + /// RPC-server timeout. + pub(crate) server_timeout: Duration, +} + +/// Webserver settings +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Webserver { + /// Webserver host address. + #[serde(with = "http_serde::uri")] + pub(crate) host: Uri, + /// Webserver-server port. + pub(crate) port: u16, + /// TODO + #[serde_as(as = "DurationSeconds")] + pub(crate) timeout: Duration, + /// Number of *bounded* clients to send messages to, used for a + /// [tokio::sync::broadcast::channel] + pub(crate) websocket_capacity: usize, + /// Websocket-server timeout for receiving messages from the runner. + #[serde_as(as = "DurationMilliSeconds")] + pub(crate) websocket_receiver_timeout: Duration, +} + +impl Default for Node { + fn default() -> Self { + Self { + gc_interval: Duration::from_secs(1800), + shutdown_timeout: Duration::from_secs(20), + monitoring: Default::default(), + network: Default::default(), + db: Default::default(), + } + } +} + +impl Node { + /// Monitoring settings getter. + pub fn monitoring(&self) -> &Monitoring { + &self.monitoring + } + + /// Network settings. + pub fn network(&self) -> &Network { + &self.network + } + + /// Node shutdown timeout. + pub fn shutdown_timeout(&self) -> Duration { + self.shutdown_timeout + } +} + +impl Default for Database { + fn default() -> Self { + Self { + max_pool_size: 100, + url: None, + } + } } #[cfg(feature = "monitoring")] @@ -234,6 +230,40 @@ impl Default for Monitoring { } } +impl Default for Network { + fn default() -> Self { + Self { + libp2p: Libp2p::default(), + metrics: Metrics::default(), + events_buffer_len: 1024, + rpc: Rpc::default(), + keypair_config: PubkeyConfig::Random, + poll_cache_interval: Duration::from_millis(1000), + #[cfg(feature = "ipfs")] + ipfs: Default::default(), + webserver: Webserver::default(), + } + } +} + +impl Network { + /// IPFS settings. + #[cfg(feature = "ipfs")] + pub(crate) fn ipfs(&self) -> &Ipfs { + &self.ipfs + } + + /// libp2p settings. + pub(crate) fn libp2p(&self) -> &Libp2p { + &self.libp2p + } + + /// Webserver settings. + pub(crate) fn webserver(&self) -> &Webserver { + &self.webserver + } +} + #[cfg(feature = "ipfs")] impl Default for Ipfs { fn default() -> Self { @@ -244,94 +274,35 @@ impl Default for Ipfs { } } -impl Default for Database { +impl Default for Metrics { fn default() -> Self { - Self { - max_pool_size: 100, - url: None, - } + Self { port: 4000 } } } -impl Default for Node { +impl Default for Rpc { fn default() -> Self { Self { - gc_interval: Duration::from_secs(1800), - shutdown_timeout: Duration::from_secs(20), - network: Default::default(), - db: Default::default(), + host: IpAddr::V6(Ipv6Addr::LOCALHOST), + max_connections: 10, + port: 3030, + server_timeout: Duration::new(120, 0), } } } -impl Default for Network { +impl Default for Webserver { fn default() -> Self { Self { - metrics_port: 4000, - events_buffer_len: 1024, - listen_address: Uri::from_static("/ip4/0.0.0.0/tcp/0"), - enable_rendezvous_client: true, - enable_rendezvous_server: false, - rendezvous_registration_ttl: Duration::from_secs(2 * 60 * 60), - rendezvous_discovery_interval: Duration::from_secs(10 * 60), - // TODO: we would like to enable this by default, however this breaks mdns on at least some linux distros. Requires further investigation. - enable_mdns: true, - mdns_enable_ipv6: false, - mdns_query_interval: Duration::from_secs(5 * 60), - mdns_ttl: Duration::from_secs(60 * 9), - p2p_provider_timeout: Duration::new(30, 0), - enable_pubsub: true, - pubsub_duplication_cache_time: Duration::new(1, 0), - pubsub_heartbeat: Duration::new(60, 0), - pubsub_idle_timeout: Duration::new(60 * 60 * 24, 0), - pubsub_max_transmit_size: 10 * 1024 * 1024, - pubsub_mesh_n_low: 1, - pubsub_mesh_n_high: 10, - pubsub_mesh_n: 2, - pubsub_mesh_outbound_min: 1, - receipt_quorum: 2, - rpc_host: IpAddr::V6(Ipv6Addr::LOCALHOST), - rpc_max_connections: 10, - rpc_port: 3030, - rpc_server_timeout: Duration::new(120, 0), - transport_connection_timeout: Duration::new(60, 0), - webserver_host: Uri::from_static("127.0.0.1"), - webserver_port: 1337, - webserver_timeout: Duration::new(120, 0), + host: Uri::from_static("127.0.0.1"), + port: 1337, + timeout: Duration::new(120, 0), websocket_capacity: 2048, websocket_receiver_timeout: Duration::from_millis(30_000), - workflow_quorum: 3, - keypair_config: PubkeyConfig::Random, - node_addresses: Vec::new(), - announce_addresses: Vec::new(), - max_connected_peers: 32, - max_announce_addresses: 10, - poll_cache_interval: Duration::from_millis(1000), - #[cfg(feature = "ipfs")] - ipfs: Default::default(), } } } -impl Node { - /// Network settings. - pub fn network(&self) -> &Network { - &self.network - } - /// Node shutdown timeout. - pub fn shutdown_timeout(&self) -> Duration { - self.shutdown_timeout - } -} - -impl Network { - /// TODO - #[cfg(feature = "ipfs")] - pub(crate) fn ipfs(&self) -> &Ipfs { - &self.ipfs - } -} - impl Settings { /// Load settings. /// @@ -412,20 +383,28 @@ mod test { let mut default_modded_settings = Node::default(); default_modded_settings.network.events_buffer_len = 1000; - default_modded_settings.network.webserver_port = 9999; + default_modded_settings.network.webserver.port = 9999; default_modded_settings.gc_interval = Duration::from_secs(1800); default_modded_settings.shutdown_timeout = Duration::from_secs(20); - default_modded_settings.network.node_addresses = + default_modded_settings.network.libp2p.node_addresses = vec!["/ip4/127.0.0.1/tcp/9998/ws".to_string().try_into().unwrap()]; assert_eq!(settings.node(), &default_modded_settings); } + #[test] + fn default_config() { + let settings = Settings::load().unwrap(); + let default_config = Settings::build(Some("fixtures/defaults.toml".into())) + .expect("default settings file in test fixtures"); + assert_eq!(settings, default_config); + } + #[test] fn overriding_env() { - std::env::set_var("HOMESTAR__NODE__NETWORK__RPC_PORT", "2046"); + std::env::set_var("HOMESTAR__NODE__NETWORK__RPC__PORT", "2046"); std::env::set_var("HOMESTAR__NODE__DB__MAX_POOL_SIZE", "1"); let settings = Settings::build(Some("fixtures/settings.toml".into())).unwrap(); - assert_eq!(settings.node.network.rpc_port, 2046); + assert_eq!(settings.node.network.rpc.port, 2046); assert_eq!(settings.node.db.max_pool_size, 1); } diff --git a/homestar-runtime/src/settings/libp2p_config.rs b/homestar-runtime/src/settings/libp2p_config.rs new file mode 100644 index 00000000..b561cb4b --- /dev/null +++ b/homestar-runtime/src/settings/libp2p_config.rs @@ -0,0 +1,190 @@ +//! Libp2p setttings + +use http::Uri; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DurationSeconds}; +use std::time::Duration; + +/// libp2p settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Libp2p { + /// Multiaddrs of the external addresses this node will announce to the + /// network. + #[serde_as(as = "Vec")] + pub(crate) announce_addresses: Vec, + /// Kademlia DHT Settings + pub(crate) dht: Dht, + /// Address for [Swarm] to listen on. + /// + /// [Swarm]: libp2p::swarm::Swarm + #[serde(with = "http_serde::uri")] + pub(crate) listen_address: Uri, + /// Maximum number of peers we will dial. + pub(crate) max_connected_peers: u32, + /// Limit on the number of external addresses we announce to other peers. + pub(crate) max_announce_addresses: u32, + /// Multiaddrs of the trusted nodes to connect to on startup. + #[serde_as(as = "Vec")] + pub(crate) node_addresses: Vec, + /// mDNS Settings. + pub(crate) mdns: Mdns, + /// Pubsub Settings. + pub(crate) pubsub: Pubsub, + /// Rendezvous Settings. + pub(crate) rendezvous: Rendezvous, + /// Transport connection timeout. + #[serde_as(as = "DurationSeconds")] + pub(crate) transport_connection_timeout: Duration, +} + +/// DHT settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Dht { + /// Timeout for p2p requests for a provided record. + #[serde_as(as = "DurationSeconds")] + pub(crate) p2p_provider_timeout: Duration, + /// Quorum for receipt records on the DHT. + pub(crate) receipt_quorum: usize, + /// Quorum for [workflow::Info] records on the DHT. + /// + /// [workflow::Info]: crate::workflow::Info + pub(crate) workflow_quorum: usize, +} + +/// mDNS settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Mdns { + /// Enable mDNS. + pub(crate) enable: bool, + /// mDNS IPv6 enable flag + pub(crate) enable_ipv6: bool, + /// mDNS query interval. + #[serde_as(as = "DurationSeconds")] + pub(crate) query_interval: Duration, + /// mDNS TTL. + #[serde_as(as = "DurationSeconds")] + pub(crate) ttl: Duration, +} + +/// Pubsub settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Pubsub { + /// Enable pub/sub. + pub(crate) enable: bool, + /// Pub/sub duplicate cache time. + #[serde_as(as = "DurationSeconds")] + pub(crate) duplication_cache_time: Duration, + /// Pub/sub hearbeat interval for mesh configuration. + #[serde_as(as = "DurationSeconds")] + pub(crate) heartbeat: Duration, + /// Pub/sub idle timeout + #[serde_as(as = "DurationSeconds")] + pub(crate) idle_timeout: Duration, + /// Maximum byte size of pub/sub messages. + pub(crate) max_transmit_size: usize, + /// Minimum number of pub/sub peers. + pub(crate) mesh_n_low: usize, + /// Maximum number of pub/sub peers. + pub(crate) mesh_n_high: usize, + /// Target number of pub/sub peers. + pub(crate) mesh_n: usize, + /// Minimum outbound pub/sub peers before adding more peers. + pub(crate) mesh_outbound_min: usize, +} + +/// Rendezvous settings. +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub(crate) struct Rendezvous { + /// Enable Rendezvous protocol client. + pub(crate) enable_client: bool, + /// Enable Rendezvous protocol server. + pub(crate) enable_server: bool, + /// Rendezvous registration TTL. + #[serde_as(as = "DurationSeconds")] + pub(crate) registration_ttl: Duration, + /// Rendezvous discovery interval. + #[serde_as(as = "DurationSeconds")] + pub(crate) discovery_interval: Duration, +} + +impl Default for Libp2p { + fn default() -> Self { + Self { + announce_addresses: Vec::new(), + dht: Dht::default(), + listen_address: Uri::from_static("/ip4/0.0.0.0/tcp/0"), + max_connected_peers: 32, + max_announce_addresses: 10, + mdns: Mdns::default(), + node_addresses: Vec::new(), + pubsub: Pubsub::default(), + rendezvous: Rendezvous::default(), + transport_connection_timeout: Duration::new(60, 0), + } + } +} + +impl Libp2p { + /// Pub/sub settings getter. + pub(crate) fn pubsub(&self) -> &Pubsub { + &self.pubsub + } +} + +impl Default for Dht { + fn default() -> Self { + Self { + p2p_provider_timeout: Duration::new(30, 0), + receipt_quorum: 2, + workflow_quorum: 3, + } + } +} + +impl Default for Mdns { + fn default() -> Self { + Self { + enable: true, + enable_ipv6: false, + query_interval: Duration::from_secs(5 * 60), + ttl: Duration::from_secs(60 * 9), + } + } +} + +impl Default for Pubsub { + fn default() -> Self { + Self { + enable: true, + duplication_cache_time: Duration::new(1, 0), + heartbeat: Duration::new(60, 0), + idle_timeout: Duration::new(60 * 60 * 24, 0), + max_transmit_size: 10 * 1024 * 1024, + mesh_n_low: 1, + mesh_n_high: 10, + mesh_n: 2, + mesh_outbound_min: 1, + } + } +} + +impl Default for Rendezvous { + fn default() -> Self { + Self { + enable_client: true, + enable_server: false, + registration_ttl: Duration::from_secs(2 * 60 * 60), + discovery_interval: Duration::from_secs(10 * 60), + } + } +} diff --git a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs index 8334fb5f..5ffbdcc9 100644 --- a/homestar-runtime/src/test_utils/proc_macro/src/lib.rs +++ b/homestar-runtime/src/test_utils/proc_macro/src/lib.rs @@ -70,8 +70,8 @@ pub fn db_async_test(_attr: TokenStream, item: TokenStream) -> TokenStream { /// runner.runtime.block_on(rpc_server.spawn()).unwrap(); /// runner.runtime.spawn(async move { /// let addr = SocketAddr::new( -/// settings.node.network.rpc_host, -/// settings.node.network.rpc_port, +/// settings.node.network.rpc.host, +/// settings.node.network.rpc.port, /// ); /// let client = Client::new(addr, context::current()).await.unwrap(); /// let response = client.ping().await.unwrap(); @@ -98,11 +98,11 @@ pub fn runner_test(_attr: TokenStream, item: TokenStream) -> TokenStream { impl TestRunner { fn start() -> TestRunner { let mut settings = crate::Settings::load().unwrap(); - settings.node.network.webserver_port = ::homestar_core::test_utils::ports::get_port() as u16; - settings.node.network.rpc_port = ::homestar_core::test_utils::ports::get_port() as u16; - settings.node.network.metrics_port = ::homestar_core::test_utils::ports::get_port() as u16; + settings.node.network.webserver.port = ::homestar_core::test_utils::ports::get_port() as u16; + settings.node.network.rpc.port = ::homestar_core::test_utils::ports::get_port() as u16; + settings.node.network.metrics.port = ::homestar_core::test_utils::ports::get_port() as u16; settings.node.db.url = Some(format!("{}.db", #func_name_as_string)); - settings.node.network.websocket_receiver_timeout = std::time::Duration::from_millis(500); + settings.node.network.webserver.websocket_receiver_timeout = std::time::Duration::from_millis(500); let db = crate::test_utils::db::MemoryDb::setup_connection_pool(&settings.node, None).unwrap(); let runner = crate::Runner::start(settings.clone(), db).unwrap(); TestRunner { runner, settings } diff --git a/homestar-runtime/tests/fixtures/test_gossip1.toml b/homestar-runtime/tests/fixtures/test_gossip1.toml index 5c2b725c..a8500685 100644 --- a/homestar-runtime/tests/fixtures/test_gossip1.toml +++ b/homestar-runtime/tests/fixtures/test_gossip1.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5550 -[node] +# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } -[node.network] -metrics_port = 3990 -rpc_port = 9790 -webserver_port = 7990 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7020" node_addresses = [ "/ip4/127.0.0.1/tcp/7021/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 3990 + +[node.network.rpc] +port = 9790 + +[node.network.webserver] +port = 7990 diff --git a/homestar-runtime/tests/fixtures/test_gossip2.toml b/homestar-runtime/tests/fixtures/test_gossip2.toml index 0ac179a2..ccc9e9c3 100644 --- a/homestar-runtime/tests/fixtures/test_gossip2.toml +++ b/homestar-runtime/tests/fixtures/test_gossip2.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5551 -[node] +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } -[node.network] -metrics_port = 3991 -rpc_port = 9791 -webserver_port = 7991 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7021" node_addresses = [ "/ip4/127.0.0.1/tcp/7020/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc -[node.network.keypair_config] -existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } +[node.network.libp2p.mdns] +enable = false + +[node.network.metrics] +port = 3991 + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.rpc] +port = 9791 + +[node.network.webserver] +port = 7991 diff --git a/homestar-runtime/tests/fixtures/test_mdns1.toml b/homestar-runtime/tests/fixtures/test_mdns1.toml index 63bcce0b..4a0ad334 100644 --- a/homestar-runtime/tests/fixtures/test_mdns1.toml +++ b/homestar-runtime/tests/fixtures/test_mdns1.toml @@ -1,16 +1,24 @@ -[monitoring] -process_collector_interval = 500 -console_subscriber_port = 5560 - [node] -[node.network] -metrics_port = 4001 -rpc_port = 9800 -webserver_port = 8000 -listen_address = "/ip4/0.0.0.0/tcp/0" -enable_rendezvous_client = false +[node.monitoring] +process_collector_interval = 500 +console_subscriber_port = 5560 # Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN [node.network.keypair_config] existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } + +[node.network.libp2p] +listen_address = "/ip4/0.0.0.0/tcp/0" + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4001 + +[node.network.rpc] +port = 9800 + +[node.network.webserver] +port = 8000 diff --git a/homestar-runtime/tests/fixtures/test_mdns2.toml b/homestar-runtime/tests/fixtures/test_mdns2.toml index c5ddf01c..0aaea51a 100644 --- a/homestar-runtime/tests/fixtures/test_mdns2.toml +++ b/homestar-runtime/tests/fixtures/test_mdns2.toml @@ -1,16 +1,24 @@ -[monitoring] -process_collector_interval = 500 -console_subscriber_port = 5561 - [node] -[node.network] -metrics_port = 4002 -rpc_port = 9801 -webserver_port = 8001 -listen_address = "/ip4/0.0.0.0/tcp/0" -enable_rendezvous_client = false +[node.monitoring] +process_collector_interval = 500 +console_subscriber_port = 5561 # Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc [node.network.keypair_config] existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } + +[node.network.libp2p] +listen_address = "/ip4/0.0.0.0/tcp/0" + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4002 + +[node.network.rpc] +port = 9801 + +[node.network.webserver] +port = 8001 diff --git a/homestar-runtime/tests/fixtures/test_metrics.toml b/homestar-runtime/tests/fixtures/test_metrics.toml index e6c31773..718d8511 100644 --- a/homestar-runtime/tests/fixtures/test_metrics.toml +++ b/homestar-runtime/tests/fixtures/test_metrics.toml @@ -1,14 +1,18 @@ -[monitoring] -process_collector_interval = 500 -console_subscriber_port = 5570 - [node] -[node.network] -metrics_port = 4020 -rpc_port = 9810 -webserver_port = 8010 +[node.monitoring] +process_collector_interval = 500 +console_subscriber_port = 5570 # Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN [node.network.keypair_config] existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } + +[node.network.metrics] +port = 4020 + +[node.network.rpc] +port = 9810 + +[node.network.webserver] +port = 8010 diff --git a/homestar-runtime/tests/fixtures/test_network1.toml b/homestar-runtime/tests/fixtures/test_network1.toml index bd1c09cd..6c59eaaa 100644 --- a/homestar-runtime/tests/fixtures/test_network1.toml +++ b/homestar-runtime/tests/fixtures/test_network1.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5580 -[node] +# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } -[node.network] -metrics_port = 4030 -rpc_port = 9820 -webserver_port = 8020 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7000" node_addresses = [ "/ip4/127.0.0.1/tcp/7001/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4030 + +[node.network.rpc] +port = 9820 + +[node.network.webserver] +port = 8020 diff --git a/homestar-runtime/tests/fixtures/test_network2.toml b/homestar-runtime/tests/fixtures/test_network2.toml index 70c167c8..489142c2 100644 --- a/homestar-runtime/tests/fixtures/test_network2.toml +++ b/homestar-runtime/tests/fixtures/test_network2.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5581 -[node] +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } -[node.network] -metrics_port = 4031 -rpc_port = 9821 -webserver_port = 8021 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7001" node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc -[node.network.keypair_config] -existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4031 + +[node.network.rpc] +port = 9821 + +[node.network.webserver] +port = 8021 diff --git a/homestar-runtime/tests/fixtures/test_notification1.toml b/homestar-runtime/tests/fixtures/test_notification1.toml index 979515fd..212b37d8 100644 --- a/homestar-runtime/tests/fixtures/test_notification1.toml +++ b/homestar-runtime/tests/fixtures/test_notification1.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5582 -[node] +# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } -[node.network] -metrics_port = 4032 -rpc_port = 9822 -webserver_port = 8022 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7010" node_addresses = [ "/ip4/127.0.0.1/tcp/7011/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4032 + +[node.network.rpc] +port = 9822 + +[node.network.webserver] +port = 8022 diff --git a/homestar-runtime/tests/fixtures/test_notification2.toml b/homestar-runtime/tests/fixtures/test_notification2.toml index dd22b7a1..362ecdc2 100644 --- a/homestar-runtime/tests/fixtures/test_notification2.toml +++ b/homestar-runtime/tests/fixtures/test_notification2.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5583 -[node] +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } -[node.network] -metrics_port = 4033 -rpc_port = 9823 -webserver_port = 8023 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7011" node_addresses = [ "/ip4/127.0.0.1/tcp/7010/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -enable_mdns = false -enable_rendezvous_client = false -# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc -[node.network.keypair_config] -existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +enable_client = false + +[node.network.metrics] +port = 4033 + +[node.network.rpc] +port = 9823 + +[node.network.webserver] +port = 8023 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous1.toml b/homestar-runtime/tests/fixtures/test_rendezvous1.toml index 585e8837..d80d6359 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous1.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous1.toml @@ -1,17 +1,27 @@ -[monitoring] -process_collector_interval = 500 -console_subscriber_port = 5585 - [node] -[node.network] -metrics_port = 4034 -rpc_port = 9824 -webserver_port = 8024 -listen_address = "/ip4/127.0.0.1/tcp/7000" -enable_rendezvous_server = true -enable_mdns = false +[node.monitoring] +process_collector_interval = 500 +console_subscriber_port = 5585 # Peer ID 12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN [node.network.keypair_config] existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519.pem" } + +[node.network.libp2p] +listen_address = "/ip4/127.0.0.1/tcp/7000" + +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +enable_server = true + +[node.network.metrics] +port = 4034 + +[node.network.rpc] +port = 9824 + +[node.network.webserver] +port = 8024 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous2.toml b/homestar-runtime/tests/fixtures/test_rendezvous2.toml index bb142da0..0f96954d 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous2.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous2.toml @@ -1,13 +1,14 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5586 -[node] +# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc +[node.network.keypair_config] +existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } -[node.network] -metrics_port = 4036 -rpc_port = 9826 -webserver_port = 8026 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7001" announce_addresses = [ "/ip4/127.0.0.1/tcp/7001/p2p/16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc", @@ -15,8 +16,15 @@ announce_addresses = [ node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -enable_mdns = false -# Peer ID 16Uiu2HAm3g9AomQNeEctL2hPwLapap7AtPSNt8ZrBny4rLx1W5Dc -[node.network.keypair_config] -existing = { key_type = "secp256k1", path = "./fixtures/__testkey_secp256k1.der" } +[node.network.libp2p.mdns] +enable = false + +[node.network.metrics] +port = 4036 + +[node.network.rpc] +port = 9826 + +[node.network.webserver] +port = 8026 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous3.toml b/homestar-runtime/tests/fixtures/test_rendezvous3.toml index a9fceb06..dc2c2bb0 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous3.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous3.toml @@ -1,19 +1,27 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5587 -[node] +# Peer ID 12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5 +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_2.pem" } -[node.network] -metrics_port = 4037 -rpc_port = 9827 -webserver_port = 8027 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7002" node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -enable_mdns = false -# Peer ID 12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5 -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_2.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.metrics] +port = 4037 + +[node.network.rpc] +port = 9827 + +[node.network.webserver] +port = 8027 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous4.toml b/homestar-runtime/tests/fixtures/test_rendezvous4.toml index 9e404c84..af354bfe 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous4.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous4.toml @@ -1,13 +1,14 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5588 -[node] +# Peer ID 12D3KooWJWoaqZhDaoEFshF7Rh1bpY9ohihFhzcW6d69Lr2NASuq +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_3.pem" } -[node.network] -metrics_port = 4038 -rpc_port = 9828 -webserver_port = 8028 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7003" announce_addresses = [ "/ip4/127.0.0.1/tcp/7003/p2p/12D3KooWJWoaqZhDaoEFshF7Rh1bpY9ohihFhzcW6d69Lr2NASuq", @@ -15,9 +16,18 @@ announce_addresses = [ node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -rendezvous_registration_ttl = 1 -enable_mdns = false -# Peer ID 12D3KooWJWoaqZhDaoEFshF7Rh1bpY9ohihFhzcW6d69Lr2NASuq -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_3.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +registration_ttl = 1 + +[node.network.metrics] +port = 4038 + +[node.network.rpc] +port = 9828 + +[node.network.webserver] +port = 8028 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous5.toml b/homestar-runtime/tests/fixtures/test_rendezvous5.toml index f44441a0..79048fc3 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous5.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous5.toml @@ -1,20 +1,30 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5589 -[node] +# Peer ID 12D3KooWRndVhVZPCiQwHBBBdg769GyrPUW13zxwqQyf9r3ANaba +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_4.pem" } -[node.network] -metrics_port = 4039 -rpc_port = 9829 -websocket_port = 8029 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7004" node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -rendezvous_discovery_interval = 1 -enable_mdns = false -# Peer ID 12D3KooWRndVhVZPCiQwHBBBdg769GyrPUW13zxwqQyf9r3ANaba -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_4.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +discovery_interval = 1 + +[node.network.metrics] +port = 4039 + +[node.network.rpc] +port = 9829 + +[node.network.webserver] +port = 8029 diff --git a/homestar-runtime/tests/fixtures/test_rendezvous6.toml b/homestar-runtime/tests/fixtures/test_rendezvous6.toml index c96d9a14..765c1f21 100644 --- a/homestar-runtime/tests/fixtures/test_rendezvous6.toml +++ b/homestar-runtime/tests/fixtures/test_rendezvous6.toml @@ -1,13 +1,14 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5588 -[node] +# Peer ID 12D3KooWPT98FXMfDQYavZm66EeVjTqP9Nnehn1gyaydqV8L8BQw +[node.network.keypair_config] +existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_5.pem" } -[node.network] -metrics_port = 4040 -rpc_port = 9830 -webserver_port = 8030 +[node.network.libp2p] listen_address = "/ip4/127.0.0.1/tcp/7005" announce_addresses = [ "/ip4/127.0.0.1/tcp/7005/p2p/12D3KooWPT98FXMfDQYavZm66EeVjTqP9Nnehn1gyaydqV8L8BQw", @@ -15,9 +16,18 @@ announce_addresses = [ node_addresses = [ "/ip4/127.0.0.1/tcp/7000/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN", ] -rendezvous_registration_ttl = 5 -enable_mdns = false -# Peer ID 12D3KooWPT98FXMfDQYavZm66EeVjTqP9Nnehn1gyaydqV8L8BQw -[node.network.keypair_config] -existing = { key_type = "ed25519", path = "./fixtures/__testkey_ed25519_5.pem" } +[node.network.libp2p.mdns] +enable = false + +[node.network.libp2p.rendezvous] +registration_ttl = 5 + +[node.network.metrics] +port = 4040 + +[node.network.rpc] +port = 9830 + +[node.network.webserver] +port = 8030 diff --git a/homestar-runtime/tests/fixtures/test_v4.toml b/homestar-runtime/tests/fixtures/test_v4.toml index ab9ce320..ae95115f 100644 --- a/homestar-runtime/tests/fixtures/test_v4.toml +++ b/homestar-runtime/tests/fixtures/test_v4.toml @@ -1,12 +1,18 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5590 -[node] - [node.network] -metrics_port = 4045 events_buffer_len = 1000 -rpc_port = 9000 -rpc_host = "127.0.0.1" -webserver_port = 8031 + +[node.network.metrics] +port = 4045 + +[node.network.rpc] +host = "127.0.0.1" +port = 9000 + +[node.network.webserver] +port = 8031 diff --git a/homestar-runtime/tests/fixtures/test_v4_alt.toml b/homestar-runtime/tests/fixtures/test_v4_alt.toml index 72b5222b..bf91ec45 100644 --- a/homestar-runtime/tests/fixtures/test_v4_alt.toml +++ b/homestar-runtime/tests/fixtures/test_v4_alt.toml @@ -1,12 +1,18 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5596 -[node] - [node.network] -metrics_port = 4041 events_buffer_len = 1000 -rpc_port = 9836 -rpc_host = "127.0.0.1" -webserver_port = 8040 + +[node.network.metrics] +port = 4041 + +[node.network.rpc] +host = "127.0.0.1" +port = 9836 + +[node.network.webserver] +port = 8040 diff --git a/homestar-runtime/tests/fixtures/test_v6.toml b/homestar-runtime/tests/fixtures/test_v6.toml index 4f371951..e09ff622 100644 --- a/homestar-runtime/tests/fixtures/test_v6.toml +++ b/homestar-runtime/tests/fixtures/test_v6.toml @@ -1,12 +1,18 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5597 -[node] - [node.network] -metrics_port = 4042 events_buffer_len = 1000 -rpc_port = 9837 -rpc_host = "::1" -webserver_port = 8050 + +[node.network.metrics] +port = 4042 + +[node.network.rpc] +host = "::1" +port = 9837 + +[node.network.webserver] +port = 8050 diff --git a/homestar-runtime/tests/fixtures/test_windows_v4.toml b/homestar-runtime/tests/fixtures/test_windows_v4.toml index a50408f2..df78b182 100644 --- a/homestar-runtime/tests/fixtures/test_windows_v4.toml +++ b/homestar-runtime/tests/fixtures/test_windows_v4.toml @@ -1,12 +1,18 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5591 -[node] - [node.network] -metrics_port = 4046 events_buffer_len = 1000 -rpc_port = 9001 -rpc_host = "127.0.0.1" -webserver_port = 8032 + +[node.network.metrics] +port = 4046 + +[node.network.rpc] +host = "127.0.0.1" +port = 9001 + +[node.network.webserver] +port = 8032 diff --git a/homestar-runtime/tests/fixtures/test_workflow1.toml b/homestar-runtime/tests/fixtures/test_workflow1.toml index 6a0efb11..52531978 100644 --- a/homestar-runtime/tests/fixtures/test_workflow1.toml +++ b/homestar-runtime/tests/fixtures/test_workflow1.toml @@ -1,11 +1,17 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5600 -[node] - [node.network] -metrics_port = 4050 events_buffer_len = 1000 -rpc_port = 9840 -webserver_port = 8060 + +[node.network.metrics] +port = 4050 + +[node.network.rpc] +port = 9840 + +[node.network.webserver] +port = 8060 diff --git a/homestar-runtime/tests/fixtures/test_workflow2.toml b/homestar-runtime/tests/fixtures/test_workflow2.toml index e81e6d9f..120e31c2 100644 --- a/homestar-runtime/tests/fixtures/test_workflow2.toml +++ b/homestar-runtime/tests/fixtures/test_workflow2.toml @@ -1,11 +1,17 @@ -[monitoring] +[node] + +[node.monitoring] process_collector_interval = 500 console_subscriber_port = 5600 -[node] - [node.network] -metrics_port = 4070 events_buffer_len = 1000 -rpc_port = 9860 -webserver_port = 8061 + +[node.network.metrics] +port = 4070 + +[node.network.rpc] +port = 9860 + +[node.network.webserver] +port = 8061 From 7c45cf5af5e05f783de7129fcc4bf6022e4f06ec Mon Sep 17 00:00:00 2001 From: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:18:35 -0800 Subject: [PATCH 40/42] Update websocket relay example app (#448) This PR implements the following changes: - [x] Update example to use Homestar client: https://www.npmjs.com/package/@fission-codes/homestar - [x] Remove emulation mode Implements #390 - [x] Refactor (non-breaking change that updates existing functionality) The example should be tested manually to verify it works. --------- Signed-off-by: Brian Ginsburg <7957636+bgins@users.noreply.github.com> Co-authored-by: Hugo Dias --- examples/websocket-relay/README.md | 19 +- examples/websocket-relay/config/settings.toml | 5 - examples/websocket-relay/relay-app/.env | 1 - .../relay-app/package-lock.json | 352 +++++++++++-- .../websocket-relay/relay-app/package.json | 2 + .../src/components/WorkflowDetail.svelte | 10 +- .../relay-app/src/lib/channel.ts | 103 ---- .../relay-app/src/lib/workflow.ts | 489 +++++++----------- .../relay-app/src/routes/+page.svelte | 6 +- .../websocket-relay/relay-app/src/stores.ts | 39 +- 10 files changed, 544 insertions(+), 482 deletions(-) delete mode 100644 examples/websocket-relay/config/settings.toml delete mode 100644 examples/websocket-relay/relay-app/src/lib/channel.ts diff --git a/examples/websocket-relay/README.md b/examples/websocket-relay/README.md index dc074bf1..c2b08bcd 100644 --- a/examples/websocket-relay/README.md +++ b/examples/websocket-relay/README.md @@ -9,13 +9,15 @@ processing workflows that chain inputs and outputs using This application demonstrates: + * workflows built using the + [@fission-codes/homestar][@fission-codes/homestar] client library * websocket notifications of [UCAN Invocation Receipts][spec-receipts] sent - between a web client and a `homestar` runner + between a web client and a `homestar` runner using [@fission-codes/homestar][@fission-codes/homestar] * instantaneous replay of previously run, cached executions * fetching content (the original static image) over [IPFS][ipfs] through a local blockstore * the [WIT][wit] + [IPLD][ipld] interpreter for - [Wasm(time)][wasmtime] embedded execution within a `homestar` runner. + [Wasm(time)][wasmtime] embedded execution within a `homestar` runner ## Install @@ -33,9 +35,8 @@ To get started, please install: ## Usage -1. Run `cargo run -- start -c config/settings.toml` to start the runtime and - an IPFS daemon as a background process. This runtime includes - ANSI-coded logging by default. +1. Run `cargo run -- start` to start the runtime and an IPFS daemon as a + background process. This runtime includes ANSI-coded logging by default. 2. In a separate terminal window, run `npm install --prefix relay-app` to install dependencies and `npm run --prefix relay-app dev` to start the @@ -65,18 +66,17 @@ need: `brew install rust npm ipfs` We have packaged homestar binaries using brew, so `brew install fission-codes/fission/homestar` will install everything you need, -including `ipfs`. You will still need `npm` to run this example. From this folder, -you can then run the example like this: +including `ipfs`. You will still need `npm` to run this example. From this folder, you can then run the example like this: ``` -homestar start --config ./config/settings.toml --db homestar.db` +homestar start --db homestar.db` ``` Running `homestar` via `cargo run` requires a minimum Rust version of `1.70.0`. If you've got an older install of rust, update it with `rustup update`. -You do not have to start-up Kubo (IPFS) on your own. The example will do this +You do not have to start Kubo (IPFS) on your own. The example will do this for you, and use `examples/websocket-relay/tmp/.ipfs` as a local blockstore. Feel free to discard it when you don't need it. @@ -84,6 +84,7 @@ If you're already running an IPFS instance however, e.g. [IPFS Desktop][ipfs-des the example will check for an already running instance and not start a new, local one. +[@fission-codes/homestar]: https://www.npmjs.com/package/@fission-codes/homestar [install-ipfs]: https://docs.ipfs.tech/install/ [install-npm]: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm [install-rust]: https://www.rust-lang.org/tools/install diff --git a/examples/websocket-relay/config/settings.toml b/examples/websocket-relay/config/settings.toml deleted file mode 100644 index 93baf21c..00000000 --- a/examples/websocket-relay/config/settings.toml +++ /dev/null @@ -1,5 +0,0 @@ -[node] - -[node.network.keypair_config] -# generate key using a seed -random_seed = { key_type = "secp256k1", seed = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" } diff --git a/examples/websocket-relay/relay-app/.env b/examples/websocket-relay/relay-app/.env index 36d1eb1f..8a5a254d 100644 --- a/examples/websocket-relay/relay-app/.env +++ b/examples/websocket-relay/relay-app/.env @@ -1,4 +1,3 @@ VITE_WEBSOCKET_ENDPOINT="ws://localhost:1337" VITE_PING_INTERVAL=8000 VITE_MAX_PING_RETRIES=3 -VITE_EMULATION_MODE=false diff --git a/examples/websocket-relay/relay-app/package-lock.json b/examples/websocket-relay/relay-app/package-lock.json index cacfd5dd..5d1333fa 100644 --- a/examples/websocket-relay/relay-app/package-lock.json +++ b/examples/websocket-relay/relay-app/package-lock.json @@ -8,8 +8,10 @@ "name": "websocket-relay", "version": "0.0.1", "dependencies": { + "@fission-codes/homestar": "^1.0.0", "@zerodevx/svelte-json-view": "^1.0.3", "clipboard-copy": "^4.0.1", + "iso-base": "^2.0.1", "svelvet": "8.1.0" }, "devDependencies": { @@ -137,6 +139,75 @@ "node": ">=14" } }, + "node_modules/@fission-codes/homestar": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@fission-codes/homestar/-/homestar-1.0.0.tgz", + "integrity": "sha512-kOmnpAs2izF3B5tfOyPiCMhWCcUZlDHP/5VCW1LcHBmKD3e7TPffdmXeMnyJexxn+ssFxa9qYqsRGADCNhiUKA==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.6", + "@ipld/dag-json": "^10.1.5", + "@multiformats/sha3": "^3.0.1", + "@ts-ast-parser/core": "^0.6.3", + "emittery": "^1.0.1", + "esbuild": "^0.19.8", + "get-tsconfig": "^4.7.2", + "iso-websocket": "^0.1.6", + "multiformats": "^12.1.3", + "object-path": "^0.11.8", + "zod": "^3.22.4" + } + }, + "node_modules/@fission-codes/homestar/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fission-codes/homestar/node_modules/esbuild": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -170,6 +241,32 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@ipld/dag-cbor": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.6.tgz", + "integrity": "sha512-3kNab5xMppgWw6DVYx2BzmFq8t7I56AGWfp5kaU1fIPkwHVpBRglJJTYsGtbVluCi/s/q97HZM3bC+aDW4sxbQ==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^12.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-json": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.1.5.tgz", + "integrity": "sha512-AIIDRGPgIqVG2K1O42dPDzNOfP0YWV/suGApzpF+YWZLwkwdGVsxjmXcJ/+rwOhRGdjpuq/xQBKPCu1Ao6rdOQ==", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^12.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -218,11 +315,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@multiformats/sha3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/sha3/-/sha3-3.0.1.tgz", + "integrity": "sha512-lXlD8gA//SNPv6QpTEKR00029G1bt9EG8WieCmAZefiV+ONYw3yYt9UhneC3DMCFFXLKCcw8obHzodnah7EGug==", + "dependencies": { + "js-sha3": "^0.9.1", + "multiformats": "^12.1.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -235,7 +340,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -244,7 +348,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -278,6 +381,17 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sveltejs/adapter-auto": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.0.tgz", @@ -370,6 +484,75 @@ "vite": "^4.0.0" } }, + "node_modules/@ts-ast-parser/comment": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ts-ast-parser/comment/-/comment-0.1.0.tgz", + "integrity": "sha512-SLN5vUMcu0FwhsW7ngVFhwCI9p5r5MGIRT1nGyVKlrXSXMHGcKnU9oulH5Gz9s6epRQooA2TJmU/4hW9z8zy9A==", + "dependencies": { + "tslib": "^2.6.1" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || ^20.0.0" + } + }, + "node_modules/@ts-ast-parser/core": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@ts-ast-parser/core/-/core-0.6.3.tgz", + "integrity": "sha512-tRBa6eoiAUx/fNIEkxbYLYJlGur1JvKJc/6hGjlILlw9QPo9kS/Ae/dBnSLBkJV8rhOvl33TE1YmQDsNe4PdLg==", + "dependencies": { + "@ts-ast-parser/comment": "0.1.0", + "globby": "^14.0.0", + "package-json-type": "^1.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0" + }, + "peerDependencies": { + "typescript": "^4.6.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + } + }, + "node_modules/@ts-ast-parser/core/node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ts-ast-parser/core/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ts-ast-parser/core/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@types/chai": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", @@ -769,6 +952,19 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/bigint-mod-arith": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.3.1.tgz", + "integrity": "sha512-pX/cYW3dCa87Jrzv6DAr8ivbbJRzEX5yGhdt8IutnX/PCIXfpx+mabWNK/M8qqh+zQ0J3thftUBHW0ByuUlG0w==", + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -792,7 +988,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -879,6 +1074,14 @@ } ] }, + "node_modules/cborg": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.0.5.tgz", + "integrity": "sha512-q8TAjprr8pn9Fp53rOIGp/UFDdFY6os2Nq62YogPSIzczJD9M6g2b6igxMkpCiZZKJ0kn/KzDLDvG+EqBIEeCg==", + "bin": { + "cborg": "lib/bin.js" + } + }, "node_modules/chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -1052,7 +1255,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1149,6 +1351,17 @@ "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", "dev": true }, + "node_modules/emittery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.1.tgz", + "integrity": "sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -1429,10 +1642,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1448,7 +1660,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1472,7 +1683,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -1493,7 +1703,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1584,6 +1793,17 @@ "node": "*" } }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1700,7 +1920,6 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, "engines": { "node": ">= 4" } @@ -1784,7 +2003,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1793,7 +2011,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -1805,7 +2022,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -1825,6 +2041,25 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/iso-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/iso-base/-/iso-base-2.0.1.tgz", + "integrity": "sha512-ip0nUW9oZP+LG6mslSgdJPt9NWZOq1qIOKpoUIdm8sq/87VwY8WZXw9ptCkhdJclY+r4feFbZuu9P/OFDqCvkQ==", + "dependencies": { + "base-x": "^4.0.0", + "bigint-mod-arith": "^3.3.1" + } + }, + "node_modules/iso-websocket": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/iso-websocket/-/iso-websocket-0.1.6.tgz", + "integrity": "sha512-PfkpWtpmu9p0VIPmkbgKkAry7+fjNYZF/HX7KsvFV0mmD2RLI4aeM7M3B+5qLRBmm6H+A5CTVd760Bn2a60KsA==", + "dependencies": { + "debug": "^4.3.4", + "retry": "^0.13.1", + "typescript-event-target": "^1.1.0" + } + }, "node_modules/jiti": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", @@ -1834,6 +2069,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha3": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.2.tgz", + "integrity": "sha512-8kgvwd03wNGQG1GRvl3yy1Yt40sICAcIMsDU2ZLgoL0Z6z9rkRmf9Vd+bi/gYSzgAqMUGl/jiDKu0J8AWFd+BQ==" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1965,7 +2205,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -1974,7 +2213,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -2046,8 +2284,16 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } }, "node_modules/mz": { "version": "2.7.0", @@ -2132,6 +2378,14 @@ "node": ">= 6" } }, + "node_modules/object-path": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", + "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", + "engines": { + "node": ">= 10.12.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2188,6 +2442,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/package-json-type/-/package-json-type-1.0.3.tgz", + "integrity": "sha512-Bey4gdRuOwDbS8Fj1qA3/pTq5r8pqiI5E3tjSqCdhaLSsyGG364VFzXLTIexN5AaNGe/vgdBzLfoKdr7EVg2KQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2261,7 +2520,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -2477,7 +2735,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -2540,11 +2797,26 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -2585,7 +2857,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -3078,7 +3349,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -3102,10 +3372,9 @@ "dev": true }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -3165,7 +3434,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3174,6 +3442,11 @@ "node": ">=14.17" } }, + "node_modules/typescript-event-target": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/typescript-event-target/-/typescript-event-target-1.1.0.tgz", + "integrity": "sha512-PMrzUVryhnUq2n8M7tjNHNRuIHlUqly5RfGltBTpPCdVpbytgALTRDegF/t6+mFmmtBVhOqEYlbjVNBxwabIug==" + }, "node_modules/undici": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", @@ -3186,6 +3459,17 @@ "node": ">=14.0" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -3402,6 +3686,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/examples/websocket-relay/relay-app/package.json b/examples/websocket-relay/relay-app/package.json index 3d191343..3d77056f 100644 --- a/examples/websocket-relay/relay-app/package.json +++ b/examples/websocket-relay/relay-app/package.json @@ -37,8 +37,10 @@ }, "type": "module", "dependencies": { + "@fission-codes/homestar": "^1.0.0", "@zerodevx/svelte-json-view": "^1.0.3", "clipboard-copy": "^4.0.1", + "iso-base": "^2.0.1", "svelvet": "8.1.0" } } diff --git a/examples/websocket-relay/relay-app/src/components/WorkflowDetail.svelte b/examples/websocket-relay/relay-app/src/components/WorkflowDetail.svelte index 4dad639e..ce1f1f51 100644 --- a/examples/websocket-relay/relay-app/src/components/WorkflowDetail.svelte +++ b/examples/websocket-relay/relay-app/src/components/WorkflowDetail.svelte @@ -3,7 +3,7 @@ import { slide } from "svelte/transition"; import { quartOut } from "svelte/easing"; - import { workflowOneJson, workflowTwoJson } from "$lib/workflow"; + import { workflowOnePromised, workflowTwoPromised } from "$lib/workflow";
Workflow One
- + {#await workflowOnePromised then workflowOne} + + {/await}
Workflow Two
- + {#await workflowTwoPromised then workflowTwo} + + {/await}
diff --git a/examples/websocket-relay/relay-app/src/lib/channel.ts b/examples/websocket-relay/relay-app/src/lib/channel.ts deleted file mode 100644 index 857d8191..00000000 --- a/examples/websocket-relay/relay-app/src/lib/channel.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { get as getStore } from "svelte/store"; - -import type { Maybe } from "$lib"; - -import { activeWorkflowStore, channelStore } from "../stores"; -import { fail, handleMessage } from "./workflow"; - -// TYPES - -export type Channel = { - close: () => void; - send: (data: ChannelData) => void; -}; - -export type ChannelOptions = { - handleMessage: (event: MessageEvent) => void; -}; - -export type ChannelData = string | ArrayBufferLike | Blob | ArrayBufferView; - -export async function connect() { - const channel = await createWssChannel( - import.meta.env.VITE_WEBSOCKET_ENDPOINT, - { handleMessage } - ); - - channelStore.set(channel); - - setInterval(() => { - channel.send("ping"); - - setTimeout(() => { - const activeWorkflow = getStore(activeWorkflowStore); - - if (!activeWorkflow) return; - - // Check failed ping count for lost connection - const failedPingCount = activeWorkflow.failedPingCount; - - if (failedPingCount >= import.meta.env.VITE_MAX_PING_RETRIES) { - // Fail the workflow - fail(activeWorkflow.id); - - // Remove channel. Connection will be re-established on next workflow run. - channelStore.set(null); - } else { - // Assume failure. We reset the count to zero in the message handler on pong. - activeWorkflowStore.update((store) => - store ? { ...store, failedPingCount: failedPingCount + 1 } : null - ); - } - }, import.meta.env.VITE_PING_INTERVAL); - }, import.meta.env.VITE_PING_INTERVAL); -} - -export const createWssChannel = async ( - endpoint: string, - options: ChannelOptions -): Promise => { - const { handleMessage } = options; - - const topic = `ipvm-homestar`; - console.log("Opening channel", topic); - - const socket: Maybe = new WebSocket(endpoint); - await waitForOpenConnection(socket); - socket.onmessage = handleMessage; - socket.onerror = (err) => { - console.log("socket error", err); - }; - - const send = publishOnWssChannel(socket); - const close = closeWssChannel(socket); - - return { - send, - close, - }; -}; - -const waitForOpenConnection = async (socket: WebSocket): Promise => { - return new Promise((resolve, reject) => { - socket.onopen = () => resolve(); - socket.onerror = () => reject("Websocket channel could not be opened"); - }); -}; - -export const closeWssChannel = (socket: Maybe): (() => void) => { - return function () { - if (socket) socket.close(1000); - }; -}; - -export const publishOnWssChannel = ( - socket: Maybe -): ((data: ChannelData) => void) => { - return function (data: ChannelData) { - const binary = - typeof data === "string" ? new TextEncoder().encode(data).buffer : data; - - socket?.send(binary); - }; -}; diff --git a/examples/websocket-relay/relay-app/src/lib/workflow.ts b/examples/websocket-relay/relay-app/src/lib/workflow.ts index d28d6b1c..d362620e 100644 --- a/examples/websocket-relay/relay-app/src/lib/workflow.ts +++ b/examples/websocket-relay/relay-app/src/lib/workflow.ts @@ -1,16 +1,22 @@ +import { base64 } from "iso-base/rfc4648"; import { get as getStore } from "svelte/store"; +import type { MaybeResult } from "@fission-codes/homestar/codecs/types"; +import type { + Receipt as RawReceipt, + WorkflowNotification, + WorkflowNotificationError, +} from "@fission-codes/homestar"; +import * as WorkflowBuilder from "@fission-codes/homestar/workflow"; -import { base64CatStore, firstWorkflowToRunStore } from "../stores"; import type { Receipt, TaskOperation, TaskStatus, Meta } from "$lib/task"; import { activeWorkflowStore, - channelStore, + firstWorkflowToRunStore, + homestarStore, taskStore, workflowStore, } from "../stores"; -import { connect, type Channel } from "$lib/channel"; -import type { Maybe } from "$lib"; export type Workflow = { id: WorkflowId; @@ -28,15 +34,10 @@ export type WorkflowId = "one" | "two"; // RUN export async function run(workflowId: WorkflowId) { - let channel = getStore(channelStore); const firstWorkflowToRun = getStore(firstWorkflowToRunStore); + const homestar = getStore(homestarStore); const tasks = getStore(taskStore); - if (!channel) { - await connect(); - channel = getStore(channelStore); - } - // Reset workflow UI and state reset(workflowId); @@ -61,27 +62,56 @@ export async function run(workflowId: WorkflowId) { // Send run command to server if (workflowId === "one") { - channel?.send( - JSON.stringify({ - action: "run", - name: workflowId, - workflow: workflowOneJson, - }) - ); + const workflowOne = await workflowOnePromised; + homestar.runWorkflow(workflowOne, handleMessage); } else if (workflowId === "two") { - channel?.send( - JSON.stringify({ - action: "run", - name: workflowId, - workflow: workflowTwoJson, - }) - ); + const workflowTwo = await workflowTwoPromised; + homestar.runWorkflow(workflowTwo, handleMessage); } - if (import.meta.env.VITE_EMULATION_MODE === "true") { - // Emulate with an echo server - emulate(workflowId, channel); - } + checkHealth(); +} + +/** + * Check health and fail workflow when the Homestar node does not respond in time. + */ +function checkHealth() { + const homestar = getStore(homestarStore); + + let interval = setInterval(async () => { + const activeWorkflow = getStore(activeWorkflowStore); + + if (activeWorkflow) { + if (activeWorkflow.step === activeWorkflow.tasks.length - 1) { + // Workflow completed + clearInterval(interval); + } + + if ( + activeWorkflow.failedPingCount >= import.meta.env.VITE_MAX_PING_RETRIES + ) { + // Fail the workflow + fail(activeWorkflow.id); + clearInterval(interval); + } + + const health = (await homestar.health()).result; + if (health?.healthy) { + activeWorkflowStore.update((store) => + store ? { ...store, failedPingCount: 0 } : null + ); + } else { + activeWorkflowStore.update((store) => + store + ? { ...store, failedPingCount: activeWorkflow.failedPingCount + 1 } + : null + ); + } + } else { + // No workflow active + clearInterval(interval); + } + }, import.meta.env.VITE_PING_INTERVAL); } /** @@ -132,90 +162,74 @@ export function fail(workflowId: WorkflowId) { // HANDLER -export async function handleMessage(event: MessageEvent) { - const data = await event.data.text(); - +export async function handleMessage( + data: MaybeResult +) { console.log("Received message from server: ", data); - // Reset ping count on echoed ping or pong from server - if (data === "ping" || data === "pong") { - activeWorkflowStore.update((store) => - store ? { ...store, failedPingCount: 0 } : null - ); + if (data.error) { + throw data.error; + } + + const activeWorkflow = getStore(activeWorkflowStore); + if (!activeWorkflow) { + console.error("Received a receipt but workflow was not initiated"); return; } - const message = JSON.parse(data); - if (message.receipt !== undefined && message.receipt.meta !== undefined) { - const activeWorkflow = getStore(activeWorkflowStore); + const taskId = activeWorkflow.step + 1; + const status = data.result.metadata.replayed ? "replayed" : "executed"; + const receipt = parseReceipt(data.result.receipt); - if (!activeWorkflow) { - console.error("Received a receipt but workflow was not initiated"); - return; - } + // Update task in UI + taskStore.update((store) => { + const updatedTasks = store[activeWorkflow.id].map((t) => + t.id === taskId + ? { + ...t, + status, + message: getTaskMessage(status), + receipt, + } + : t + ); - const taskId = activeWorkflow.step + 1; - const status = message.metadata.replayed ? "replayed" : "executed"; - const receipt = parseReceipt(message.receipt); - - // Update task in UI - taskStore.update((store) => { - const updatedTasks = store[activeWorkflow.id].map((t) => - t.id === taskId - ? { - ...t, - status, - message: getTaskMessage(status), - receipt, - } - : t - ); - - return { ...store, [activeWorkflow.id]: updatedTasks }; - }); + return { ...store, [activeWorkflow.id]: updatedTasks }; + }); - // Log receipt - console.table(receipt); + // Log receipt + console.table(receipt); - if (activeWorkflow.step === activeWorkflow.tasks.length - 1) { - // Workflow is done. Reset workflow status to waiting. - workflowStore.update((workflows) => ({ - ...workflows, - [activeWorkflow.id]: { - ...workflows[activeWorkflow.id], - status: "waiting", - }, - })); + if (activeWorkflow.step === activeWorkflow.tasks.length - 1) { + // Workflow is done. Reset workflow status to waiting. + workflowStore.update((workflows) => ({ + ...workflows, + [activeWorkflow.id]: { + ...workflows[activeWorkflow.id], + status: "waiting", + }, + })); - // Deactivate workflow - activeWorkflowStore.set(null); - } else { - // Increment workflow step - activeWorkflowStore.update((store) => - store ? { ...store, step: store.step + 1 } : null - ); - } + // Deactivate workflow + activeWorkflowStore.set(null); } else { - console.warn("Received an unexpected message", message); + // Increment workflow step + activeWorkflowStore.update((store) => + store ? { ...store, step: store.step + 1 } : null + ); } } -function parseReceipt(raw: { - iss: string | null; - meta: Meta | null; - out: ["ok" | "error", Record<"/", Record<"bytes", string>>]; - prf: string[]; - ran: Record<"/", string>; -}): Receipt { +const parseReceipt = (raw: RawReceipt): Receipt => { return { - iss: raw.iss, - meta: raw.meta, - out: [raw.out[0], raw.out[1]["/"].bytes], - prf: raw.prf, - ran: raw.ran["/"], + iss: raw.iss ?? null, + meta: raw.meta as Meta, + out: [raw.out[0], base64.encode(raw.out[1])], + prf: raw.prf.map(toString), + ran: raw.ran.toString(), }; -} +}; function getTaskMessage(status: TaskStatus) { switch (status) { @@ -233,226 +247,77 @@ function getTaskMessage(status: TaskStatus) { } } -// JSON WORKFLOWS - -export const workflowOneJson = { - tasks: [ - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm", - }, - 150, - 350, - 500, - 500, - ], - func: "crop", +// WORKFLOWS + +export const workflowOnePromised = WorkflowBuilder.workflow({ + name: "one", + workflow: { + tasks: [ + WorkflowBuilder.crop({ + name: "crop", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{ cid:bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm }}", + x: 150, + y: 350, + height: 500, + width: 500, }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "await/ok": { - "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy", - }, - }, - ], - func: "rotate90", + }), + WorkflowBuilder.rotate90({ + name: "rotate90", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{needs.crop.output}}", }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "await/ok": { - "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4", - }, - }, - 20.2, - ], - func: "blur", + }), + WorkflowBuilder.blur({ + name: "blur", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{needs.rotate90.output}}", + sigma: 20.2, }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - ], -}; - -export const workflowTwoJson = { - tasks: [ - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "/": "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm", - }, - 150, - 350, - 500, - 500, - ], - func: "crop", + }), + ], + }, +}); + +export const workflowTwoPromised = WorkflowBuilder.workflow({ + name: "two", + workflow: { + tasks: [ + WorkflowBuilder.crop({ + name: "crop", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{ cid:bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm }}", + x: 150, + y: 350, + height: 500, + width: 500, }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "await/ok": { - "/": "bafyrmigev36skyfjnslfswcez24rnrorzeaxkrpb3wci2arfkly5zcrepy", - }, - }, - ], - func: "rotate90", + }), + WorkflowBuilder.rotate90({ + name: "rotate90", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{needs.crop.output}}", }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - { - cause: null, - meta: { - memory: 4294967296, - time: 100000, - }, - prf: [], - run: { - input: { - args: [ - { - "await/ok": { - "/": "bafyrmiegkif6ofatmowjjmw7yttm7mi5pjjituoxtp5qqsmc3fw65ypbm4", - }, - }, - ], - func: "grayscale", + }), + WorkflowBuilder.grayscale({ + name: "grayscale", + resource: + "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + args: { + data: "{{needs.rotate90.output}}", }, - nnc: "", - op: "wasm/run", - rsc: "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", - }, - }, - ], -}; - -// EMULATION - -function emulate(workflowId: string, channel: Maybe) { - if (!channel) { - console.error("Cannot emulate. Channel has not been set."); - return; - } - - if (workflowId === "one") { - Promise.resolve() - .then(() => sendEmulated("executed", "one", "crop", channel, 500)) - .then(() => sendEmulated("executed", "one", "rotate90", channel, 1500)) - .then(() => sendEmulated("executed", "one", "blur", channel, 20000)); - } else if (workflowId === "two") { - Promise.resolve() - .then(() => sendEmulated("replayed", "two", "crop", channel, 200)) - .then(() => sendEmulated("replayed", "two", "rotate90", channel, 200)) - .then(() => sendEmulated("executed", "two", "grayscale", channel, 1500)); - } -} - -function sendEmulated( - status: TaskStatus, - workflowId: string, - op: TaskOperation, - channel: Channel, - delay: number -) { - return new Promise((resolve) => { - setTimeout(() => { - const message = JSON.stringify(sampleReceipt(status, workflowId, op)); - - channel.send(message); - - resolve(null); - }, delay); - }); -} - -function sampleReceipt( - status: TaskStatus, - workflowId: string, - op: TaskOperation -) { - const base64Cat = getStore(base64CatStore); - - return { - metadata: { - name: workflowId, - replayed: status == "executed" ? false : true, - receipt_cid: { - "/": "bafyrmiczrugtx6jj42qbwd2ctlmj766th2nwzfsqmvathjdxk63rwkkvpi", - }, - }, - receipt: { - iss: null, - meta: { - op: op, - workflow: "bafyrmiczrugtx6jj42qbwd2ctlmj766th2nwzfsqmvathjdxk63rwkkvpd", - }, - out: ["ok", { "/": { bytes: `${base64Cat}` } }], - prf: [], - ran: { - "/": "bafkr4ickinozehpaz72vtgpbhhqpf6v2fi67rvr6uis52bwsesoss6vinq", - }, - }, - }; -} + }), + ], + }, +}); diff --git a/examples/websocket-relay/relay-app/src/routes/+page.svelte b/examples/websocket-relay/relay-app/src/routes/+page.svelte index f8b54fa7..4f96b10c 100644 --- a/examples/websocket-relay/relay-app/src/routes/+page.svelte +++ b/examples/websocket-relay/relay-app/src/routes/+page.svelte @@ -2,8 +2,7 @@ import { onDestroy } from "svelte"; import { Svelvet } from "svelvet"; - import { connect } from "$lib/channel"; - import { base64CatStore, nodeStore, taskStore } from "../stores"; + import { base64CatStore, nodeStore } from "../stores"; import Controls from "$components/Controls.svelte"; import Header from "$components/Header.svelte"; import WorkflowDetail from "$components/WorkflowDetail.svelte"; @@ -37,9 +36,6 @@ // Set spacecat unmodified image const fetchCat = initializeSpaceCat(); - // Connect to websocket server - connect(); - onDestroy(() => { unsubscribeNodeStore(); }); diff --git a/examples/websocket-relay/relay-app/src/stores.ts b/examples/websocket-relay/relay-app/src/stores.ts index 78b1a158..e5d483fd 100644 --- a/examples/websocket-relay/relay-app/src/stores.ts +++ b/examples/websocket-relay/relay-app/src/stores.ts @@ -1,16 +1,26 @@ -import { derived, writable } from "svelte/store"; -import type { Readable, Writable } from "svelte/store"; +import { + derived, + readable, + writable, + type Readable, + type Writable, +} from "svelte/store"; +import { Homestar } from "@fission-codes/homestar"; import type { NodeProps } from "svelvet"; +import { WebsocketTransport } from "@fission-codes/homestar/transports/ws.js"; -import type { Channel } from "$lib/channel"; import type { Workflow, WorkflowId, WorkflowState } from "$lib/workflow"; import type { Maybe } from "$lib"; import type { Task } from "$lib/task"; -// Initialized in +page.svelte -export const base64CatStore: Writable = writable("") +export const homestarStore: Readable = readable( + new Homestar({ + transport: new WebsocketTransport(import.meta.env.VITE_WEBSOCKET_ENDPOINT), + }) +); -export const channelStore: Writable> = writable(null); +// Initialized in +page.svelte +export const base64CatStore: Writable = writable(""); export const workflowStore: Writable> = writable({ one: { @@ -26,7 +36,8 @@ export const workflowStore: Writable> = writable({ export const activeWorkflowStore: Writable> = writable(null); -export const firstWorkflowToRunStore: Writable<'one' | 'two'| null> = writable(null) +export const firstWorkflowToRunStore: Writable<"one" | "two" | null> = + writable(null); export const taskStore: Writable> = writable({ one: [ @@ -84,13 +95,13 @@ export const taskStore: Writable> = writable({ }); const NODE_TASK_MAP = { - '2': 'crop', - '3': 'rotate90', - '4': 'blur', - '5': 'crop', - '6': 'rotate90', - '7': 'grayscale', -} + "2": "crop", + "3": "rotate90", + "4": "blur", + "5": "crop", + "6": "rotate90", + "7": "grayscale", +}; export const nodeStore: Readable = derived( [firstWorkflowToRunStore, taskStore], ($stores) => { From 0aa6e3ad7aabe1d005f7605187d3f19ef57ad159 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 29 Nov 2023 17:57:23 +0000 Subject: [PATCH 41/42] feat: add npm package to homestar runtime (#434) - adds npm packages to homestar-runtime. - adds CI/CD to build and publish wrapper package and os specific packages. --- .github/workflows/builds.yml | 71 +++++- .gitignore | 3 + Cross.toml | 4 +- README.md | 1 + homestar-runtime/npm/base/index.js | 42 ++++ homestar-runtime/npm/base/package-lock.json | 263 ++++++++++++++++++++ homestar-runtime/npm/base/package.json | 38 +++ homestar-runtime/npm/package.json.tmpl | 22 ++ homestar-runtime/npm/readme.md | 75 ++++++ 9 files changed, 515 insertions(+), 4 deletions(-) create mode 100644 homestar-runtime/npm/base/index.js create mode 100644 homestar-runtime/npm/base/package-lock.json create mode 100644 homestar-runtime/npm/base/package.json create mode 100644 homestar-runtime/npm/package.json.tmpl create mode 100644 homestar-runtime/npm/readme.md diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index ae1ee665..e22ad4e9 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -1,7 +1,6 @@ name: ⚃ Builds # TODO: brew formula (Macs), cargo-wix (Windows Installs), cargo-aur (Arch) -# TODO: bring back release hook (vs on PR) on: workflow_dispatch: @@ -16,7 +15,7 @@ on: # for debugging # pull_request: - # branches: [ '**' ] + # branches: ['**'] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -30,14 +29,19 @@ jobs: include: - target: aarch64-unknown-linux-gnu - target: aarch64-unknown-linux-musl + npm: linux-arm64 - target: aarch64-apple-darwin os: macos-latest + npm: darwin-arm64 - target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-musl + npm: linux-x64 - target: x86_64-apple-darwin os: macos-latest + npm: darwin-x64 - target: x86_64-pc-windows-msvc os: windows-latest + npm: windows-x64 - target: x86_64-unknown-freebsd permissions: @@ -95,6 +99,69 @@ jobs: include: LICENSE,README.md token: ${{ secrets.GITHUB_TOKEN }} + npm-publish: + needs: binary-builds + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - target: aarch64-unknown-linux-musl + os: linux + arch: arm64 + - target: x86_64-unknown-linux-musl + os: linux + arch: x64 + - target: aarch64-apple-darwin + os: darwin + arch: arm64 + - target: x86_64-apple-darwin + os: darwin + arch: x64 + - target: x86_64-pc-windows-msvc + os: windows + arch: x64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + registry-url: 'https://registry.npmjs.org' + - name: Install cargo get + run: cargo install cargo-get + - name: Prepare os/arch packages + shell: bash + env: + node_os: ${{ matrix.os }} + node_arch: ${{ matrix.arch }} + node_pkg: homestar-${{ matrix.os }}-${{ matrix.arch }} + run: | + export node_version=$(cargo get workspace.package.version) + echo "node_pkg=${node_pkg}" >> "$GITHUB_ENV" + cd homestar-runtime/npm + mkdir -p "${node_pkg}/bin" + envsubst < package.json.tmpl > "${node_pkg}/package.json" + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: ${{ matrix.target }} + path: 'homestar-runtime/npm/${{ env.node_pkg }}/bin' + - name: Publish production + if: github.event_name == 'release' && github.event.action == 'published' + run: | + cd "homestar-runtime/npm/${{ env.node_pkg }}" + npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + - name: Publish RC + if: github.event_name == 'workflow_dispatch' + run: | + cd "homestar-runtime/npm/${{ env.node_pkg }}" + npm version $(cargo get package.version)-rc.$(date +%s) --git-tag-version false + npm publish --access public --tag rc + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + build-packages: runs-on: ubuntu-latest strategy: diff --git a/.gitignore b/.gitignore index 8e6e9508..2279e665 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ examples/**/tmp/* # nix build results /result + +# npm packages +homestar-runtime/npm/binaries \ No newline at end of file diff --git a/Cross.toml b/Cross.toml index ff92d71f..78ef8d29 100644 --- a/Cross.toml +++ b/Cross.toml @@ -17,8 +17,8 @@ passthrough = [ [target.x86_64-unknown-linux-musl] image = "burntsushi/cross:x86_64-unknown-linux-musl" -[target.aarch64-unknown-linux-musl] -image = "burntsushi/cross:aarch64-unknown-linux-musl" +[target.aarch64-unknown-linux-gnu] +image = "burntsushi/cross:aarch64-unknown-linux-gnu" [target.x86_64-apple-darwin] image = "freeznet/x86_64-apple-darwin-cross:11.3" diff --git a/README.md b/README.md index 32e41165..6aa1a7e7 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ different platforms. - [homebrew][homebrew]: `brew install fission-codes/fission/homestar` This includes `ipfs` in the install by default. +- [npm](https://www.npmjs.com/package/homestar-runtime): `npm install homestar-runtime -g` Wraps the `homestar-runtime` binary in a node script. ## Running Examples diff --git a/homestar-runtime/npm/base/index.js b/homestar-runtime/npm/base/index.js new file mode 100644 index 00000000..65abe3ff --- /dev/null +++ b/homestar-runtime/npm/base/index.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +import { execa } from 'execa' +import { createRequire } from 'module' +const require = createRequire(import.meta.url) + +/** + * Returns the executable path which is located inside `node_modules` + * The naming convention is app-${os}-${arch} + * If the platform is `win32` or `cygwin`, executable will include a `.exe` extension. + * @see https://nodejs.org/api/os.html#osarch + * @see https://nodejs.org/api/os.html#osplatform + * @example "x/xx/node_modules/app-darwin-arm64" + */ +function getExePath() { + const arch = process.arch + let os = process.platform + let extension = '' + if (['win32', 'cygwin'].includes(process.platform)) { + os = 'windows' + extension = '.exe' + } + + try { + // Since the binary will be located inside `node_modules`, we can simply call `require.resolve` + return require.resolve(`homestar-${os}-${arch}/bin/homestar${extension}`) + } catch (e) { + throw new Error( + `Couldn't find application binary inside node_modules for ${os}-${arch}` + ) + } +} + +/** + * Runs the application with args using nodejs spawn + */ +function run() { + const args = process.argv.slice(2) + execa(getExePath(), args, { stdio: 'inherit' }) +} + +run() diff --git a/homestar-runtime/npm/base/package-lock.json b/homestar-runtime/npm/base/package-lock.json new file mode 100644 index 00000000..dcf9b431 --- /dev/null +++ b/homestar-runtime/npm/base/package-lock.json @@ -0,0 +1,263 @@ +{ + "name": "homestar-runtime", + "version": "0.0.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "homestar-runtime", + "version": "0.0.8", + "license": "MIT", + "dependencies": { + "execa": "^8.0.1" + }, + "bin": { + "homestar": "index.js" + }, + "optionalDependencies": { + "homestar-darwin-arm64": "*", + "homestar-darwin-x64": "*", + "homestar-linux-arm64": "*", + "homestar-linux-x64": "*", + "homestar-windows-arm64": "*", + "homestar-windows-x64": "*" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/homestar-darwin-arm64": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/homestar-darwin-arm64/-/homestar-darwin-arm64-0.0.1.tgz", + "integrity": "sha512-ftcZyXJalctBtj3jhTepLVE6LjNaB/k2KB9zAAZjQi6neAKs+MMTqaRt8TV3/X16hOryOeyjDPCshgbGnqpBJw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/homestar-darwin-x64": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/homestar-darwin-x64/-/homestar-darwin-x64-0.0.1.tgz", + "integrity": "sha512-DT4H2XnKD6bwjY/3ooYRwfqnP8maKlLp53ZOkeSPIWT8HDf7DI/6WJxeZZy8AGkMox5SU0xP64CrIQ3W/D57NA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/homestar-linux-arm64": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/homestar-linux-arm64/-/homestar-linux-arm64-0.0.1.tgz", + "integrity": "sha512-IKDrLIvZWmp1ZrcYyySV1xp7wOYOCHPELeuiOEd0a3YuHssURXS4CdibUGKXGnTnxv7w7bjNla5HAVyOnC/dNA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/homestar-linux-x64": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/homestar-linux-x64/-/homestar-linux-x64-0.0.1.tgz", + "integrity": "sha512-LuY2HA3SM1B5B4LFpyb+eAKHFaKlEJ0vtkr/aFJCR9d0SA/omf3ZpqmeT4zDrCNgCnqT81rvVDjBOP094890zw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/homestar-runtime/npm/base/package.json b/homestar-runtime/npm/base/package.json new file mode 100644 index 00000000..d5a6669a --- /dev/null +++ b/homestar-runtime/npm/base/package.json @@ -0,0 +1,38 @@ +{ + "name": "homestar-runtime", + "version": "0.0.8", + "description": "The IPVM reference implementation", + "author": "Hugo Dias (hugodias.me)", + "homepage": "https://github.com/ipvm-wg/homestar/tree/main/homestar-runtime", + "repository": { + "url": "ipvm-wg/homestar", + "directory": "homestar-runtime" + }, + "keywords": [ + "homestar", + "wasm", + "wit", + "webassembly", + "workflows", + "scheduling" + ], + "bin": { + "homestar": "index.js" + }, + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "license": "Apache-2.0", + "optionalDependencies": { + "homestar-darwin-arm64": "*", + "homestar-darwin-x64": "*", + "homestar-linux-arm64": "*", + "homestar-linux-x64": "*", + "homestar-windows-arm64": "*", + "homestar-windows-x64": "*" + }, + "dependencies": { + "execa": "^8.0.1" + } +} diff --git a/homestar-runtime/npm/package.json.tmpl b/homestar-runtime/npm/package.json.tmpl new file mode 100644 index 00000000..90d0dc29 --- /dev/null +++ b/homestar-runtime/npm/package.json.tmpl @@ -0,0 +1,22 @@ +{ + "name": "${node_pkg}", + "version": "${node_version}", + "description": "The IPVM reference implementation", + "homepage": "https://github.com/ipvm-wg/homestar/tree/main/homestar-runtime", + "repository": { + "url": "ipvm-wg/homestar", + "directory": "homestar-runtime" + }, + "keywords": ["homestar", "wasm", "wit", "webassembly", "workflows", "scheduling"], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Hugo Dias (hugodias.me)", + "license": "Apache-2.0", + "os": [ + "${node_os}" + ], + "cpu": [ + "${node_arch}" + ] +} diff --git a/homestar-runtime/npm/readme.md b/homestar-runtime/npm/readme.md new file mode 100644 index 00000000..cd51c934 --- /dev/null +++ b/homestar-runtime/npm/readme.md @@ -0,0 +1,75 @@ +# Homestar NPM packages + +## Packages + +- [homestar-runtime](https://www.npmjs.com/package/homestar-runtime) - This is the main package that installs the os specific binary package and runs it. +- [homestar-darwin-arm64](https://www.npmjs.com/package/homestar-darwin-arm64) +- [homestar-darwin-x64](https://www.npmjs.com/package/homestar-darwin-x64) +- [homestar-linux-arm64](https://www.npmjs.com/package/homestar-linux-arm64) +- [homestar-linux-x64](https://www.npmjs.com/package/homestar-linux-x64) +- [homestar-windows-x64](https://www.npmjs.com/package/homestar-windows-x64) + +## Usage + +```bash +npx homestar-runtime --help + +# Global install +npm install -g homestar-runtime +homestar start -c config.toml +``` + +## Manual publishing + +```bash + +rustup target add aarch64-unknown-linux-gnu +rustup target add x86_64-unknown-linux-musl +cargo install cargo-get + + +export node_version=$(cargo get workspace.package.version) +export bin="homestar" + + +## darwin arm64 +cargo build -p homestar-runtime --features ansi-logs --locked --release --target aarch64-apple-darwin +export node_os=darwin +export node_arch=arm64 +export node_pkg="${bin}-${node_os}-${node_arch}" +mkdir -p "binaries/${node_pkg}/bin" +envsubst < package.json.tmpl > "binaries/${node_pkg}/package.json" +cp "../../target/aarch64-apple-darwin/release/${bin}" "binaries/${node_pkg}/bin" + +## darwin x64 +cross build -p homestar-runtime --features ansi-logs --locked --release --target x86_64-apple-darwin +export node_os=darwin +export node_arch=x64 +export node_pkg="${bin}-${node_os}-${node_arch}" +mkdir -p "binaries/${node_pkg}/bin" +envsubst < package.json.tmpl > "binaries/${node_pkg}/package.json" +cp "../../target/x86_64-apple-darwin/release/${bin}" "binaries/${node_pkg}/bin" + +## linux arm64 +cross build -p homestar-runtime --features ansi-logs --locked --release --target aarch64-unknown-linux-gnu +export node_os=linux +export node_arch=arm64 +export node_pkg="${bin}-${node_os}-${node_arch}" +mkdir -p "binaries/${node_pkg}/bin" +envsubst < package.json.tmpl > "binaries/${node_pkg}/package.json" +cp "../../target/aarch64-unknown-linux-gnu/release/${bin}" "binaries/${node_pkg}/bin" + +## linux x64 +cross build -p homestar-runtime --features ansi-logs --locked --release --target x86_64-unknown-linux-musl +export node_os=linux +export node_arch=x64 +export node_pkg="${bin}-${node_os}-${node_arch}" +mkdir -p "binaries/${node_pkg}/bin" +envsubst < package.json.tmpl > "binaries/${node_pkg}/package.json" +cp "../../target/x86_64-unknown-linux-musl/release/${bin}" "binaries/${node_pkg}/bin" + +# publish the RC package +cd "${node_pkg}" +npm version $(cargo get package.version)-rc.$(date +%s) --git-tag-version false +npm publish --access public --tag rc +``` From f794950663b2dbf878116583ac0c0d43a316904d Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Wed, 29 Nov 2023 15:34:03 -0500 Subject: [PATCH 42/42] chore: comments, logs, & cleanup (#456) # Description Remove many TODOs, add comments on *pub* (*pub* crate), and work through logs. - Includes a few other minor fixins and additions. ## Type of change - [X] Refactor (non-breaking change that updates existing functionality) Closes #262. --------- Signed-off-by: Zeeshan Lakhani Co-authored-by: Brian Ginsburg <7957636+bgins@users.noreply.github.com> --- Cargo.lock | 21 + Cargo.toml | 2 + DEVELOPMENT.md | 41 +- README.md | 3 +- examples/README.md | 2 +- examples/websocket-relay/example_test.wasm | Bin 295627 -> 296851 bytes .../relay-app/src/lib/workflow.ts | 12 +- examples/websocket-relay/src/main.rs | 9 +- flake.lock | 12 +- flake.nix | 3 +- homestar-core/src/test_utils/workflow.rs | 2 +- homestar-core/src/workflow/instruction.rs | 4 +- homestar-core/src/workflow/task.rs | 2 +- homestar-functions/test/src/lib.rs | 12 +- homestar-functions/test/wit/host.wit | 1 + homestar-runtime/Cargo.toml | 3 +- .../src/{event_handler => }/channel.rs | 4 +- homestar-runtime/src/cli/show.rs | 2 + homestar-runtime/src/db.rs | 9 +- homestar-runtime/src/db/utils.rs | 3 + homestar-runtime/src/event_handler.rs | 47 +- homestar-runtime/src/event_handler/cache.rs | 6 + homestar-runtime/src/event_handler/event.rs | 180 +- .../src/event_handler/notification.rs | 36 +- .../src/event_handler/notification/receipt.rs | 9 +- .../src/event_handler/notification/swarm.rs | 4 + .../src/event_handler/swarm_event.rs | 312 +- homestar-runtime/src/lib.rs | 5 +- homestar-runtime/src/libp2p/mod.rs | 2 + homestar-runtime/src/libp2p/multiaddr.rs | 2 + homestar-runtime/src/main.rs | 4 +- homestar-runtime/src/metrics.rs | 2 + homestar-runtime/src/network/error.rs | 2 + homestar-runtime/src/network/mod.rs | 7 +- .../src/network/pubsub/message.rs | 4 + homestar-runtime/src/network/rpc.rs | 22 +- homestar-runtime/src/network/swarm.rs | 45 +- homestar-runtime/src/network/webserver.rs | 66 +- .../src/network/webserver/listener.rs | 4 +- .../src/network/webserver/notifier.rs | 12 +- homestar-runtime/src/network/webserver/rpc.rs | 53 +- homestar-runtime/src/receipt.rs | 5 +- homestar-runtime/src/runner.rs | 116 +- homestar-runtime/src/runner/nodeinfo.rs | 15 +- homestar-runtime/src/runner/response.rs | 2 +- homestar-runtime/src/scheduler.rs | 16 +- homestar-runtime/src/settings.rs | 4 +- .../src/settings/libp2p_config.rs | 2 +- .../src/settings/pubkey_config.rs | 34 +- homestar-runtime/src/tasks.rs | 2 - homestar-runtime/src/tasks/fetch.rs | 20 +- homestar-runtime/src/tasks/wasm.rs | 4 + .../src/test_utils/worker_builder.rs | 33 +- homestar-runtime/src/worker.rs | 72 +- homestar-runtime/src/workflow.rs | 7 +- homestar-runtime/src/workflow/info.rs | 22 + homestar-runtime/src/workflow/settings.rs | 5 + homestar-runtime/tests/cli.rs | 4 +- .../tests/fixtures/test-workflow-add-one.json | 6 +- .../test-workflow-image-pipeline.json | 10 +- .../tests/fixtures/test-workflow-jco.json | 25 +- .../fixtures/test-workflow-no-awaits1.json | 2 +- .../fixtures/test-workflow-no-awaits2.json | 4 +- homestar-runtime/tests/network.rs | 6 +- homestar-runtime/tests/webserver.rs | 9 +- homestar-wasm/fixtures/example_add.wasm | Bin 8780 -> 8774 bytes homestar-wasm/fixtures/example_add.wat | 2642 ++++++++-------- .../fixtures/example_add_component.wasm | Bin 8803 -> 8797 bytes .../fixtures/example_add_component.wat | 2644 ++++++++--------- homestar-wasm/fixtures/example_test.wasm | Bin 295627 -> 296851 bytes .../fixtures/example_test_component.wasm | Bin 296304 -> 297577 bytes 71 files changed, 3680 insertions(+), 3002 deletions(-) rename homestar-runtime/src/{event_handler => }/channel.rs (93%) diff --git a/Cargo.lock b/Cargo.lock index c63b475a..3c858554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,6 +1035,26 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -2494,6 +2514,7 @@ dependencies = [ "clap", "config", "console-subscriber", + "const_format", "criterion", "crossbeam", "daemonize", diff --git a/Cargo.toml b/Cargo.toml index d22f0536..0035ea79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ lto = true debug-assertions = false [profile.release.package.homestar-runtime] +# Will slow-down compile, but improve perf on generated code. codegen-units = 1 debug-assertions = false @@ -87,6 +88,7 @@ debug-assertions = false # Example: `cargo build -p homestar-functions-test --target wasm32-unknown-unknown --profile release-wasm-fn` [profile.release-wasm-fn] inherits = "release" +# Will slow-down compile, but improve perf on generated code. codegen-units = 1 # Tell `rustc` to optimize for small code size. opt-level = "z" # 'z' to optimize "aggressively" for size diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 072e8502..a92f110e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,6 +2,7 @@ ## Outline +- [Building and Running the Project](#building-and-running-the-project) - [Testing the Project](#testing-the-project) - [Running the Runtime on Docker](#running-the-runtime-on-docker) - [Nix](#nix) @@ -10,18 +11,51 @@ - [Recommended Development Flow](#recommended-development-flow) - [Conventional Commits](#conventional-commits) +## Building and Running the Project + +- Building `homestar`: + + For the fastest compile-times and prettiest logs while developing `homestar`, + build with: + + ``` console + cargo build --no-default-features --features dev + ``` + + This removes underlying `wasmtime` `zstd` compression while also turning on + ANSI color-coded logs. If you build with default features, `zstd` compression + and other `wasmtime` defaults will be included in the build. + +- Running the `homestar` server/runtime: + + ``` console + cargo run --no-default-features --features dev -- start + ``` + +- Running with [`tokio-console`][tokio-console] for diagnosis and debugging: + + ``` console + cargo run --no-default-features --features dev,console -- start + ``` + + Then, in another window: + + ```console + tokio-console --retain-for 60sec + ``` + ## Testing the Project - Running the tests: -We recommend using [cargo nextest][cargo-nextest], which is installed by default -in our [Nix flake](#nix) or can be [installed separately][cargo-nextest-install]. + We recommend using [cargo nextest][cargo-nextest], which is installed by default + in our [Nix flake](#nix) or can be [installed separately][cargo-nextest-install]. ```console cargo nextest run --all-features --no-capture ``` -The above command translates to this using the default `cargo test`: + This command translates to the default `cargo test` command: ```console cargo test --all-features -- --nocapture @@ -139,4 +173,5 @@ a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: [nix]:https://nixos.org/download.html [nix-flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ +[tokio-console]: https://github.com/tokio-rs/console [wit-bindgen]: https://github.com/bytecodealliance/wit-bindgen diff --git a/README.md b/README.md index 6aa1a7e7..0a0fd360 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Our current list includes: - [websocket relay](./examples/websocket-relay/README.md) - An example (browser-based) application that connects to the `homestar-runtime` over a - websocket connection in order to run a couple static Wasm-based, image + WebSocket connection in order to run a couple static Wasm-based, image processing workflows that chain inputs and outputs. ## Workspace @@ -177,7 +177,6 @@ We would be happy to try to answer your question or try opening a new issue on G - [IPVM - IPFS and WASM][ipfs-thing-ipvm] by Brooklyn Zelenka - [Breaking Down the Interplanetary Virtual Machine][blog-1] - [Ucan Invocation Spec][ucan-invocation] -- [Wasm/Wit Demo - Februrary 2023][demo-1] by Zeeshan Lakhani ## License diff --git a/examples/README.md b/examples/README.md index b0766833..a99ab3fc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,7 +5,7 @@ and the `homestar runtime`. Each example is set up as its own crate, demonstrating the necessary dependencies and setup(s). * [websocket relay](./websocket-relay) - An example (browser-based) application - that connects to the `homestar-runtime` over a websocket connection in order + that connects to the `homestar-runtime` over a WebSocket connection in order to run a couple static Wasm-based, image processing workflows that chain inputs and outputs using [inlined promises][pipelines]. diff --git a/examples/websocket-relay/example_test.wasm b/examples/websocket-relay/example_test.wasm index 0d0e7528d835ff49d349f962434efb46988b1458..15306f11e0e5d808928e8c95f241ea52cd44cbe8 100755 GIT binary patch delta 29460 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 F{|D@VwJHDr diff --git a/examples/websocket-relay/relay-app/src/lib/workflow.ts b/examples/websocket-relay/relay-app/src/lib/workflow.ts index d362620e..f71e7448 100644 --- a/examples/websocket-relay/relay-app/src/lib/workflow.ts +++ b/examples/websocket-relay/relay-app/src/lib/workflow.ts @@ -256,7 +256,7 @@ export const workflowOnePromised = WorkflowBuilder.workflow({ WorkflowBuilder.crop({ name: "crop", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{ cid:bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm }}", x: 150, @@ -268,7 +268,7 @@ export const workflowOnePromised = WorkflowBuilder.workflow({ WorkflowBuilder.rotate90({ name: "rotate90", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{needs.crop.output}}", }, @@ -276,7 +276,7 @@ export const workflowOnePromised = WorkflowBuilder.workflow({ WorkflowBuilder.blur({ name: "blur", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{needs.rotate90.output}}", sigma: 20.2, @@ -293,7 +293,7 @@ export const workflowTwoPromised = WorkflowBuilder.workflow({ WorkflowBuilder.crop({ name: "crop", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{ cid:bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm }}", x: 150, @@ -305,7 +305,7 @@ export const workflowTwoPromised = WorkflowBuilder.workflow({ WorkflowBuilder.rotate90({ name: "rotate90", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{needs.crop.output}}", }, @@ -313,7 +313,7 @@ export const workflowTwoPromised = WorkflowBuilder.workflow({ WorkflowBuilder.grayscale({ name: "grayscale", resource: - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", args: { data: "{{needs.rotate90.output}}", }, diff --git a/examples/websocket-relay/src/main.rs b/examples/websocket-relay/src/main.rs index d5b00051..b17d847a 100644 --- a/examples/websocket-relay/src/main.rs +++ b/examples/websocket-relay/src/main.rs @@ -16,18 +16,11 @@ fn main() -> Result<()> { // daemon. Typically, these would be started separately. let ipfs_daemon = ipfs_setup(); - info!( - subject = "settings", - category = "homestar_init", - "starting with settings: {:?}", - settings, - ); + info!("starting with settings: {:?}", settings,); let db = Db::setup_connection_pool(settings.node(), None).expect("to setup database pool"); info!( - subject = "database", - category = "homestar_init", "starting with database: {}", Db::url().expect("database url to be provided"), ); diff --git a/flake.lock b/flake.lock index 4c33b930..685d0257 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1699186365, - "narHash": "sha256-Pxrw5U8mBsL3NlrJ6q1KK1crzvSUcdfwb9083sKDrcU=", + "lastModified": 1701080425, + "narHash": "sha256-QUaQPXLMsgIWxY2JsbK2TlqKHtcbhf9BGpmn4ilAkrI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a0b3b06b7a82c965ae0bb1d59f6e386fe755001d", + "rev": "21ea9c80732bc4168ed38c5c2f1f4df37c57a6dd", "type": "github" }, "original": { @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1699323235, - "narHash": "sha256-ZFRItRv0dDSzsfpqSjj9qWM/SA1kRrOk6R04qhBZuxM=", + "lastModified": 1701224160, + "narHash": "sha256-qnMmxNMKmd6Soel0cfauyMJ+LzuZbvmiDQPSIuTbQ+M=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "8a9d6f544c08ee898c7f3761cc9587be7565db5e", + "rev": "4a080e26d55eaedb95ab1bf8eeaeb84149c10f12", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index cbf11fa8..b7795159 100644 --- a/flake.nix +++ b/flake.nix @@ -57,7 +57,6 @@ cargo-expand cargo-nextest cargo-sort - cargo-spellcheck cargo-unused-features cargo-udeps cargo-watch @@ -101,7 +100,7 @@ devRunServer = pkgs.writeScriptBin "cargo-run-dev" '' #!${pkgs.stdenv.shell} - cargo run --no-default-features --features dev -- start -c homestar-runtime/config/settings.toml + cargo run --no-default-features --features dev -- start ''; doc = pkgs.writeScriptBin "doc" '' diff --git a/homestar-core/src/test_utils/workflow.rs b/homestar-core/src/test_utils/workflow.rs index 956cedc4..2c11f01e 100644 --- a/homestar-core/src/test_utils/workflow.rs +++ b/homestar-core/src/test_utils/workflow.rs @@ -19,7 +19,7 @@ use std::collections::BTreeMap; use url::Url; const RAW: u64 = 0x55; -const WASM_CID: &str = "bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"; +const WASM_CID: &str = "bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a"; type NonceBytes = Vec; diff --git a/homestar-core/src/workflow/instruction.rs b/homestar-core/src/workflow/instruction.rs index 991a4134..3c9c18d8 100644 --- a/homestar-core/src/workflow/instruction.rs +++ b/homestar-core/src/workflow/instruction.rs @@ -336,7 +336,7 @@ mod test { ( RESOURCE_KEY.into(), Ipld::String( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia".into() + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a".into() ) ), (OP_KEY.into(), Ipld::String("ipld/fun".to_string())), @@ -357,7 +357,7 @@ mod test { "func": "join-strings" }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"}); + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a"}); let instruction = Instruction::::try_from(ipld.clone()).unwrap(); let instr_cid = instruction.to_cid().unwrap(); diff --git a/homestar-core/src/workflow/task.rs b/homestar-core/src/workflow/task.rs index 8bd6d914..02b35255 100644 --- a/homestar-core/src/workflow/task.rs +++ b/homestar-core/src/workflow/task.rs @@ -190,7 +190,7 @@ mod test { ( "rsc".into(), Ipld::String( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia".into(), + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a".into(), ), ), ("op".into(), Ipld::String("ipld/fun".to_string())), diff --git a/homestar-functions/test/src/lib.rs b/homestar-functions/test/src/lib.rs index 706aee9a..b4332144 100644 --- a/homestar-functions/test/src/lib.rs +++ b/homestar-functions/test/src/lib.rs @@ -6,7 +6,11 @@ wit_bindgen::generate!({ }); use base64::{engine::general_purpose, Engine}; -use std::io::Cursor; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + io::Cursor, +}; pub struct Component; @@ -115,6 +119,12 @@ impl Guest for Component { .unwrap(); Self::rotate90(decoded) } + + fn hash(s: String) -> Vec { + let mut hash = DefaultHasher::new(); + s.hash(&mut hash); + hash.finish().to_be_bytes().to_vec() + } } #[cfg(test)] diff --git a/homestar-functions/test/wit/host.wit b/homestar-functions/test/wit/host.wit index cab94571..87cdb521 100644 --- a/homestar-functions/test/wit/host.wit +++ b/homestar-functions/test/wit/host.wit @@ -13,4 +13,5 @@ world test { export grayscale-base64: func(data: string) -> list export rotate90: func(data: list) -> list export rotate90-base64: func(data: string) -> list + export hash: func(data: string) -> list } diff --git a/homestar-runtime/Cargo.toml b/homestar-runtime/Cargo.toml index e991ca26..5588f3d6 100644 --- a/homestar-runtime/Cargo.toml +++ b/homestar-runtime/Cargo.toml @@ -54,6 +54,7 @@ config = { version = "0.13", default-features = false, features = ["toml"] } console-subscriber = { version = "0.2", default-features = false, features = [ "parking_lot", ], optional = true } +const_format = "0.2" crossbeam = "0.8" dagga = "0.2" dashmap = "5.5" @@ -218,7 +219,7 @@ wait-timeout = "0.2" [features] default = ["wasmtime-default", "ipfs", "monitoring", "websocket-notify"] -dev = ["ansi-logs", "console", "ipfs", "monitoring", "websocket-notify"] +dev = ["ansi-logs", "ipfs", "monitoring", "websocket-notify"] ansi-logs = ["tracing-logfmt/ansi_logs"] console = ["dep:console-subscriber"] ipfs = ["dep:ipfs-api", "dep:ipfs-api-backend-hyper"] diff --git a/homestar-runtime/src/event_handler/channel.rs b/homestar-runtime/src/channel.rs similarity index 93% rename from homestar-runtime/src/event_handler/channel.rs rename to homestar-runtime/src/channel.rs index 4eab0e24..d06eb361 100644 --- a/homestar-runtime/src/event_handler/channel.rs +++ b/homestar-runtime/src/channel.rs @@ -1,5 +1,5 @@ -//! Wrapper around [crossbeam::channel] and [flume::bounded] to provide common -//! interfaces for sync/async bounded and non-tokio "oneshot" channels. +//! Wrapper around [crossbeam::channel] and [flume] to provide common +//! interfaces for sync/async (un)bounded and non-tokio "oneshot" channels. use crossbeam::channel; diff --git a/homestar-runtime/src/cli/show.rs b/homestar-runtime/src/cli/show.rs index c631b539..d38bdd2d 100644 --- a/homestar-runtime/src/cli/show.rs +++ b/homestar-runtime/src/cli/show.rs @@ -1,3 +1,5 @@ +//! Styled, output response for console table. + use std::{ fmt, io::{self, Write}, diff --git a/homestar-runtime/src/db.rs b/homestar-runtime/src/db.rs index 02f14fc7..f8f06238 100644 --- a/homestar-runtime/src/db.rs +++ b/homestar-runtime/src/db.rs @@ -25,7 +25,6 @@ use tracing::info; pub mod schema; pub(crate) mod utils; -pub(crate) const ENV: &str = "DATABASE_URL"; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/"); const PRAGMAS: &str = " PRAGMA journal_mode = WAL; -- better write-concurrency @@ -35,6 +34,9 @@ PRAGMA busy_timeout = 1000; -- sleep if the database is busy PRAGMA foreign_keys = ON; -- enforce foreign keys "; +/// Database environment variable. +pub(crate) const ENV: &str = "DATABASE_URL"; + /// A Sqlite connection [pool]. /// /// [pool]: r2d2::Pool @@ -92,7 +94,7 @@ pub trait Database: Send + Sync + Clone { fn setup(url: &str) -> Result { info!( subject = "database", - category = "homestar_init", + category = "homestar.init", "setting up database at {}, running migrations if needed", url ); @@ -211,6 +213,9 @@ pub trait Database: Send + Sync + Clone { } /// Store localized workflow cid and information, e.g. number of tasks. + /// + /// On conflicts, do nothing. + /// Otherwise, return the stored workflow. fn store_workflow( workflow: workflow::Stored, conn: &mut Connection, diff --git a/homestar-runtime/src/db/utils.rs b/homestar-runtime/src/db/utils.rs index 6dc7c64c..08d76e36 100644 --- a/homestar-runtime/src/db/utils.rs +++ b/homestar-runtime/src/db/utils.rs @@ -1,5 +1,8 @@ +//! Utility functions Database interaction. + use chrono::NaiveDateTime; +/// Trait for converting nanoseconds to a timestamp. pub(crate) trait Timestamp { fn timestamp_from_nanos(&self) -> Option; } diff --git a/homestar-runtime/src/event_handler.rs b/homestar-runtime/src/event_handler.rs index fdc6e9d9..0a3f9490 100644 --- a/homestar-runtime/src/event_handler.rs +++ b/homestar-runtime/src/event_handler.rs @@ -5,6 +5,7 @@ use crate::network::webserver::{self, notifier}; #[cfg(feature = "ipfs")] use crate::network::IpfsCli; use crate::{ + channel, db::Database, network::swarm::{ComposedBehaviour, PeerDiscoveryInfo, RequestResponseKey}, settings, @@ -22,7 +23,6 @@ use swarm_event::ResponseEvent; use tokio::{runtime::Handle, select}; pub(crate) mod cache; -pub mod channel; pub(crate) mod error; pub(crate) mod event; #[cfg(feature = "websocket-notify")] @@ -51,24 +51,45 @@ where #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(missing_debug_implementations, dead_code)] pub(crate) struct EventHandler { + /// Minimum number of peers required to receive a receipt. receipt_quorum: usize, + /// Minimum number of peers required to receive workflow information. workflow_quorum: usize, + /// Timeout for p2p provider requests. p2p_provider_timeout: Duration, + /// Accessible database instance. db: DB, + /// [libp2p::swarm::Swarm] swarm instance. swarm: Swarm, + /// [moka::future::Cache] instance, used for retry logic. cache: Arc>, + /// [channel::AsyncChannelSender] for sending [Event]s to the [EventHandler]. sender: Arc>, + /// [channel::AsyncChannelReceiver] for receiving [Event]s from the [EventHandler]. receiver: channel::AsyncChannelReceiver, + /// [QueryId] to [RequestResponseKey] and [P2PSender] mapping. query_senders: FnvHashMap)>, + /// [PeerId] to [ConnectedPoint] connections mapping. connections: Connections, + /// [RequestId] to [RequestResponseKey] and [P2PSender] mapping. request_response_senders: FnvHashMap, + /// Rendezvous protocol configurations and state (cookies). rendezvous: Rendezvous, + /// Whether or not to enable pubsub. pubsub_enabled: bool, + /// [tokio::sync::broadcast::Sender] for websocket event + /// notification messages. ws_evt_sender: webserver::Notifier, + /// [tokio::sync::broadcast::Sender] for websocket workflow-related + /// notification messages. ws_workflow_sender: webserver::Notifier, + /// [libp2p::Multiaddr] addresses to dial. node_addresses: Vec, + /// [libp2p::Multiaddr] externally reachable addresses to announce to the network. announce_addresses: Vec, + /// Maximum number of externally reachable addresses to announce to the network. external_address_limit: u32, + /// Interval for polling the cache for expired entries. poll_cache_interval: Duration, } @@ -76,22 +97,39 @@ pub(crate) struct EventHandler { #[cfg(not(feature = "websocket-notify"))] #[allow(missing_debug_implementations, dead_code)] pub(crate) struct EventHandler { + /// Minimum number of peers required to receive a receipt. receipt_quorum: usize, + /// Minimum number of peers required to receive workflow information. workflow_quorum: usize, + /// Timeout for p2p provider requests. p2p_provider_timeout: Duration, + /// Accesible database instance. db: DB, + /// [libp2p::swarm::Swarm] swarm instance. swarm: Swarm, + /// [moka::future::Cache] instance, centered around retry logic. cache: Arc>, + /// [channel::AsyncChannelReceiver] for receiving [Event]s from the [EventHandler]. sender: Arc>, + /// [channel::AsyncChannelReceiver] for receiving [Event]s from the [EventHandler]. receiver: channel::AsyncChannelReceiver, + /// [QueryId] to [RequestResponseKey] and [P2PSender] mapping. query_senders: FnvHashMap)>, + /// [PeerId] to [ConnectedPoint] connections mapping. connections: Connections, + /// [RequestId] to [RequestResponseKey] and [P2PSender] mapping. request_response_senders: FnvHashMap, + /// Rendezvous protocol configurations and state (cookies). rendezvous: Rendezvous, + /// Whether or not to enable pubsub. pubsub_enabled: bool, + /// [libp2p::Multiaddr] addresses to dial. node_addresses: Vec, + /// [libp2p::Multiaddr] externally reachable addresses to announce to the network. announce_addresses: Vec, + /// Maximum number of externally reachable addresses to announce to the network. external_address_limit: u32, + /// Interval for polling the cache for expired entries. poll_cache_interval: Duration, } @@ -210,8 +248,8 @@ where self.sender.clone() } - /// [tokio::sync::broadcast::Sender] for sending messages through the - /// webSocket server to subscribers. + /// [tokio::sync::broadcast::Sender] for sending workflow-related messages + /// through the WebSocket server to subscribers. #[cfg(feature = "websocket-notify")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(dead_code)] @@ -219,7 +257,8 @@ where self.ws_workflow_sender.clone() } - /// TODO + /// [tokio::sync::broadcast::Sender] for sending event-related messages + /// through the WebSocket server to subscribers. #[cfg(feature = "websocket-notify")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] #[allow(dead_code)] diff --git a/homestar-runtime/src/event_handler/cache.rs b/homestar-runtime/src/event_handler/cache.rs index a5227dad..a4fedb8b 100644 --- a/homestar-runtime/src/event_handler/cache.rs +++ b/homestar-runtime/src/event_handler/cache.rs @@ -1,3 +1,5 @@ +//! Event-handler cache for retry events. + use crate::{channel, event_handler::Event}; use libp2p::PeerId; use moka::{ @@ -24,6 +26,7 @@ impl ExpiryBase for Expiry { } } +/// A cache value, made-up of an expiration and data map. #[derive(Clone, Debug)] pub(crate) struct CacheValue { expiration: Duration, @@ -36,18 +39,21 @@ impl CacheValue { } } +/// Kinds of data to be stored in the cache. #[derive(Clone, Debug)] pub(crate) enum CacheData { Peer(PeerId), OnExpiration(DispatchEvent), } +/// Events to be dispatched on cache expiration. #[derive(Clone, Debug)] pub(crate) enum DispatchEvent { RegisterPeer, DiscoverPeers, } +/// Setup a cache with an eviction listener. pub(crate) fn setup_cache( sender: Arc>, ) -> Cache { diff --git a/homestar-runtime/src/event_handler/event.rs b/homestar-runtime/src/event_handler/event.rs index 8ab39f62..6cf2c33a 100644 --- a/homestar-runtime/src/event_handler/event.rs +++ b/homestar-runtime/src/event_handler/event.rs @@ -34,6 +34,8 @@ use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; use tokio::runtime::Handle; use tracing::{error, info, warn}; +const RENDEZVOUS_NAMESPACE: &str = "homestar"; + /// A [Receipt] captured (inner) event. #[allow(dead_code)] #[derive(Debug, Clone)] @@ -115,12 +117,10 @@ pub(crate) enum Event { RegisterPeer(PeerId), /// Discover peers from a rendezvous node. DiscoverPeers(PeerId), - /// TODO + /// Dynamically get listeners for the swarm. GetListeners(AsyncChannelSender>), } -const RENDEZVOUS_NAMESPACE: &str = "homestar"; - #[allow(unreachable_patterns)] impl Event { async fn handle_info(self, event_handler: &mut EventHandler) -> Result<()> @@ -132,7 +132,11 @@ impl Event { let _ = captured.publish_and_notify(event_handler); } Event::Shutdown(tx) => { - info!("event_handler server shutting down"); + info!( + subject = "shutdown", + category = "handle_event", + "event_handler server shutting down" + ); event_handler.shutdown().await; let _ = tx.send_async(()).await; } @@ -183,7 +187,12 @@ impl Event { } } Event::Providers(Err(err)) => { - error!("failed to find providers: {}", err); + info!( + subject = "libp2p.providers.err", + category = "handle_event", + err=?err, + "failed to find providers", + ); } Event::RegisterPeer(peer_id) => { if let Some(rendezvous_client) = event_handler @@ -199,6 +208,8 @@ impl Event { Some(event_handler.rendezvous.registration_ttl.as_secs()), ) { warn!( + subject = "libp2p.register.rendezvous.err", + category = "handle_event", peer_id = peer_id.to_string(), err = format!("{err}"), "failed to register with rendezvous peer" @@ -265,6 +276,14 @@ impl Captured { ) } + // short-circuit if no peers + // + // - don't gossip receipt + // - don't store receipt or workflow info on DHT + if event_handler.connections.peers.is_empty() { + return Ok((self.receipt, invocation_receipt)); + } + if event_handler.pubsub_enabled { match event_handler.swarm.behaviour_mut().gossip_publish( pubsub::RECEIPTS_TOPIC, @@ -272,6 +291,8 @@ impl Captured { ) { Ok(msg_id) => { info!( + subject = "libp2p.gossip.publish", + category = "publish_event", cid = receipt_cid.to_string(), message_id = msg_id.to_string(), "message published on {} topic for receipt with cid: {receipt_cid}", @@ -292,8 +313,10 @@ impl Captured { } Err(err) => { warn!( - err=?err, + subject = "libp2p.gossip.publish.err", + category = "publish_event", cid = receipt_cid.to_string(), + err=?err, "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC ) @@ -322,7 +345,12 @@ impl Captured { Record::new(instruction_bytes, receipt_bytes.to_vec()), receipt_quorum, ) - .map_err(|err| warn!(err=?err, "receipt not PUT on dht")); + .map_err(|err| { + warn!(subject = "libp2p.put_record.err", + category = "publish_event", + err=?err, + "receipt not PUT onto DHT") + }); Arc::make_mut(&mut self.workflow).increment_progress(receipt_cid); let workflow_cid_bytes = self.workflow.cid_as_bytes(); @@ -335,15 +363,27 @@ impl Captured { Record::new(workflow_cid_bytes, workflow_bytes), workflow_quorum, ) - .map_err(|err| warn!(err=?err, "workflow information not PUT on dht")); + .map_err(|err| { + warn!(subject = "libp2p.put_record.err", + category = "publish_event", + err=?err, + "workflow information not PUT onto DHT") + }); } else { error!( - "cannot convert workflow information {} to bytes", - self.workflow.cid() + subject = "libp2p.put_record.err", + category = "publish_event", + cid = self.workflow.cid().to_string(), + "cannot convert workflow information to bytes", ); } } else { - error!("cannot convert receipt {receipt_cid} to bytes"); + error!( + subject = "libp2p.put_record.err", + category = "publish_event", + cid = receipt_cid.to_string(), + "cannot convert receipt to bytes" + ); } Ok((self.receipt, invocation_receipt)) @@ -401,28 +441,35 @@ impl Replay { TopicMessage::CapturedReceipt(pubsub::Message::new(receipt.clone())), ) .map(|msg_id| { - info!(cid=receipt_cid, - message_id = msg_id.to_string(), - "message published on {} topic for receipt with cid: {receipt_cid}", - pubsub::RECEIPTS_TOPIC); - - #[cfg(feature = "websocket-notify")] - notification::emit_event( - event_handler.ws_evt_sender(), - EventNotificationTyp::SwarmNotification( - SwarmNotification::PublishedReceiptPubsub, - ), - btreemap! { - "cid" => receipt.cid().to_string(), - "ran" => receipt.ran().to_string() - }, - ); + info!( + subject = "libp2p.gossip.publish.replay", + category = "publish_event", + cid = receipt_cid, + message_id = msg_id.to_string(), + "message published on {} topic for receipt with cid: {receipt_cid}", + pubsub::RECEIPTS_TOPIC + ); + + #[cfg(feature = "websocket-notify")] + notification::emit_event( + event_handler.ws_evt_sender(), + EventNotificationTyp::SwarmNotification( + SwarmNotification::PublishedReceiptPubsub, + ), + btreemap! { + "cid" => receipt.cid().to_string(), + "ran" => receipt.ran().to_string() + }, + ); }) - .map_err( - |err| - warn!(err=?err, cid=receipt_cid, - "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC), - ); + .map_err(|err| { + warn!( + subject = "libp2p.gossip.publish.replay.err", + category = "publish_event", + err=?err, + cid=receipt_cid, + "message not published on {} topic for receipt", pubsub::RECEIPTS_TOPIC) + }); }); } Ok(()) @@ -444,8 +491,6 @@ impl QueryRecord { DB: Database, { if event_handler.connections.peers.is_empty() { - info!("no connections to send request to"); - if let Some(sender) = self.sender { let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; } @@ -468,8 +513,6 @@ impl QueryRecord { DB: Database, { if event_handler.connections.peers.is_empty() { - info!("no connections to send request to"); - if let Some(sender) = self.sender { let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; } @@ -495,8 +538,6 @@ impl QueryRecord { DB: Database, { if event_handler.connections.peers.is_empty() { - info!("no connections to send request to"); - if let Some(sender) = self.sender { let _ = sender.send_async(ResponseEvent::NoPeersAvailable).await; } @@ -534,7 +575,10 @@ where #[cfg(not(feature = "ipfs"))] async fn handle_event(self, event_handler: &mut EventHandler) { if let Err(err) = self.handle_info(event_handler).await { - error!(error=?err, "error storing event") + error!(subject = "handle.err", + category = "handle_event", + error=?err, + "error storing event") } } @@ -551,37 +595,59 @@ where let handle = Handle::current(); let ipfs = ipfs.clone(); handle.spawn(async move { - if let Ok(bytes) = receipt.try_into() { - match ipfs.put_receipt_bytes(bytes).await { - Ok(put_cid) => { - info!(cid = put_cid, "IPLD DAG node stored"); - #[cfg(debug_assertions)] - debug_assert_eq!(put_cid, cid.to_string()); - } - Err(err) => { - error!(error=?err, cid=cid.to_string(), "failed to store IPLD DAG node"); + if let Ok(bytes) = receipt.try_into() { + match ipfs.put_receipt_bytes(bytes).await { + Ok(put_cid) => { + info!( + subject = "ipfs.put.receipt", + category = "handle_event", + cid = put_cid, + "IPLD DAG node stored" + ); + #[cfg(debug_assertions)] + debug_assert_eq!(put_cid, cid.to_string()); + } + Err(err) => { + warn!(subject = "ipfs.put.receipt.err", + category = "handle_event", + error=?err, + cid=cid.to_string(), + "failed to store IPLD DAG node"); + } } + } else { + warn!( + subject = "ipfs.put.receipt.err", + category = "handle_event", + cid = cid.to_string(), + "failed to convert receipt to bytes" + ); } - } else { - warn!(cid=cid.to_string(), "failed to convert receipt to bytes"); - } - }); + }); } - #[cfg(feature = "test-utils")] - info!(cid = cid.to_string(), "cid stored on the network"); } else { - error!("failed to store receipt"); + error!( + subject = "ipfs.put.receipt.err", + category = "handle_event", + "failed to capture receipt" + ); } } #[cfg(feature = "websocket-notify")] Event::ReplayReceipts(replay) => { if let Err(err) = replay.notify(event_handler) { - error!(error=?err, "error notifying receipts") + error!(subject = "replay.err", + category = "handle_event", + error=?err, + "error replaying and notifying receipts") } } event => { if let Err(err) = event.handle_info(event_handler).await { - error!(error=?err, "error storing event") + error!(subject = "event.err", + category = "handle_event", + error=?err, + "error storing event") } } } diff --git a/homestar-runtime/src/event_handler/notification.rs b/homestar-runtime/src/event_handler/notification.rs index 1d630d6d..f074c07c 100644 --- a/homestar-runtime/src/event_handler/notification.rs +++ b/homestar-runtime/src/event_handler/notification.rs @@ -1,3 +1,5 @@ +//! Evented notifications emitted to clients. + use crate::{ network::webserver::{ notifier::{self, Header, Message, Notifier, SubscriptionTyp}, @@ -16,8 +18,8 @@ use homestar_core::{ }; use libipld::{serde::from_ipld, Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, str::FromStr}; -use tracing::{info, warn}; +use std::{collections::BTreeMap, fmt, str::FromStr}; +use tracing::{debug, warn}; pub(crate) mod receipt; pub(crate) mod swarm; @@ -39,9 +41,11 @@ pub(crate) fn emit_receipt( let notification = ReceiptNotification::with(invocation_receipt, receipt_cid, metadata.clone()); if let Ok(json) = notification.to_json() { - info!( + debug!( + subject = "notification.receipt", + category = "notification", cid = receipt_cid.to_string(), - "Sending receipt to websocket" + "emitting receipt to WebSocket" ); if let Some(ipld) = metadata { match (ipld.get(WORKFLOW_KEY), ipld.get(WORKFLOW_NAME_KEY)) { @@ -58,7 +62,12 @@ pub(crate) fn emit_receipt( } } } else { - warn!("Unable to serialize receipt as bytes: {receipt:?}"); + warn!( + subject = "notification.err", + category = "notification", + cid = receipt_cid.to_string(), + "unable to serialize receipt notification as bytes" + ); } } @@ -77,7 +86,12 @@ pub(crate) fn emit_event( if let Ok(json) = notification.to_json() { let _ = notifier.notify(Message::new(header, json)); } else { - warn!("Unable to serialize notification as bytes: {notification:?}"); + warn!( + subject = "notification.err", + category = "notification", + "unable to serialize event notification as bytes: {}", + notification.typ + ); } } @@ -153,6 +167,16 @@ pub(crate) enum EventNotificationTyp { SwarmNotification(SwarmNotification), } +impl fmt::Display for EventNotificationTyp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EventNotificationTyp::SwarmNotification(subtype) => { + write!(f, "swarm notification: {}", subtype) + } + } + } +} + impl DagJson for EventNotificationTyp where Ipld: From {} impl From for Ipld { diff --git a/homestar-runtime/src/event_handler/notification/receipt.rs b/homestar-runtime/src/event_handler/notification/receipt.rs index f2f222ec..8ca58583 100644 --- a/homestar-runtime/src/event_handler/notification/receipt.rs +++ b/homestar-runtime/src/event_handler/notification/receipt.rs @@ -1,23 +1,26 @@ +//! Notification receipts. + use homestar_core::{ipld::DagJson, workflow::Receipt}; use libipld::{ipld, Cid, Ipld}; -/// A [Receipt] that is sent out *just* for websocket notifications. +/// A [Receipt] that is sent out for websocket notifications. #[derive(Debug, Clone, PartialEq)] pub(crate) struct ReceiptNotification(Ipld); impl ReceiptNotification { - /// TODO + /// Obtain a reference to the inner [Ipld] value. #[allow(dead_code)] pub(crate) fn inner(&self) -> &Ipld { &self.0 } - /// TODO + /// Obtain ownership of the inner [Ipld] value. #[allow(dead_code)] pub(crate) fn into_inner(self) -> Ipld { self.0.to_owned() } + /// Create a new [ReceiptNotification]. pub(crate) fn with(receipt: Receipt, cid: Cid, metadata: Option) -> Self { let receipt: Ipld = receipt.into(); let data = ipld!({ diff --git a/homestar-runtime/src/event_handler/notification/swarm.rs b/homestar-runtime/src/event_handler/notification/swarm.rs index c2745bd2..d5aeda46 100644 --- a/homestar-runtime/src/event_handler/notification/swarm.rs +++ b/homestar-runtime/src/event_handler/notification/swarm.rs @@ -1,3 +1,7 @@ +// Notification types for [swarm] events. +// +// [swarm]: libp2p_swarm::Swarm + use anyhow::anyhow; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; diff --git a/homestar-runtime/src/event_handler/swarm_event.rs b/homestar-runtime/src/event_handler/swarm_event.rs index a9ced8d0..efa6abef 100644 --- a/homestar-runtime/src/event_handler/swarm_event.rs +++ b/homestar-runtime/src/event_handler/swarm_event.rs @@ -61,9 +61,9 @@ const RENDEZVOUS_NAMESPACE: &str = "homestar"; pub(crate) enum ResponseEvent { /// Found [PeerRecord] on the DHT. Found(Result), - /// TODO + /// No peers available to network with on the DHT. NoPeersAvailable, - /// Found Providers/[PeerId]s on the DHT. + /// Found providers/[PeerId]s on the DHT. Providers(Result>), } @@ -115,17 +115,33 @@ async fn handle_swarm_event( SwarmEvent::Behaviour(ComposedEvent::Identify(identify_event)) => { match identify_event { identify::Event::Error { peer_id, error } => { - warn!(peer_id=peer_id.to_string(), err=?error, "error while attempting to identify the remote") + warn!(subject = "libp2p.identify.err", + category = "handle_swarm_event", + peer_id=peer_id.to_string(), + err=?error, + "error while attempting to identify the remote") } identify::Event::Sent { peer_id } => { - debug!(peer_id = peer_id.to_string(), "sent identify info to peer") + debug!( + subject = "libp2p.identify.sent", + category = "handle_swarm_event", + peer_id = peer_id.to_string(), + "sent identify info to peer" + ) } identify::Event::Received { peer_id, info } => { - debug!(peer_id=peer_id.to_string(), info=?info, "identify info received from peer"); + debug!(subject = "libp2p.identify.recv", + category = "handle_swarm_event", + peer_id=peer_id.to_string(), + info=?info, + "identify info received from peer"); // Ignore peers that do not use the Homestar protocol if info.protocol_version != HOMESTAR_PROTOCOL_VER { - info!(protocol_version=info.protocol_version, "peer was not using our homestar protocol version: {HOMESTAR_PROTOCOL_VER}"); + debug!(subject ="libp2p.identify.recv", + category="handle_swarm_event", + protocol_version=info.protocol_version, + "peer was not using our homestar protocol version: {HOMESTAR_PROTOCOL_VER}"); return; } @@ -145,7 +161,6 @@ async fn handle_swarm_event( .all(|proto| !proto.is_private()) // Identify observed a potentially valid external address that we weren't aware of. // Add it to the addresses we announce to other peers. - // TODO: have a set of _maybe_ external addresses that we validate with other peers first before adding it .then(|| event_handler.swarm.add_external_address(info.observed_addr)); } @@ -156,6 +171,8 @@ async fn handle_swarm_event( for addr in info.listen_addrs { behavior.kademlia.add_address(&peer_id, addr); debug!( + subject = "libp2p.identify.recv", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "added identified node to kademlia routing table" ); @@ -177,6 +194,8 @@ async fn handle_swarm_event( Some(event_handler.rendezvous.registration_ttl.as_secs()), ) { warn!( + subject = "libp2p.identify.recv", + category = "handle_swarm_event", peer_id = peer_id.to_string(), err = format!("{err}"), "failed to register with rendezvous peer" @@ -194,6 +213,8 @@ async fn handle_swarm_event( } } identify::Event::Pushed { peer_id } => debug!( + subject = "libp2p.identify.pushed", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "pushed identify info to peer" ), @@ -208,6 +229,8 @@ async fn handle_swarm_event( } => { if cookie.namespace() == Some(&Namespace::from_static(RENDEZVOUS_NAMESPACE)) { debug!( + subject = "libp2p.rendezvous.client.discovered", + category = "handle_swarm_event", peer_id = rendezvous_node.to_string(), "received discovery from rendezvous server" ); @@ -222,7 +245,11 @@ async fn handle_swarm_event( // Skip dialing peers if at connected peers limit if connected_peers_count >= event_handler.connections.max_peers as usize { - warn!("peers discovered through rendezvous not dialed because max connected peers limit reached"); + debug!( + subject = "libp2p.rendezvous.client.discovered.err", + category = "handle_swarm_event", + "peers discovered not dialed because max connected peers limit reached" + ); return; } @@ -263,14 +290,18 @@ async fn handle_swarm_event( ); } Err(err) => { - warn!(peer_id=peer_id.to_string(), err=?err, "failed to dial peer discovered through rendezvous"); + warn!(subject = "libp2p.rendezvous.client.discovered.err", + category = "handle_swarm_event", + peer_id=peer_id.to_string(), + err=?err, + "failed to dial discovered peer"); } }; } else if !self_registration { - warn!( - peer_id=registration.record.peer_id().to_string(), - "peer discovered through rendezvous not dialed because the max connected peers limit was reached" - ) + debug!(subject = "libp2p.rendezvous.client.discovered.err", + category = "handle_swarm_event", + peer_id=registration.record.peer_id().to_string(), + "peer discovered not dialed because the max connected peers limit was reached") } } @@ -298,22 +329,31 @@ async fn handle_swarm_event( .await; } else { // Do not dial peers that are not using our namespace - warn!(peer_id=rendezvous_node.to_string(), namespace=?cookie.namespace(), "rendezvous peer gave records from an unexpected namespace"); + debug!(subject = "libp2p.rendezvous.client.discovered.err", + category = "handle_swarm_event", + peer_id=rendezvous_node.to_string(), + namespace=?cookie.namespace(), + "rendezvous peer gave records from an unexpected namespace"); } } rendezvous::client::Event::DiscoverFailed { rendezvous_node, error, .. - } => { - error!(peer_id=rendezvous_node.to_string(), err=?error, "failed to discover peers from rendezvous peer") - } + } => warn!(subject = "libp2p.rendezvous.client.discovered.err", + category = "handle_swarm_event", + peer_id=rendezvous_node.to_string(), + err=?error, + "failed to discover peers"), + rendezvous::client::Event::Registered { rendezvous_node, ttl, .. } => { debug!( + subject = "libp2p.rendezvous.client.registered", + category = "handle_swarm_event", peer_id = rendezvous_node.to_string(), ttl = ttl, "registered self with rendezvous node" @@ -344,7 +384,11 @@ async fn handle_swarm_event( error, .. } => { - error!(peer_id=rendezvous_node.to_string(), err=?error, "failed to register self with rendezvous peer") + warn!(subject = "libp2p.rendezvous.client.registered.err", + category = "handle_swarm_event", + peer_id=rendezvous_node.to_string(), + err=?error, + "failed to register self with rendezvous peer") } rendezvous::client::Event::Expired { peer } => { // re-discover records from peer @@ -373,14 +417,22 @@ async fn handle_swarm_event( SwarmEvent::Behaviour(ComposedEvent::RendezvousServer(rendezvous_server_event)) => { match rendezvous_server_event { rendezvous::server::Event::DiscoverServed { enquirer, .. } => debug!( + subject = "libp2p.rendezvous.server.discover", + category = "handle_swarm_event", peer_id = enquirer.to_string(), "served rendezvous discover request to peer" ), rendezvous::server::Event::DiscoverNotServed { enquirer, error } => { - warn!(peer_id=enquirer.to_string(), err=?error, "did not serve rendezvous discover request") + warn!(subject = "libp2p.rendezvous.server.discover.err", + category = "handle_swarm_event", + peer_id=enquirer.to_string(), + err=?error, + "did not serve rendezvous discover request") } rendezvous::server::Event::PeerRegistered { peer, .. } => { debug!( + subject = "libp2p.rendezvous.server.peer_registered", + category = "handle_swarm_event", peer_id = peer.to_string(), "registered peer through rendezvous" ) @@ -390,10 +442,17 @@ async fn handle_swarm_event( namespace, error, } => { - warn!(peer_id=peer.to_string(), err=?error, namespace=?namespace, "did not register peer with rendezvous") + debug!(subject = "libp2p.rendezvous.server.peer_registered.err", + category = "handle_swarm_event", + peer_id=peer.to_string(), + err=?error, + namespace=?namespace, + "did not register peer with rendezvous") } rendezvous::server::Event::RegistrationExpired(registration) => { debug!( + subject = "libp2p.rendezvous.server.registration_expired", + category = "handle_swarm_event", peer_id = registration.record.peer_id().to_string(), "rendezvous peer registration expired on server" ) @@ -409,10 +468,11 @@ async fn handle_swarm_event( } => { let bytes: Vec = message.data; match pubsub::Message::::try_from(bytes) { - // TODO: dont fail blindly if we get a non receipt message Ok(msg) => { let receipt = msg.payload; info!( + subject = "libp2p.gossipsub.recv", + category = "handle_swarm_event", peer_id = propagation_source.to_string(), message_id = message_id.to_string(), "message received on receipts topic: {}", @@ -439,16 +499,19 @@ async fn handle_swarm_event( }, ); } - Err(err) => info!(err=?err, "cannot handle incoming gossipsub message"), + Err(err) => debug!(subject = "libp2p.gossipsub.err", + category = "handle_swarm_event", + err=?err, + "cannot handle incoming gossipsub message"), } } - gossipsub::Event::Subscribed { peer_id, topic } => { - debug!( - peer_id = peer_id.to_string(), - topic = topic.to_string(), - "subscribed to topic over gossipsub" - ) - } + gossipsub::Event::Subscribed { peer_id, topic } => debug!( + subject = "libp2p.gossipsub.subscribed", + category = "handle_swarm_event", + peer_id = peer_id.to_string(), + topic = topic.to_string(), + "subscribed to topic over gossipsub" + ), _ => {} }, @@ -458,9 +521,11 @@ async fn handle_swarm_event( .. })) => { match result { - QueryResult::Bootstrap(Ok(BootstrapOk { peer, .. })) => { - debug!("successfully bootstrapped peer: {peer}") - } + QueryResult::Bootstrap(Ok(BootstrapOk { peer, .. })) => debug!( + subject = "libp2p.kad.bootstrap", + category = "handle_swarm_event", + "successfully bootstrapped peer: {peer}" + ), QueryResult::GetProviders(Ok(GetProvidersOk::FoundProviders { key: _, providers, @@ -488,7 +553,10 @@ async fn handle_swarm_event( } QueryResult::GetProviders(Err(err)) => { - warn!(err=?err, "error retrieving outbound query providers"); + warn!(subject = "libp2p.kad.get_providers.err", + category = "handle_swarm_event", + err=?err, + "error retrieving outbound query providers"); let Some((_, sender)) = event_handler.query_senders.remove(&id) else { return; @@ -502,8 +570,11 @@ async fn handle_swarm_event( } QueryResult::GetRecord(Ok(GetRecordOk::FoundRecord(peer_record))) => { debug!( + subject = "libp2p.kad.get_record.err", + category = "handle_swarm_event", "found record {:#?}, published by {:?}", - peer_record.record.key, peer_record.record.publisher + peer_record.record.key, + peer_record.record.publisher ); match peer_record.found_record() { Ok(event) => { @@ -516,8 +587,10 @@ async fn handle_swarm_event( } } Err(err) => { - warn!(err=?err, "error retrieving record"); - + warn!(subject = "libp2p.kad.get_record.err", + category = "handle_swarm_event", + err=?err, + "error retrieving record"); let Some((_, sender)) = event_handler.query_senders.remove(&id) else { return; }; @@ -530,7 +603,10 @@ async fn handle_swarm_event( } QueryResult::GetRecord(Ok(_)) => {} QueryResult::GetRecord(Err(err)) => { - warn!(err=?err, "error retrieving record"); + warn!(subject = "libp2p.kad.get_record.err", + category = "handle_swarm_event", + err=?err, + "error retrieving record"); // Upon an error, attempt to find the record on the DHT via // a provider if it's a Workflow/Info one. @@ -562,42 +638,67 @@ async fn handle_swarm_event( )))) .await; } else { - warn!("not a valid provider record tag: {capsule_tag}",) + warn!( + subject = "libp2p.kad.req_resp.err", + category = "handle_swarm_event", + "not a valid provider record tag: {capsule_tag}", + ) } } - None => { - info!("No provider found for outbound query {id:?}") - } + None => debug!( + subject = "libp2p.kad.req_resp.err", + category = "handle_swarm_event", + "No provider found for outbound query {id:?}" + ), } } - QueryResult::PutRecord(Ok(PutRecordOk { key })) => { - debug!("successfully put record {key:#?}"); - } - QueryResult::PutRecord(Err(err)) => { - warn!("error putting record: {err}") - } + QueryResult::PutRecord(Ok(PutRecordOk { key })) => debug!( + subject = "libp2p.kad.put_record", + category = "handle_swarm_event", + "successfully put record {key:#?}" + ), + QueryResult::PutRecord(Err(err)) => warn!( + subject = "libp2p.kad.put_record.err", + category = "handle_swarm_event", + err=?err, + "error putting record"), QueryResult::StartProviding(Ok(AddProviderOk { key })) => { // Currently, we don't send anything to the channel, // once they key is provided. let _ = event_handler.query_senders.remove(&id); - debug!("successfully providing {key:#?}"); + debug!( + subject = "libp2p.kad.provide_record", + category = "handle_swarm_event", + "successfully providing {key:#?}" + ); } QueryResult::StartProviding(Err(err)) => { // Currently, we don't send anything to the channel, // once they key is provided. let _ = event_handler.query_senders.remove(&id); - warn!("error providing key: {:#?}", err.key()); + warn!( + subject = "libp2p.kad.provide_record.err", + category = "handle_swarm_event", + "error providing key: {:#?}", + err.key() + ); } _ => {} } } SwarmEvent::Behaviour(ComposedEvent::Kademlia(kad::Event::InboundRequest { request })) => { - debug!("kademlia inbound request received {request:?}") + debug!( + subject = "libp2p.kad.inbound_request", + category = "handle_swarm_event", + "kademlia inbound request received {request:?}" + ) } SwarmEvent::Behaviour(ComposedEvent::Kademlia(kad::Event::RoutingUpdated { peer, .. })) => { debug!( + subject = "libp2p.kad.routing", + category = "handle_swarm_event", peer = peer.to_string(), "kademlia routing table updated with peer" ) @@ -646,7 +747,11 @@ async fn handle_swarm_event( } } Err(err) => { - warn!(err=?err, cid=?cid, "error retrieving workflow info"); + warn!(subject = "libp2p.req_resp.err", + category = "handle_swarm_event", + err=?err, + cid=?cid, + "error retrieving workflow info"); let _ = event_handler .swarm @@ -687,11 +792,11 @@ async fn handle_swarm_event( let _ = sender.send_async(ResponseEvent::Found(Ok(event))).await; } Err(err) => { - warn!( - err=?err, - cid = key_cid.as_str(), - "error returning capsule for request_id: {request_id}" - ); + warn!(subject = "libp2p.req_resp.resp.err", + category = "handle_swarm_event", + err=?err, + cid = key_cid.as_str(), + "error returning capsule for request_id: {request_id}"); let _ = sender.send_async(ResponseEvent::Found(Err(err))).await; } @@ -702,7 +807,9 @@ async fn handle_swarm_event( }, SwarmEvent::Behaviour(ComposedEvent::Mdns(mdns::Event::Discovered(list))) => { for (peer_id, multiaddr) in list { - info!( + debug!( + subject = "libp2p.mdns.discovered", + category = "handle_swarm_event", peer_id = peer_id.to_string(), addr = multiaddr.to_string(), "mDNS discovered a new peer" @@ -717,9 +824,10 @@ async fn handle_swarm_event( .build(), ); } else { - warn!( - peer_id = peer_id.to_string(), - "peer discovered by mDNS not dialed because max connected peers limit reached" + debug!(subject = "libp2p.mdns.discovered.err", + category = "handle_swarm_event", + peer_id = peer_id.to_string(), + "peer discovered by mDNS not dialed because max connected peers limit reached" ) } } @@ -729,13 +837,17 @@ async fn handle_swarm_event( if let Some(mdns) = behaviour.mdns.as_ref() { for (peer_id, multiaddr) in list { - info!( + debug!( + subject = "libp2p.mdns.expired", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "mDNS discover peer has expired" ); if mdns.has_node(&peer_id) { behaviour.kademlia.remove_address(&peer_id, &multiaddr); debug!( + subject = "libp2p.mdns.expired", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "removed peer address from kademlia table" ); @@ -747,8 +859,11 @@ async fn handle_swarm_event( let local_peer = *event_handler.swarm.local_peer_id(); info!( + subject = "libp2p.listen.addr", + category = "handle_swarm_event", peer_id = local_peer.to_string(), - "local node is listening on {}", address + "local node is listening on {}", + address ); #[cfg(feature = "websocket-notify")] @@ -765,7 +880,12 @@ async fn handle_swarm_event( SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => { - debug!(peer_id=peer_id.to_string(), endpoint=?endpoint, "peer connection established"); + debug!(subject = "libp2p.conn.established", + category = "handle_swarm_event", + peer_id=peer_id.to_string(), + endpoint=?endpoint, + "peer connection established"); + // add peer to connected peers list event_handler .connections @@ -789,6 +909,8 @@ async fn handle_swarm_event( .. } => { debug!( + subject = "libp2p.conn.closed", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "peer connection closed, cause: {cause:#?}, endpoint: {endpoint:#?}" ); @@ -801,7 +923,11 @@ async fn handle_swarm_event( } else { // TODO: We may want to check the multiadress without relying on // the peer ID. This would give more flexibility when configuring nodes. - warn!("Configured peer must include a peer ID: {multiaddr}"); + warn!( + subject = "libp2p.conn.closed", + category = "handle_swarm_event", + "Configured peer must include a peer ID: {multiaddr}" + ); true } }) { @@ -812,6 +938,8 @@ async fn handle_swarm_event( .remove_peer(&peer_id); debug!( + subject = "libp2p.kad.remove", + category = "handle_swarm_event", peer_id = peer_id.to_string(), "removed peer from kademlia table" ); @@ -832,11 +960,12 @@ async fn handle_swarm_event( peer_id, error, } => { - error!( - peer_id=peer_id.map(|p| p.to_string()).unwrap_or_default(), - err=?error, - connection_id=?connection_id, - "outgoing connection error" + warn!(subject = "libp2p.outgoing.err", + category = "handle_swarm_event", + peer_id=peer_id.map(|p| p.to_string()).unwrap_or_default(), + err=?error, + connection_id=?connection_id, + "outgoing connection error" ); #[cfg(feature = "websocket-notify")] @@ -855,13 +984,13 @@ async fn handle_swarm_event( send_back_addr, error, } => { - error!( - err=?error, - connection_id=?connection_id, - local_address=local_addr.to_string(), - remote_address=send_back_addr.to_string(), - "incoming connection error" - ); + warn!(subject = "libp2p.incoming.err", + category = "handle_swarm_event", + err=?error, + connection_id=?connection_id, + local_address=local_addr.to_string(), + remote_address=send_back_addr.to_string(), + "incoming connection error"); #[cfg(feature = "websocket-notify")] notification::emit_event( @@ -873,13 +1002,31 @@ async fn handle_swarm_event( ); } SwarmEvent::ListenerError { listener_id, error } => { - error!(err=?error, listener_id=?listener_id, "listener error") + error!(subject = "libp2p.listener.err", + category = "handle_swarm_event", + err=?error, + listener_id=?listener_id, + "listener error") } SwarmEvent::Dialing { peer_id, .. } => match peer_id { - Some(id) => debug!(peer_id = id.to_string(), "dialing peer"), - None => debug!("dialing an unknown peer"), + Some(id) => { + debug!( + subject = "libp2p.dialing", + category = "handle_swarm_event", + peer_id = id.to_string(), + "dialing peer" + ) + } + None => debug!( + subject = "libp2p.dialing", + category = "handle_swarm_event", + "dialing an unknown peer" + ), }, - e => debug!(e=?e, "uncaught event"), + e => debug!(subject = "libp2p.event", + category = "handle_swarm_event", + e=?e, + "uncaught event"), } } @@ -915,10 +1062,7 @@ fn decode_capsule(key_cid: Cid, value: &Vec) -> Result { Ok(ipld) => Err(anyhow!( "decode mismatch: expected an Ipld map, got {ipld:#?}", )), - Err(err) => { - warn!(error=?err, "error deserializing record value"); - Err(anyhow!("error deserializing record value")) - } + Err(err) => Err(anyhow!("error deserializing record value: {err}")), } } diff --git a/homestar-runtime/src/lib.rs b/homestar-runtime/src/lib.rs index e6f84323..b81b5364 100644 --- a/homestar-runtime/src/lib.rs +++ b/homestar-runtime/src/lib.rs @@ -17,6 +17,7 @@ //! [homestar-core]: homestar_core //! [homestar-wasm]: homestar_wasm +pub mod channel; pub mod cli; pub mod daemon; pub mod db; @@ -37,12 +38,14 @@ pub mod workflow; pub mod test_utils; pub use db::Db; -pub use event_handler::channel; pub(crate) mod libp2p; pub use logger::*; pub(crate) mod metrics; +#[allow(unused_imports)] +pub(crate) use event_handler::EventHandler; pub use receipt::{Receipt, RECEIPT_TAG, VERSION_KEY}; pub use runner::Runner; +pub(crate) use scheduler::TaskScheduler; pub use settings::Settings; pub(crate) use worker::Worker; pub use workflow::WORKFLOW_TAG; diff --git a/homestar-runtime/src/libp2p/mod.rs b/homestar-runtime/src/libp2p/mod.rs index 8254556f..c2ac9fe1 100644 --- a/homestar-runtime/src/libp2p/mod.rs +++ b/homestar-runtime/src/libp2p/mod.rs @@ -1 +1,3 @@ +//! [libp2p] utilities. + pub(crate) mod multiaddr; diff --git a/homestar-runtime/src/libp2p/multiaddr.rs b/homestar-runtime/src/libp2p/multiaddr.rs index 894d7008..12f43531 100644 --- a/homestar-runtime/src/libp2p/multiaddr.rs +++ b/homestar-runtime/src/libp2p/multiaddr.rs @@ -1,5 +1,7 @@ +/// Multiaddr extension methods. use libp2p::{multiaddr::Protocol, Multiaddr, PeerId}; +/// [Multiaddr] extension trait. pub(crate) trait MultiaddrExt { fn peer_id(&self) -> Option; } diff --git a/homestar-runtime/src/main.rs b/homestar-runtime/src/main.rs index f9fce8f7..9639b3aa 100644 --- a/homestar-runtime/src/main.rs +++ b/homestar-runtime/src/main.rs @@ -37,7 +37,7 @@ fn main() -> Result<()> { info!( subject = "settings", - category = "homestar_init", + category = "homestar.init", "starting with settings: {:?}", settings, ); @@ -47,7 +47,7 @@ fn main() -> Result<()> { info!( subject = "database", - category = "homestar_init", + category = "homestar.init", "starting with database: {}", Db::url().expect("database url to be provided"), ); diff --git a/homestar-runtime/src/metrics.rs b/homestar-runtime/src/metrics.rs index e0b308e9..d5a3623a 100644 --- a/homestar-runtime/src/metrics.rs +++ b/homestar-runtime/src/metrics.rs @@ -11,6 +11,7 @@ mod exporter; mod node; /// Start metrics collection and setup scrape endpoint. +/// Also, spawn a task to collect process metrics at a regular interval. #[cfg(feature = "monitoring")] pub(crate) async fn start( monitor_settings: &settings::Monitoring, @@ -27,6 +28,7 @@ pub(crate) async fn start( Ok(metrics_hdl) } +/// Start metrics collection and setup scrape endpoint. #[cfg(not(feature = "monitoring"))] pub(crate) async fn start(network_settings: &settings::Network) -> Result { let metrics_hdl = exporter::setup_metrics_recorder(network_settings)?; diff --git a/homestar-runtime/src/network/error.rs b/homestar-runtime/src/network/error.rs index 10f18416..1e0319f1 100644 --- a/homestar-runtime/src/network/error.rs +++ b/homestar-runtime/src/network/error.rs @@ -1,3 +1,5 @@ +//! # Error types centered around the networking. + #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("pubsub error: {0}")] diff --git a/homestar-runtime/src/network/mod.rs b/homestar-runtime/src/network/mod.rs index 01a55a31..e47cb79e 100644 --- a/homestar-runtime/src/network/mod.rs +++ b/homestar-runtime/src/network/mod.rs @@ -1,9 +1,8 @@ -//! [libp2p], multi-use [http] and [websocket] server, and [ipfs] networking +//! [libp2p], multi-use [HTTP] and [WebSocket] server, and [ipfs] networking //! interfaces. //! -//! [libp2p]: libp2p -//! [http]: jsonrpsee::server -//! [websocket]: jsonrpsee::server +//! [HTTP]: jsonrpsee::server +//! [WebSocket]: jsonrpsee::server //! [ipfs]: ipfs_api pub(crate) mod error; diff --git a/homestar-runtime/src/network/pubsub/message.rs b/homestar-runtime/src/network/pubsub/message.rs index 841212b8..8153af05 100644 --- a/homestar-runtime/src/network/pubsub/message.rs +++ b/homestar-runtime/src/network/pubsub/message.rs @@ -1,3 +1,7 @@ +//! [Message] type for messages transmitted over [gossipsub]. +//! +//! [gossipsub]: libp2p::gossipsub + use anyhow::{anyhow, Result}; use homestar_core::workflow::Nonce; use libipld::{self, cbor::DagCborCodec, prelude::Codec, serde::from_ipld, Ipld}; diff --git a/homestar-runtime/src/network/rpc.rs b/homestar-runtime/src/network/rpc.rs index 667f1d9c..f260634f 100644 --- a/homestar-runtime/src/network/rpc.rs +++ b/homestar-runtime/src/network/rpc.rs @@ -1,4 +1,4 @@ -//! RPC server implementation. +//! CLI-focused RPC server implementation. use crate::{ channel::{AsyncChannel, AsyncChannelReceiver, AsyncChannelSender}, @@ -138,7 +138,9 @@ impl Interface for ServerHandler { }, _ = time::sleep_until(now + self.timeout) => { let s = format!("server timeout of {} ms reached", self.timeout.as_millis()); - info!("{s}"); + info!(subject = "rpc.timeout", + category = "rpc", + "{s}"); Err(Error::FailureToReceiveOnChannel(s)) } @@ -180,7 +182,12 @@ impl Server { tarpc::serde_transport::tcp::listen(self.addr, MessagePack::default).await?; listener.config_mut().max_frame_length(usize::MAX); - info!("RPC server listening on {}", self.addr); + info!( + subject = "rpc.spawn", + category = "rpc", + "RPC server listening on {}", + self.addr + ); // setup valved listener for cancellation let (exit, incoming) = Valved::new(listener); @@ -203,11 +210,16 @@ impl Server { select! { Ok(ServerMessage::GracefulShutdown(tx)) = self.receiver.recv_async() => { - info!("RPC server shutting down"); + info!(subject = "shutdown", + category = "homestar.shutdown", + "RPC server shutting down"); drop(exit); let _ = tx.send_async(()).await; } - _ = fut => warn!("RPC server exited unexpectedly"), + _ = fut => + warn!(subject = "rpc.spawn.err", + category = "rpc", + "RPC server exited unexpectedly"), } }); diff --git a/homestar-runtime/src/network/swarm.rs b/homestar-runtime/src/network/swarm.rs index ce638854..22ab6b2a 100644 --- a/homestar-runtime/src/network/swarm.rs +++ b/homestar-runtime/src/network/swarm.rs @@ -1,9 +1,6 @@ -#![allow(missing_docs)] - //! Sets up a [libp2p] [Swarm], containing the state of the network and the way //! it should behave. //! -//! [libp2p]: libp2p //! [Swarm]: libp2p::Swarm use crate::{ @@ -11,6 +8,7 @@ use crate::{ settings, Receipt, RECEIPT_TAG, WORKFLOW_TAG, }; use anyhow::{Context, Result}; +use const_format::formatcp; use enum_assoc::Assoc; use faststr::FastStr; use libp2p::{ @@ -32,7 +30,10 @@ use serde::{Deserialize, Serialize}; use std::fmt; use tracing::{info, warn}; -pub(crate) const HOMESTAR_PROTOCOL_VER: &str = "homestar/0.0.1"; +/// Homestar protocol version, shared among peers, tied to the homestar version. +pub(crate) const HOMESTAR_PROTOCOL_VER: &str = formatcp!("homestar/{VERSION}"); + +const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Build a new [Swarm] with a given transport and a tokio executor. pub(crate) async fn new(settings: &settings::Network) -> Result> { @@ -42,7 +43,12 @@ pub(crate) async fn new(settings: &settings::Network) -> Result)), /// Acknowledgement of a [Workflow] run. AckWorkflow((Cid, FastStr)), - /// TODO + /// Message sent to the [Runner] to gather node information from the [EventHandler]. + /// + /// [Runner]: crate::Runner + /// [EventHandler]: crate::EventHandler GetNodeInfo, - /// TODO + /// Acknowledgement of a [Message::GetNodeInfo] request, receiving static and dynamic + /// node information. AckNodeInfo((StaticNodeInfo, DynamicNodeInfo)), } -/// WebSocket server fields. +/// Server fields. #[cfg(feature = "websocket-notify")] #[derive(Clone, Debug)] pub(crate) struct Server { - /// Address of the websocket server. + /// Address of the server. addr: SocketAddr, - /// TODO + /// Message buffer capacity for the server. capacity: usize, - /// TODO + /// Message sender for broadcasting internal events to clients connected to + /// to the server. evt_notifier: Notifier, - /// Message sender for broadcasting to clients connected to the - /// websocket server. + /// Message sender for broadcasting workflow-related events to clients + /// connected to to the server. workflow_msg_notifier: Notifier, - /// Receiver timeout for the websocket server. + /// Receiver timeout for the server when communicating with the [Runner]. + /// + /// [Runner]: crate::Runner receiver_timeout: Duration, - /// TODO + /// General timeout for the server. webserver_timeout: Duration, } +/// Server fields. #[cfg(not(feature = "websocket-notify"))] #[derive(Clone, Debug)] pub(crate) struct Server { - /// Address of the websocket server. + /// Address of the server. addr: SocketAddr, - /// TODO + /// Message buffer capacity for the server. capacity: usize, - /// Receiver timeout for the websocket server. + /// Receiver timeout for the server when communicating with the [Runner]. + /// + /// [Runner]: crate::Runner receiver_timeout: Duration, - /// TODO + /// General timeout for the server. webserver_timeout: Duration, } impl Server { /// Setup bounded, MPMC channel for runtime to send and received messages - /// through the websocket connection(s). + /// through the WebSocket connection(s). #[cfg(feature = "websocket-notify")] fn setup_channel( capacity: usize, @@ -111,6 +120,8 @@ impl Server { broadcast::channel(capacity) } + /// Set up a new [Server] instance, which acts as both a + /// WebSocket and HTTP server. #[cfg(feature = "websocket-notify")] pub(crate) fn new(settings: &settings::Webserver) -> Result { let (evt_sender, _receiver) = Self::setup_channel(settings.websocket_capacity); @@ -136,6 +147,7 @@ impl Server { }) } + /// Set up a new [Server] instance, which only acts as an HTTP server. #[cfg(not(feature = "websocket-notify"))] pub(crate) fn new(settings: &settings::Webserver) -> Result { let host = IpAddr::from_str(&settings.host.to_string())?; @@ -157,7 +169,7 @@ impl Server { }) } - /// Start the websocket server. + /// Instantiates the [JsonRpc] module, and starts the server. #[cfg(feature = "websocket-notify")] pub(crate) async fn start( &self, @@ -176,7 +188,7 @@ impl Server { self.start_inner(module).await } - /// Start the websocket server. + /// Instantiates the [JsonRpc] module, and starts the server. #[cfg(not(feature = "websocket-notify"))] pub(crate) async fn start( &self, @@ -192,23 +204,29 @@ impl Server { self.start_inner(module).await } - /// Get websocket message sender for broadcasting messages to websocket + /// Return the WebSocket event sender for broadcasting messages to connected /// clients. #[cfg(feature = "websocket-notify")] pub(crate) fn evt_notifier(&self) -> Notifier { self.evt_notifier.clone() } - /// Get websocket message sender for broadcasting messages to websocket - /// clients. + /// Get WebSocket message sender for broadcasting workflow-related messages + /// to connected clients. #[cfg(feature = "websocket-notify")] pub(crate) fn workflow_msg_notifier(&self) -> Notifier { self.workflow_msg_notifier.clone() } + /// Shared start logic for both WebSocket and HTTP servers. async fn start_inner(&self, module: JsonRpc) -> Result { let addr = self.addr; - info!("webserver listening on {}", addr); + info!( + subject = "webserver.start", + category = "webserver", + "webserver listening on {}", + addr + ); let cors = CorsLayer::new() // Allow `POST` when accessing the resource diff --git a/homestar-runtime/src/network/webserver/listener.rs b/homestar-runtime/src/network/webserver/listener.rs index 47a37c7a..9423ce20 100644 --- a/homestar-runtime/src/network/webserver/listener.rs +++ b/homestar-runtime/src/network/webserver/listener.rs @@ -1,3 +1,5 @@ +//! Listener for incoming requests types. + use faststr::FastStr; use homestar_core::{ipld::DagJson, Workflow}; use homestar_wasm::io::Arg; @@ -5,7 +7,7 @@ use names::{Generator, Name}; use serde::{de, Deserialize, Deserializer, Serialize}; use serde_json::value::RawValue; -/// A [Workflow] run command via a websocket channel. +/// A [Workflow] run command via a WebSocket channel. /// /// Note: We leverage the [RawValue] type in order to use our [DagJson] /// implementation, which is not a direct [Deserialize] implementation. diff --git a/homestar-runtime/src/network/webserver/notifier.rs b/homestar-runtime/src/network/webserver/notifier.rs index dc7abd6a..b1ad4f86 100644 --- a/homestar-runtime/src/network/webserver/notifier.rs +++ b/homestar-runtime/src/network/webserver/notifier.rs @@ -6,7 +6,7 @@ use libipld::Cid; use std::{fmt, sync::Arc}; use tokio::sync::broadcast; -/// Type-wrapper for websocket sender. +/// Type-wrapper for WebSocket sender. #[derive(Debug)] pub(crate) struct Notifier(Arc>); @@ -37,7 +37,7 @@ where self.0 } - /// Send a message to all connected websocket clients. + /// Send a message to all connected WebSocket clients. pub(crate) fn notify(&self, msg: T) -> Result<()> { let _ = self.0.send(msg)?; Ok(()) @@ -52,7 +52,7 @@ pub(crate) enum SubscriptionTyp { Cid(Cid), } -/// A header for a message to be sent to a websocket client. +/// A header for a message to be sent to a WebSocket client. #[derive(Debug, Clone)] pub(crate) struct Header { pub(crate) subscription: SubscriptionTyp, @@ -69,7 +69,7 @@ impl Header { } } -/// A message to be sent to a websocket client, with a header and payload. +/// A message to be sent to a WebSocket client, with a header and payload. #[derive(Debug, Clone)] pub(crate) struct Message { pub(crate) header: Header, @@ -82,13 +82,13 @@ impl Message { Self { header, payload } } - /// TODO + /// Get a reference to the [Header] of a [Message]. #[allow(dead_code)] pub(crate) fn header(&self) -> &Header { &self.header } - /// TODO + /// Get a reference to the payload of a [Message]. #[allow(dead_code)] pub(crate) fn payload(&self) -> &[u8] { &self.payload diff --git a/homestar-runtime/src/network/webserver/rpc.rs b/homestar-runtime/src/network/webserver/rpc.rs index c56b3c65..51b72eb8 100644 --- a/homestar-runtime/src/network/webserver/rpc.rs +++ b/homestar-runtime/src/network/webserver/rpc.rs @@ -1,3 +1,5 @@ +//! JSON-RPC module for registering methods and subscriptions. + #[cfg(feature = "websocket-notify")] use super::notifier::{self, Header, Notifier, SubscriptionTyp}; #[allow(unused_imports)] @@ -34,10 +36,10 @@ use tokio::sync::oneshot; use tokio::{runtime::Handle, select}; #[cfg(feature = "websocket-notify")] use tokio_stream::wrappers::BroadcastStream; -#[allow(unused_imports)] -use tracing::warn; #[cfg(feature = "websocket-notify")] -use tracing::{debug, error, info}; +use tracing::debug; +#[allow(unused_imports)] +use tracing::{error, warn}; /// Health endpoint. pub(crate) const HEALTH_ENDPOINT: &str = "health"; @@ -56,7 +58,7 @@ pub(crate) const SUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "subscribe_network_ev #[cfg(feature = "websocket-notify")] pub(crate) const UNSUBSCRIBE_NETWORK_EVENTS_ENDPOINT: &str = "unsubscribe_network_events"; -/// TODO +/// Context for RPC methods. #[cfg(feature = "websocket-notify")] pub(crate) struct Context { metrics_hdl: PrometheusHandle, @@ -67,7 +69,7 @@ pub(crate) struct Context { workflow_listeners: Arc, (Cid, FastStr)>>, } -/// TODO +/// Context for RPC methods. #[allow(dead_code)] #[cfg(not(feature = "websocket-notify"))] pub(crate) struct Context { @@ -77,7 +79,7 @@ pub(crate) struct Context { } impl Context { - /// TODO + /// Create a new [Context] instance. #[cfg(feature = "websocket-notify")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket-notify")))] pub(crate) fn new( @@ -97,7 +99,7 @@ impl Context { } } - /// TODO + /// Create a new [Context] instance. #[cfg(not(feature = "websocket-notify"))] pub(crate) fn new( metrics_hdl: PrometheusHandle, @@ -150,7 +152,12 @@ impl JsonRpc { Ok(serde_json::json!({ "healthy": true, "nodeInfo": { "static": static_info, "dynamic": dyn_info}})) } else { - warn!(sub = HEALTH_ENDPOINT, "did not acknowledge message in time"); + error!( + subject = "call.health", + category = "jsonrpc.call", + sub = HEALTH_ENDPOINT, + "did not acknowledge message in time" + ); Err(internal_err("failed to get node information".to_string())) } })?; @@ -229,7 +236,9 @@ impl JsonRpc { let stream = BroadcastStream::new(rx); Self::handle_workflow_subscription(sink, stream, ctx).await?; } else { - warn!( + error!( + subject = "subscription.workflow.err", + category = "jsonrpc.subscription", sub = SUBSCRIBE_RUN_WORKFLOW_ENDPOINT, workflow_name = name.to_string(), "did not acknowledge message in time" @@ -243,7 +252,10 @@ impl JsonRpc { } } Err(err) => { - warn!("failed to parse run workflow params: {}", err); + warn!(subject = "subscription.workflow.err", + category = "jsonrpc.subscription", + err=?err, + "failed to parse run workflow params"); let _ = pending.reject(err).await; } } @@ -278,7 +290,10 @@ impl JsonRpc { })) if evt == subscription_type => payload, Some(Ok(_)) => continue, Some(Err(err)) => { - error!("subscription stream error: {}", err); + error!(subject = "subscription.event.err", + category = "jsonrpc.subscription", + err=?err, + "subscription stream error"); break Err(err.into()); } None => break Ok(()), @@ -290,7 +305,9 @@ impl JsonRpc { break Err(anyhow!("subscription sink closed")); } Err(TrySendError::Full(_)) => { - info!("subscription sink full"); + error!(subject = "subscription.event.err", + category = "jsonrpc.subscription", + "subscription sink full"); } } } @@ -326,9 +343,11 @@ impl JsonRpc { .and_then(|v| { let (v_cid, v_name) = v.value(); if v_cid == &cid && (Some(v_name) == ident.as_ref() || ident.is_none()) { - debug!(cid = cid.to_string(), - ident = ident.clone().unwrap_or( - "undefined".into()).to_string(), "received message"); + debug!( + subject = "subscription.workflow", + category = "jsonrpc.subscription", + cid = cid.to_string(), + ident = ident.clone().unwrap_or("undefined".into()).to_string(), "received message"); Some(payload) } else { None @@ -359,7 +378,9 @@ impl JsonRpc { break Err(anyhow!("subscription sink closed")); } Err(TrySendError::Full(_)) => { - info!("subscription sink full"); + error!(subject = "subscription.workflow.err", + category = "jsonrpc.subscription", + "subscription sink full"); } } } diff --git a/homestar-runtime/src/receipt.rs b/homestar-runtime/src/receipt.rs index bf65fa35..c8dcd1f1 100644 --- a/homestar-runtime/src/receipt.rs +++ b/homestar-runtime/src/receipt.rs @@ -1,4 +1,7 @@ -//! Output of an invocation, referenced by its invocation pointer. +//! Runtime, extended representation of a [Receipt] for [Invocation]s and storage. +//! +//! [Receipt]: homestar_core::workflow::Receipt +//! [Invocation]: homestar_core::workflow::Invocation use anyhow::anyhow; use diesel::{ diff --git a/homestar-runtime/src/runner.rs b/homestar-runtime/src/runner.rs index ed6217a6..7c5470ab 100644 --- a/homestar-runtime/src/runner.rs +++ b/homestar-runtime/src/runner.rs @@ -38,7 +38,7 @@ use tokio::{ time, }; use tokio_util::time::{delay_queue, DelayQueue}; -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; mod error; pub(crate) mod file; @@ -74,13 +74,13 @@ pub(crate) type RpcReceiver = AsyncChannelReceiver<( Option>, )>; -/// [AsyncChannelSender] for sending messages websocket server clients. +/// [AsyncChannelSender] for sending messages WebSocket server clients. pub(crate) type WsSender = AsyncChannelSender<( webserver::Message, Option>, )>; -/// [AsyncChannelReceiver] for receiving messages from websocket server clients. +/// [AsyncChannelReceiver] for receiving messages from WebSocket server clients. pub(crate) type WsReceiver = AsyncChannelReceiver<( webserver::Message, Option>, @@ -129,7 +129,7 @@ impl Runner { } /// MPSC channel for sending and receiving messages through to/from - /// websocket server clients. + /// WebSocket server clients. pub(crate) fn setup_ws_mpsc_channel(capacity: usize) -> (WsSender, WsReceiver) { AsyncChannel::with(capacity) } @@ -262,21 +262,27 @@ impl Runner { Ok(ControlFlow::Break(())) => break now.elapsed(), Ok(ControlFlow::Continue(rpc::ServerMessage::Skip)) => {}, Ok(ControlFlow::Continue(msg @ rpc::ServerMessage::RunAck(_))) => { - info!("sending message to rpc server"); + debug!(subject = "rpc.ack", + category = "rpc", + "sending message to rpc server"); let _ = oneshot_tx.send_async(msg).await; }, Err(err) => { - error!(err=?err, "error handling rpc message"); + error!(subject = "rpc.err", + category = "rpc", + err=?err, + "error handling rpc message"); let _ = oneshot_tx.send_async(rpc::ServerMessage::RunErr(err.into())).await; }, _ => {} } } Ok(msg) = ws_receiver.recv_async() => { - println!("ws message: {:?}", msg); match msg { (webserver::Message::RunWorkflow((name, workflow)), Some(oneshot_tx)) => { - info!("running workflow: {}", name); + info!(subject = "workflow", + category = "workflow.run", + "running workflow: {}", name); // TODO: Parse this from the workflow data itself. let workflow_settings = workflow::Settings::default(); match self.run_worker( @@ -287,18 +293,25 @@ impl Runner { db.clone(), ).await { Ok(data) => { - info!("sending message to rpc server"); + debug!(subject = "jsonrpc.ack", + category = "jsonrpc", + "sending message to jsonrpc server"); let _ = oneshot_tx.send_async(webserver::Message::AckWorkflow((data.info.cid, data.name))).await; } Err(err) => { - error!(err=?err, "error handling ws message"); + error!(subject = "jsonrpc.err", + category = "jsonrpc", + err=?err, + "error handling ws message"); let _ = oneshot_tx.send_async(webserver::Message::RunErr(err.into())).await; } } } (webserver::Message::GetNodeInfo, Some(oneshot_tx)) => { - info!("getting node info"); + debug!(subject = "jsonrpc.nodeinfo", + category = "jsonrpc", + "getting node info"); let (tx, rx) = AsyncChannel::oneshot(); let _ = self.event_sender.send_async(Event::GetListeners(tx)).await; let dyn_node_info = if let Ok(listeners) = rx.recv_deadline(Instant::now() + self.settings.node.network.webserver.timeout) { @@ -331,12 +344,16 @@ impl Runner { Err(_) => Poll::Pending, } ) => { - info!("worker expired, aborting"); + info!(subject = "worker.expired", + category = "worker", + "worker expired, aborting"); let _ = self.abort_worker(*expired.get_ref()); }, // Handle shutdown signal. _ = Self::shutdown_signal() => { - info!("gracefully shutting down runner"); + info!(subject = "shutdown", + category = "homestar.shutdown", + "gracefully shutting down runner"); let now = time::Instant::now(); let drain_timeout = now + shutdown_timeout; @@ -347,7 +364,9 @@ impl Runner { }, // Force shutdown upon drain timeout. _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); + info!(subject = "shutdown", + category = "homestar.shutdown", + "shutdown timeout reached, shutting down runner anyway"); break now.elapsed(); } } @@ -360,7 +379,11 @@ impl Runner { if shutdown_time_left < shutdown_timeout { self.runtime .shutdown_timeout(shutdown_timeout - shutdown_time_left); - info!("runner shutdown complete"); + info!( + subject = "shutdown", + category = "homestar.shutdown", + "runner shutdown complete" + ); } Ok(()) @@ -488,9 +511,18 @@ impl Runner { let mut sigterm = signal(SignalKind::terminate())?; select! { - _ = tokio::signal::ctrl_c() => info!("CTRL-C received, shutting down"), - _ = sigint.recv() => info!("SIGINT received, shutting down"), - _ = sigterm.recv() => info!("SIGTERM received, shutting down"), + _ = tokio::signal::ctrl_c() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "CTRL-C received, shutting down"), + _ = sigint.recv() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "SIGINT received, shutting down"), + _ = sigterm.recv() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "SIGTERM received, shutting down"), } Ok(()) } @@ -503,10 +535,22 @@ impl Runner { let mut sighup = windows::ctrl_break()?; select! { - _ = tokio::signal::ctrl_c() => info!("CTRL-C received, shutting down"), - _ = sigint.recv() => info!("SIGINT received, shutting down"), - _ = sigterm.recv() => info!("SIGTERM received, shutting down"), - _ = sighup.recv() => info!("SIGHUP received, shutting down") + _ = tokio::signal::ctrl_c() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "CTRL-C received, shutting down"), + _ = sigint.recv() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "SIGINT received, shutting down"), + _ = sigterm.recv() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "SIGTERM received, shutting down"), + _ = sighup.recv() => + info!(subject = "shutdown", + category = "homestar.shutdown", + "SIGHUP received, shutting down") } Ok(()) } @@ -526,7 +570,11 @@ impl Runner { .await; let _ = shutdown_receiver; - info!("shutting down webserver"); + info!( + subject = "shutdown", + category = "homestar.shutdown", + "shutting down webserver" + ); let _ = ws_hdl.stop(); ws_hdl.clone().stopped().await; @@ -553,7 +601,11 @@ impl Runner { ) -> Result> { match msg { rpc::ServerMessage::ShutdownCmd => { - info!("RPC shutdown signal received, shutting down runner"); + info!( + subject = "rpc.command", + category = "rpc", + "RPC shutdown signal received, shutting down runner" + ); let drain_timeout = now + self.settings.node.shutdown_timeout; select! { // we can unwrap here b/c we know we have a sender based @@ -562,7 +614,9 @@ impl Runner { Ok(ControlFlow::Break(())) }, _ = time::sleep_until(drain_timeout) => { - info!("shutdown timeout reached, shutting down runner anyway"); + info!(subject = "shutdown", + category = "homestar.shutdown", + "shutdown timeout reached, shutting down runner anyway"); Ok(ControlFlow::Break(())) } } @@ -582,7 +636,12 @@ impl Runner { )))) } msg => { - warn!("received unexpected message: {:?}", msg); + warn!( + subject = "rpc.command", + category = "rpc", + "received unexpected message: {:?}", + msg + ); Ok(ControlFlow::Continue(rpc::ServerMessage::Skip)) } } @@ -619,8 +678,11 @@ impl Runner { // Spawn worker, which initializees the scheduler and runs // the workflow. info!( + subject = "workflow.run", + category = "workflow", cid = worker.workflow_info.cid.to_string(), - "running workflow with settings: {:#?}", worker.workflow_settings + "running workflow with settings: {:#?}", + worker.workflow_settings ); // Provide workflow to network. diff --git a/homestar-runtime/src/runner/nodeinfo.rs b/homestar-runtime/src/runner/nodeinfo.rs index 58e9484a..aa14be84 100644 --- a/homestar-runtime/src/runner/nodeinfo.rs +++ b/homestar-runtime/src/runner/nodeinfo.rs @@ -1,33 +1,38 @@ +//! Node information. + use libp2p::{Multiaddr, PeerId}; use serde::{Deserialize, Serialize}; -/// TODO +/// Static node information available at startup. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct StaticNodeInfo { + /// The [PeerId] of a node. pub(crate) peer_id: PeerId, } impl StaticNodeInfo { - /// TODO + /// Create an instance of [StaticNodeInfo]. pub(crate) fn new(peer_id: PeerId) -> Self { Self { peer_id } } - /// TODO + /// Get a reference to the [PeerId] of a node. #[allow(dead_code)] pub(crate) fn peer_id(&self) -> &PeerId { &self.peer_id } } -/// TODO +/// Dynamic node information available through events +/// at runtime. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DynamicNodeInfo { + /// Listeners for the node. pub(crate) listeners: Vec, } impl DynamicNodeInfo { - /// TODO + /// Create an instance of [DynamicNodeInfo]. pub(crate) fn new(listeners: Vec) -> Self { Self { listeners } } diff --git a/homestar-runtime/src/runner/response.rs b/homestar-runtime/src/runner/response.rs index c6b8b781..8a5928c4 100644 --- a/homestar-runtime/src/runner/response.rs +++ b/homestar-runtime/src/runner/response.rs @@ -1,5 +1,5 @@ //! Responses for display/return to the user for -//! Client requests. +//! client requests. use crate::{ cli::show::{self, ApplyStyle}, diff --git a/homestar-runtime/src/scheduler.rs b/homestar-runtime/src/scheduler.rs index 1970bc0a..de364ee1 100644 --- a/homestar-runtime/src/scheduler.rs +++ b/homestar-runtime/src/scheduler.rs @@ -19,7 +19,7 @@ use indexmap::IndexMap; use libipld::Cid; use std::{ops::ControlFlow, str::FromStr, sync::Arc}; use tokio::sync::RwLock; -use tracing::info; +use tracing::debug; /// Type alias for a [Dag] set of batched nodes. /// @@ -150,7 +150,11 @@ impl<'a> TaskScheduler<'a> { } } Err(_) => { - info!("receipt not available in the database"); + debug!( + subject = "receipt.db.check", + category = "scheduler.run", + "receipt not available in the database" + ); continue; } } @@ -199,7 +203,9 @@ impl<'a> TaskScheduler<'a> { } } - /// TODO + /// Get the number of tasks that have already ran in the [Workflow]. + /// + /// [Workflow]: homestar_core::Workflow #[allow(dead_code)] pub(crate) fn ran_length(&self) -> usize { self.ran @@ -208,7 +214,9 @@ impl<'a> TaskScheduler<'a> { .unwrap_or_default() } - /// TODO + /// Get the number of tasks left to run in the [Workflow]. + /// + /// [Workflow]: homestar_core::Workflow #[allow(dead_code)] pub(crate) fn run_length(&self) -> usize { self.run.iter().flatten().collect::>().len() diff --git a/homestar-runtime/src/settings.rs b/homestar-runtime/src/settings.rs index f82c97ea..fb06d9a7 100644 --- a/homestar-runtime/src/settings.rs +++ b/homestar-runtime/src/settings.rs @@ -1,4 +1,4 @@ -//! Settings / Configuration. +//! General runtime settings / configuration. use config::{Config, ConfigError, Environment, File}; use http::Uri; @@ -162,7 +162,7 @@ pub(crate) struct Webserver { pub(crate) host: Uri, /// Webserver-server port. pub(crate) port: u16, - /// TODO + /// Webserver timeout. #[serde_as(as = "DurationSeconds")] pub(crate) timeout: Duration, /// Number of *bounded* clients to send messages to, used for a diff --git a/homestar-runtime/src/settings/libp2p_config.rs b/homestar-runtime/src/settings/libp2p_config.rs index b561cb4b..3df3aba9 100644 --- a/homestar-runtime/src/settings/libp2p_config.rs +++ b/homestar-runtime/src/settings/libp2p_config.rs @@ -1,4 +1,4 @@ -//! Libp2p setttings +//! [libp2p] configuration. use http::Uri; use serde::{Deserialize, Serialize}; diff --git a/homestar-runtime/src/settings/pubkey_config.rs b/homestar-runtime/src/settings/pubkey_config.rs index 171dcb87..a8c87ec6 100644 --- a/homestar-runtime/src/settings/pubkey_config.rs +++ b/homestar-runtime/src/settings/pubkey_config.rs @@ -1,3 +1,5 @@ +//! Pubkey configuration. + use anyhow::{anyhow, Context}; use libp2p::{identity, identity::secp256k1}; use rand::{Rng, SeedableRng}; @@ -54,7 +56,11 @@ impl PubkeyConfig { pub(crate) fn keypair(&self) -> anyhow::Result { match self { PubkeyConfig::Random => { - info!("generating random ed25519 key"); + info!( + subject = "pubkey_config.random", + category = "pubkey_config", + "generating random ed25519 key" + ); Ok(identity::Keypair::generate_ed25519()) } PubkeyConfig::GenerateFromSeed(RNGSeed { key_type, seed }) => { @@ -64,14 +70,22 @@ impl PubkeyConfig { match key_type { KeyType::Ed25519 => { - info!("generating random ed25519 key from seed"); + info!( + subject = "pubkey_config.random_seed.ed25519", + category = "pubkey_config", + "generating random ed25519 key from seed" + ); identity::Keypair::ed25519_from_bytes(new_key).map_err(|e| { anyhow!("failed to generate ed25519 key from random: {:?}", e) }) } KeyType::Secp256k1 => { - info!("generating random secp256k1 key from seed"); + info!( + subject = "pubkey_config.random_seed.secp256k1", + category = "pubkey_config", + "generating random secp256k1 key from seed" + ); let sk = secp256k1::SecretKey::try_from_bytes(&mut new_key).map_err(|e| { @@ -94,7 +108,12 @@ impl PubkeyConfig { KeyType::Ed25519 => { const PEM_HEADER: &str = "PRIVATE KEY"; - info!("importing ed25519 key from: {}", path.display()); + info!( + subject = "pubkey_config.path.ed25519", + category = "pubkey_config", + "importing ed25519 key from: {}", + path.display() + ); let (tag, mut key) = sec1::der::pem::decode_vec(&buf) .map_err(|e| anyhow!("key file must be PEM formatted: {:#?}", e))?; @@ -107,7 +126,12 @@ impl PubkeyConfig { .with_context(|| "imported key material was invalid for ed25519") } KeyType::Secp256k1 => { - info!("importing secp256k1 key from: {}", path.display()); + info!( + subject = "pubkey_config.path.secp256k1", + category = "pubkey_config", + "importing secp256k1 key from: {}", + path.display() + ); let sk = match path.extension().and_then(|ext| ext.to_str()) { Some("der") => sec1::EcPrivateKey::from_der(buf.as_slice()).map_err(|e| anyhow!("failed to parse DER encoded secp256k1 key: {e:#?}")), diff --git a/homestar-runtime/src/tasks.rs b/homestar-runtime/src/tasks.rs index caf8ca77..c054c877 100644 --- a/homestar-runtime/src/tasks.rs +++ b/homestar-runtime/src/tasks.rs @@ -1,5 +1,3 @@ -#![allow(missing_docs)] - //! Module for working with task-types and task-specific functionality. use anyhow::{anyhow, Result}; diff --git a/homestar-runtime/src/tasks/fetch.rs b/homestar-runtime/src/tasks/fetch.rs index 87922afd..d56df0f5 100644 --- a/homestar-runtime/src/tasks/fetch.rs +++ b/homestar-runtime/src/tasks/fetch.rs @@ -14,7 +14,7 @@ use std::sync::Arc; pub(crate) struct Fetch; #[cfg(any(test, feature = "test-utils"))] -const WASM_CID: &str = "bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia"; +const WASM_CID: &str = "bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a"; #[cfg(any(test, feature = "test-utils"))] const CAT_CID: &str = "bafybeiejevluvtoevgk66plh5t6xiy3ikyuuxg3vgofuvpeckb6eadresm"; @@ -34,6 +34,8 @@ impl Fetch { for rsc in resources.iter() { let task = tryhard::retry_fn(|| async { tracing::info!( + subject = "fetch_rsc", + category = "fetch", rsc = rsc.to_string(), "attempting to fetch resource from IPFS" ); @@ -47,20 +49,32 @@ impl Fetch { async move { if attempts < retries { tracing::warn!( + subject = "fetch_rsc.err", + category = "fetch", err = err, attempts = attempts, "retrying fetch after error @ {}ms", next_delay.map(|d| d.as_millis()).unwrap_or(0) ); } else { - tracing::warn!(err = err, attempts = attempts, "maxed out # of retries"); + tracing::warn!( + subject = "fetch_rsc.err", + category = "fetch", + err = err, + attempts = attempts, + "maxed out # of retries" + ); } } }); tasks.push(task); } - tracing::info!("fetching necessary resources from IPFS"); + tracing::info!( + subject = "fetch_rscs", + category = "fetch", + "fetching necessary resources from IPFS" + ); if let Ok(vec) = tasks.try_collect::>().await { vec.into_iter() .try_fold(IndexMap::default(), |mut acc, res| { diff --git a/homestar-runtime/src/tasks/wasm.rs b/homestar-runtime/src/tasks/wasm.rs index b55f3a37..7ba5fd70 100644 --- a/homestar-runtime/src/tasks/wasm.rs +++ b/homestar-runtime/src/tasks/wasm.rs @@ -1,3 +1,7 @@ +//! Functionality around Wasm-based [tasks]. +//! +//! [tasks]: homestar_core::workflow::Task + use super::FileLoad; use async_trait::async_trait; use homestar_core::workflow::input::Args; diff --git a/homestar-runtime/src/test_utils/worker_builder.rs b/homestar-runtime/src/test_utils/worker_builder.rs index f9c8e24c..14e0d255 100644 --- a/homestar-runtime/src/test_utils/worker_builder.rs +++ b/homestar-runtime/src/test_utils/worker_builder.rs @@ -25,26 +25,45 @@ use homestar_wasm::io::Arg; use indexmap::IndexMap; use libipld::Cid; -/// TODO +/// Utility structure for building out [Worker]s for testing purposes. +/// +/// [Worker]: crate::Worker #[cfg(feature = "ipfs")] pub(crate) struct WorkerBuilder<'a> { + /// In-memory database for testing. db: MemoryDb, + /// Event channel sender. event_sender: AsyncChannelSender, + /// [IPFS client]. + /// + /// [IPFS client]: crate::network::IpfsCli ipfs: IpfsCli, + /// Runner channel sender. runner_sender: AsyncChannelSender, + /// Name of the workflow. name: Option, + /// [Workflow] to run. workflow: Workflow<'a, Arg>, + /// [Workflow] settings. workflow_settings: workflow::Settings, } -/// TODO +/// Utility structure for building out [Worker]s for testing purposes. +/// +/// [Worker]: crate::Worker #[cfg(not(feature = "ipfs"))] pub(crate) struct WorkerBuilder<'a> { + /// In-memory database for testing. db: MemoryDb, + /// Event channel sender. event_sender: AsyncChannelSender, + /// Runner channel sender. runner_sender: AsyncChannelSender, + /// Name of the workflow. name: Option, + /// [Workflow] to run. workflow: Workflow<'a, Arg>, + /// [Workflow] settings. workflow_settings: workflow::Settings, } @@ -113,7 +132,10 @@ impl<'a> WorkerBuilder<'a> { .unwrap() } - /// TODO + /// Fetch-function closure for the [Worker]/[Scheduler] to use. + /// + /// [Worker]: crate::Worker + /// [Scheduler]: crate::TaskScheduler #[cfg(feature = "ipfs")] #[allow(dead_code)] pub(crate) fn fetch_fn( @@ -129,7 +151,10 @@ impl<'a> WorkerBuilder<'a> { fetch_fn } - /// TODO + /// Fetch-function closure for the [Worker]/[Scheduler] to use. + /// + /// [Worker]: crate::Worker + /// [Scheduler]: crate::TaskScheduler #[cfg(not(feature = "ipfs"))] #[allow(dead_code)] pub(crate) fn fetch_fn( diff --git a/homestar-runtime/src/worker.rs b/homestar-runtime/src/worker.rs index 05f55af3..50bbbe71 100644 --- a/homestar-runtime/src/worker.rs +++ b/homestar-runtime/src/worker.rs @@ -2,7 +2,7 @@ //! sends [Event]'s to the [EventHandler]. //! //! [Workflow]: homestar_core::Workflow -//! [EventHandler]: crate::event_handler::EventHandler +//! [EventHandler]: crate::EventHandler #[cfg(feature = "websocket-notify")] use crate::event_handler::event::Replay; @@ -16,10 +16,10 @@ use crate::{ }, network::swarm::CapsuleTag, runner::{ModifiedSet, RunningTaskSet}, - scheduler::{ExecutionGraph, TaskScheduler}, + scheduler::ExecutionGraph, tasks::{RegisteredTasks, WasmContext}, workflow::{self, Resource}, - Db, Receipt, + Db, Receipt, TaskScheduler, }; use anyhow::{anyhow, Context, Result}; use chrono::NaiveDateTime; @@ -51,8 +51,12 @@ use tracing::{debug, error, info}; #[allow(dead_code)] pub(crate) type TaskSet = JoinSet>; +/// Messages sent to [Worker] from [Runner]. +/// +/// [Runner]: crate::Runner #[derive(Debug, Clone, PartialEq)] pub(crate) enum WorkerMessage { + /// Signal that the [Worker] has been dropped for a workflow run. Dropped(Cid), } @@ -60,13 +64,25 @@ pub(crate) enum WorkerMessage { #[allow(dead_code)] #[allow(missing_debug_implementations)] pub(crate) struct Worker<'a, DB: Database> { + /// [ExecutionGraph] of the [Workflow] to run. pub(crate) graph: Arc>, + /// [EventHandler] channel to send [Event]s to. + /// + /// [EventHandler]: crate::EventHandler pub(crate) event_sender: Arc>, + /// [Runner] channel to send [WorkerMessage]s to. + /// + /// [Runner]: crate::Runner pub(crate) runner_sender: AsyncChannelSender, + /// [Database] pool to pull connections from for the [Worker] run. pub(crate) db: DB, + /// Local name of the [Workflow] being run. pub(crate) workflow_name: FastStr, + /// [Workflow] information. pub(crate) workflow_info: Arc, + /// [Workflow] settings. pub(crate) workflow_settings: Arc, + /// [NaiveDateTime] of when the [Workflow] was started. pub(crate) workflow_started: NaiveDateTime, } @@ -152,7 +168,10 @@ where { Ok(ctx) => self.run_queue(ctx.scheduler, running_tasks).await, Err(err) => { - error!(err=?err, "error initializing scheduler"); + error!(subject = "worker.init.err", + category = "worker.run", + err=?err, + "error initializing scheduler"); Err(anyhow!("error initializing scheduler")) } } @@ -184,16 +203,28 @@ where event_sender: Arc>, ) -> Result, ResolveError> { info!( + subject = "worker.resolve_cid", + category = "worker.run", workflow_cid = workflow_cid.to_string(), cid = cid.to_string(), "attempting to resolve cid in workflow" ); if let Some(result) = linkmap.read().await.get(&cid) { - debug!(cid = cid.to_string(), "found in in-memory linkmap"); + debug!( + subject = "worker.resolve_cid", + category = "worker.run", + cid = cid.to_string(), + "found CID in in-memory linkmap" + ); Ok(result.to_owned()) } else if let Some(bytes) = resources.read().await.get(&Resource::Cid(cid)) { - debug!(cid = cid.to_string(), "found in resources"); + debug!( + subject = "worker.resolve_cid", + category = "worker.run", + cid = cid.to_string(), + "found CID in map of resources" + ); Ok(InstructionResult::Ok(Arg::Ipld(Ipld::Bytes( bytes.to_vec(), )))) @@ -202,7 +233,11 @@ where match Db::find_instruction_by_cid(cid, conn) { Ok(found) => Ok(found.output_as_arg()), Err(_) => { - debug!("no related instruction receipt found in the DB"); + debug!( + subject = "worker.resolve_cid", + category = "worker.run", + "no related instruction receipt found in the DB" + ); let (tx, rx) = AsyncChannel::oneshot(); let _ = event_sender .send_async(Event::FindRecord(QueryRecord::with( @@ -251,6 +286,8 @@ where { if scheduler.ran_length() > 0 { info!( + subject = "worker.replay", + category = "worker.run", workflow_cid = self.workflow_info.cid.to_string(), "{} tasks left to run, sending last batch for workflow", scheduler.run_length() @@ -350,7 +387,10 @@ where let resolved = match resolved.await { Ok(inst_result) => inst_result, Err(err) => { - error!(err=?err, "error resolving cid"); + error!(subject = "worker.resolve_cid.err", + category = "worker.run", + err=?err, + "error resolving cid"); return Err(anyhow!("error resolving cid: {err}")) .with_context(|| { format!("could not spawn task for cid: {workflow_cid}") @@ -367,13 +407,15 @@ where Err(err) => Err( anyhow!("cannot execute wasm module: {err}")) .with_context(|| { - format!("not able to run fn {fun} for promised cid: {instruction_ptr}, in workflow {workflow_cid}") + format!("not able to run fn {fun} for cid: {instruction_ptr}, in workflow {workflow_cid}") }), } }); handles.push(handle); } None => error!( + subject = "worker.run.task.err", + category = "worker.run", "no valid task/instruction-type referenced by operation: {}", instruction.op() ), @@ -387,11 +429,17 @@ where { Ok(Ok(data)) => data, Ok(Err(err)) => { - error!(err=?err, "error in running task"); + error!(subject = "worker.run.task.err", + category = "worker.run", + err=?err, + "error in running task"); break; } Err(err) => { - error!(err=?err, "error in running task"); + error!(subject = "worker.run.task.err", + category = "worker.run", + err=?err, + "error in running task"); break; } }; @@ -426,6 +474,8 @@ where Db::commit_receipt(self.workflow_info.cid, receipt, &mut self.db.conn()?)?; debug!( + subject = "db.commit_receipt", + category = "worker.run", cid = self.workflow_info.cid.to_string(), "commited to database" ); diff --git a/homestar-runtime/src/workflow.rs b/homestar-runtime/src/workflow.rs index a01b48ce..b4669a9a 100644 --- a/homestar-runtime/src/workflow.rs +++ b/homestar-runtime/src/workflow.rs @@ -156,7 +156,12 @@ impl<'a> Builder<'a> { (Dag::default(), vec![], vec![], IndexMap::new()), |(mut dag, mut unawaits, mut awaited, mut resources), (i, task)| { let instr_cid = task.instruction_cid()?; - debug!("instruction cid: {}", instr_cid); + debug!( + subject = "task.instruction", + category = "aot.information", + "instruction cid of task: {}", + instr_cid + ); // Clone as we're owning the struct going backward. let ptr: Pointer = Invocation::::from(task.clone()).try_into()?; diff --git a/homestar-runtime/src/workflow/info.rs b/homestar-runtime/src/workflow/info.rs index 7110a64d..e4b7e351 100644 --- a/homestar-runtime/src/workflow/info.rs +++ b/homestar-runtime/src/workflow/info.rs @@ -44,11 +44,29 @@ const RESOURCES_KEY: &str = "resources"; #[derive(Debug, Clone, PartialEq, Queryable, Insertable, Identifiable, Selectable)] #[diesel(table_name = crate::db::schema::workflows, primary_key(cid))] pub struct Stored { + /// Wrapped-[Cid] of [Workflow]. + /// + /// [Workflow]: homestar_core::Workflow pub(crate) cid: Pointer, + /// Local name of [Workflow]. + /// + /// [Workflow]: homestar_core::Workflow pub(crate) name: Option, + /// Number of tasks in [Workflow]. + /// + /// [Workflow]: homestar_core::Workflow pub(crate) num_tasks: i32, + /// Map of [Instruction] [Cid]s to resources. + /// + /// [Instruction]: homestar_core::workflow::Instruction pub(crate) resources: IndexedResources, + /// Local timestamp of [Workflow] creation. + /// + /// [Workflow]: homestar_core::Workflow pub(crate) created_at: NaiveDateTime, + /// Local timestamp of [Workflow] completion. + /// + /// [Workflow]: homestar_core::Workflow pub(crate) completed_at: Option, } @@ -275,6 +293,8 @@ impl Info { Ok((_, info)) => Ok((info, timestamp)), Err(_err) => { info!( + subject = "workflow.init.db.check", + category = "workflow", cid = workflow_cid.to_string(), "workflow information not available in the database" ); @@ -322,6 +342,8 @@ impl Info { Some((_name, workflow_info)) => Ok(workflow_info), None => { info!( + subject = "workflow.gather.db.check", + category = "workflow", cid = workflow_cid.to_string(), "workflow information not available in the database" ); diff --git a/homestar-runtime/src/workflow/settings.rs b/homestar-runtime/src/workflow/settings.rs index 9ea3e682..b5eae6de 100644 --- a/homestar-runtime/src/workflow/settings.rs +++ b/homestar-runtime/src/workflow/settings.rs @@ -7,10 +7,15 @@ use std::time::Duration; /// Workflow settings. #[derive(Debug, Clone, PartialEq)] pub struct Settings { + /// Number of retries for a given workflow. pub(crate) retries: u32, + /// Maximum delay between retries. pub(crate) retry_max_delay: Duration, + /// Initial delay between retries. pub(crate) retry_initial_delay: Duration, + /// Timeout for P2P/DHT operations. pub(crate) p2p_timeout: Duration, + /// Timeout for a given workflow. pub(crate) timeout: Duration, } diff --git a/homestar-runtime/tests/cli.rs b/homestar-runtime/tests/cli.rs index 550b0263..e7fce91c 100644 --- a/homestar-runtime/tests/cli.rs +++ b/homestar-runtime/tests/cli.rs @@ -200,7 +200,7 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); @@ -215,7 +215,7 @@ fn test_workflow_run_serial() -> Result<()> { .assert() .success() .stdout(predicate::str::contains( - "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia", + "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a", )) .stdout(predicate::str::contains("num_tasks")) .stdout(predicate::str::contains("progress_count")); diff --git a/homestar-runtime/tests/fixtures/test-workflow-add-one.json b/homestar-runtime/tests/fixtures/test-workflow-add-one.json index 05aa7c3c..5b83a711 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-add-one.json +++ b/homestar-runtime/tests/fixtures/test-workflow-add-one.json @@ -17,7 +17,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } }, { @@ -33,7 +33,7 @@ "args": [ { "await/ok": { - "/": "bafyrmid5morwzj3lz436g44usvu35xcfyn4ommfe4ozulmnrsohybdez3a" + "/": "bafyrmie3vqqilbt6ghxqkvzieials254hwqr23fl6ecktt4kdmftbxejfu" } } ], @@ -41,7 +41,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } } ] diff --git a/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json index 5d252963..a9362b96 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json +++ b/homestar-runtime/tests/fixtures/test-workflow-image-pipeline.json @@ -22,7 +22,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } }, { @@ -37,7 +37,7 @@ "args": [ { "await/ok": { - "/": "bafyrmid35kzcxbn5xhsewyzzja5gngm54peve5i45vdj3bxgokmuvj2wwy" + "/": "bafyrmiaadxb2oauwkak5ugyvgmi4jtw5bck2e3rvoznlegahniv654b3l4" } } ], @@ -45,7 +45,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } }, { @@ -60,7 +60,7 @@ "args": [ { "await/ok": { - "/": "bafyrmicubn76iynz6hjice6x47p7hgdlhht6qoizhuu746uea6e674jap4" + "/": "bafyrmigat3k3pbwavivjg3fldsnlwaxpznechmuibnhhldfhfiwmbnbyrq" } } ], @@ -68,7 +68,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } } ] diff --git a/homestar-runtime/tests/fixtures/test-workflow-jco.json b/homestar-runtime/tests/fixtures/test-workflow-jco.json index 85bbaeb3..abc2d206 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-jco.json +++ b/homestar-runtime/tests/fixtures/test-workflow-jco.json @@ -9,13 +9,36 @@ "prf": [], "run": { "input": { - "args": ["hello", 10], + "args": ["helloworld", 10], "func": "sum" }, "nnc": "", "op": "wasm/run", "rsc": "ipfs://bafybeibawnb3pytqmky4ph37hj7y7qosqcneofjextqq55zhxfniiletfu" } + }, + { + "cause": null, + "meta": { + "memory": 4294967296, + "time": 100000 + }, + "prf": [], + "run": { + "input": { + "args": [ + { + "await/ok": { + "/": "bafyrmigltroc4nrdaskjselusvfcuooc52k2rl7nmrf72edbauyptmx5bq" + } + } + ], + "func": "hashbytes" + }, + "nnc": "", + "op": "wasm/run", + "rsc": "ipfs://bafybeifrgndv3blrxucig2pei6uaoyiyipk5vvzds7ps47ec6pzf4ax2d4" + } } ] } diff --git a/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json b/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json index cc0407fc..b8569a09 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json +++ b/homestar-runtime/tests/fixtures/test-workflow-no-awaits1.json @@ -22,7 +22,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } } ] diff --git a/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json b/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json index d7d6a2e2..e82e7f63 100644 --- a/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json +++ b/homestar-runtime/tests/fixtures/test-workflow-no-awaits2.json @@ -22,7 +22,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } }, { @@ -47,7 +47,7 @@ }, "nnc": "", "op": "wasm/run", - "rsc": "ipfs://bafybeiczefaiu7464ehupezpzulnti5jvcwnvdalqrdliugnnwcdz6ljia" + "rsc": "ipfs://bafybeig6u35v6t3f4j3zgz2jvj4erd45fbkeolioaddu3lmu6uxm3ilb7a" } } ] diff --git a/homestar-runtime/tests/network.rs b/homestar-runtime/tests/network.rs index c4d78acc..c52eb19f 100644 --- a/homestar-runtime/tests/network.rs +++ b/homestar-runtime/tests/network.rs @@ -456,7 +456,7 @@ fn test_libp2p_connect_rendezvous_discovery_serial() -> Result<()> { } // Wait for registration to complete - // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep + // TODO When we have WebSocket push events, listen on a registration event instead of using an arbitrary sleep thread::sleep(Duration::from_secs(2)); // Start a peer that will discover the registrant through the rendezvous server @@ -762,7 +762,7 @@ fn test_libp2p_disconnect_rendezvous_discovery_serial() -> Result<()> { } // Wait for registration to complete. - // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep. + // TODO When we have WebSocket push events, listen on a registration event instead of using an arbitrary sleep. thread::sleep(Duration::from_secs(2)); // Start a peer that will discover the registrant through the rendezvous server @@ -1036,7 +1036,7 @@ fn test_libp2p_rendezvous_rediscover_on_expiration_serial() -> Result<()> { } // Wait for registration to complete. - // TODO When we have websocket push events, listen on a registration event instead of using an arbitrary sleep. + // TODO When we have WebSocket push events, listen on a registration event instead of using an arbitrary sleep. thread::sleep(Duration::from_secs(2)); // Start a peer that will discover with the rendezvous server when diff --git a/homestar-runtime/tests/webserver.rs b/homestar-runtime/tests/webserver.rs index 1aebda3f..6e3bb537 100644 --- a/homestar-runtime/tests/webserver.rs +++ b/homestar-runtime/tests/webserver.rs @@ -20,6 +20,7 @@ use std::{ static BIN: Lazy = Lazy::new(|| assert_cmd::cargo::cargo_bin(BIN_NAME)); const SUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "subscribe_run_workflow"; const UNSUBSCRIBE_RUN_WORKFLOW_ENDPOINT: &str = "unsubscribe_run_workflow"; +const AWAIT_CID: &str = "bafyrmih5bwjinspvn5ktpcaxqvvxkmhxrznhuu3qqmu45jnpmo3ab72vaq"; #[test] #[file_serial] @@ -75,7 +76,7 @@ fn test_workflow_run_serial() -> Result<()> { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + let expected = serde_json::json!({"name": "test", "replayed": false, "workflow": {"/": format!("{AWAIT_CID}")}}); assert_eq!(check, &expected); received_cids += 1; } else { @@ -103,7 +104,7 @@ fn test_workflow_run_serial() -> Result<()> { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": format!("{AWAIT_CID}")}}); assert_eq!(check, &expected); received_cids += 1; } else { @@ -137,7 +138,7 @@ fn test_workflow_run_serial() -> Result<()> { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + let expected = serde_json::json!({"name": "test", "replayed": true, "workflow": {"/": format!("{AWAIT_CID}")}}); assert_eq!(check, &expected); received_cids += 1; } else { @@ -172,7 +173,7 @@ fn test_workflow_run_serial() -> Result<()> { let json: serde_json::Value = serde_json::from_slice(&msg.unwrap().unwrap()).unwrap(); let check = json.get("metadata").unwrap(); - let expected = serde_json::json!({"name": "another_test", "replayed": true, "workflow": {"/": "bafyrmihfhdhxmhotbgn5digt6n7vgz2ukisafhjozki2e6nwtvunep3mrm"}}); + let expected = serde_json::json!({"name": "another_test", "replayed": true, "workflow": {"/": format!("{AWAIT_CID}")}}); assert_eq!(check, &expected); received_cids += 1; } else { diff --git a/homestar-wasm/fixtures/example_add.wasm b/homestar-wasm/fixtures/example_add.wasm index 2f3d643ad13ee8ea7279d27590360382da2245da..47bea7528a208bc000f958acfddac932f26ede02 100755 GIT binary patch delta 2018 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 diff --git a/homestar-wasm/fixtures/example_add.wat b/homestar-wasm/fixtures/example_add.wat index 868c0d5e..042ab78d 100644 --- a/homestar-wasm/fixtures/example_add.wat +++ b/homestar-wasm/fixtures/example_add.wat @@ -402,7 +402,7 @@ block ;; label = @6 i32.const 0 i32.load offset=1048984 - local.tee 7 + local.tee 6 i32.const 16 local.get 0 i32.const 11 @@ -432,51 +432,51 @@ i32.and local.get 1 i32.add - local.tee 2 + local.tee 1 i32.const 3 i32.shl - local.tee 5 + local.tee 2 i32.const 1048728 i32.add i32.load local.tee 0 i32.const 8 i32.add - local.tee 6 + local.tee 7 i32.load - local.tee 1 - local.get 5 + local.tee 5 + local.get 2 i32.const 1048720 i32.add - local.tee 5 + local.tee 2 i32.eq br_if 0 (;@8;) - local.get 1 local.get 5 + local.get 2 i32.store offset=12 + local.get 2 local.get 5 - local.get 1 i32.store offset=8 br 1 (;@7;) end i32.const 0 - local.get 7 + local.get 6 i32.const -2 - local.get 2 + local.get 1 i32.rotl i32.and i32.store offset=1048984 end local.get 0 - local.get 2 + local.get 1 i32.const 3 i32.shl - local.tee 2 + local.tee 1 i32.const 3 i32.or i32.store offset=4 local.get 0 - local.get 2 + local.get 1 i32.add local.tee 0 local.get 0 @@ -484,7 +484,7 @@ i32.const 1 i32.or i32.store offset=4 - local.get 6 + local.get 7 return end local.get 2 @@ -513,7 +513,7 @@ i32.const 1048576 i32.add i32.load - local.tee 6 + local.tee 7 i32.load offset=4 i32.const -8 i32.and @@ -522,11 +522,11 @@ local.set 5 block ;; label = @13 block ;; label = @14 - local.get 6 + local.get 7 i32.load offset=16 local.tee 0 br_if 0 (;@14;) - local.get 6 + local.get 7 i32.const 20 i32.add i32.load @@ -544,7 +544,7 @@ local.tee 8 local.get 5 i32.lt_u - local.set 7 + local.set 6 block ;; label = @15 local.get 0 i32.load offset=16 @@ -558,47 +558,47 @@ end local.get 8 local.get 5 - local.get 7 + local.get 6 select local.set 5 local.get 0 - local.get 6 local.get 7 + local.get 6 select - local.set 6 + local.set 7 local.get 1 local.set 0 local.get 1 br_if 0 (;@14;) end end - local.get 6 + local.get 7 call 7 local.get 5 i32.const 16 i32.lt_u br_if 2 (;@10;) - local.get 6 + local.get 7 local.get 2 i32.const 3 i32.or i32.store offset=4 - local.get 6 + local.get 7 local.get 2 i32.add - local.tee 2 + local.tee 1 local.get 5 i32.const 1 i32.or i32.store offset=4 - local.get 2 + local.get 1 local.get 5 i32.add local.get 5 i32.store i32.const 0 i32.load offset=1048992 - local.tee 7 + local.tee 6 br_if 1 (;@11;) br 5 (;@7;) end @@ -623,7 +623,7 @@ local.tee 1 i32.const 3 i32.shl - local.tee 6 + local.tee 7 i32.const 1048728 i32.add i32.load @@ -633,22 +633,22 @@ local.tee 8 i32.load local.tee 5 - local.get 6 + local.get 7 i32.const 1048720 i32.add - local.tee 6 + local.tee 7 i32.eq br_if 0 (;@13;) local.get 5 - local.get 6 + local.get 7 i32.store offset=12 - local.get 6 + local.get 7 local.get 5 i32.store offset=8 br 1 (;@12;) end i32.const 0 - local.get 7 + local.get 6 i32.const -2 local.get 1 i32.rotl @@ -663,34 +663,34 @@ local.get 0 local.get 2 i32.add - local.tee 7 + local.tee 6 local.get 1 i32.const 3 i32.shl - local.tee 1 + local.tee 5 local.get 2 i32.sub - local.tee 2 + local.tee 1 i32.const 1 i32.or i32.store offset=4 local.get 0 - local.get 1 + local.get 5 i32.add - local.get 2 + local.get 1 i32.store i32.const 0 i32.load offset=1048992 - local.tee 5 + local.tee 2 br_if 2 (;@9;) br 3 (;@8;) end - local.get 7 + local.get 6 i32.const -8 i32.and i32.const 1048720 i32.add - local.set 1 + local.set 2 i32.const 0 i32.load offset=1049000 local.set 0 @@ -700,42 +700,41 @@ i32.load offset=1048984 local.tee 8 i32.const 1 - local.get 7 + local.get 6 i32.const 3 i32.shr_u i32.shl - local.tee 7 + local.tee 6 i32.and - i32.eqz br_if 0 (;@12;) - local.get 1 - i32.load offset=8 - local.set 7 + i32.const 0 + local.get 8 + local.get 6 + i32.or + i32.store offset=1048984 + local.get 2 + local.set 6 br 1 (;@11;) end - i32.const 0 - local.get 8 - local.get 7 - i32.or - i32.store offset=1048984 - local.get 1 - local.set 7 + local.get 2 + i32.load offset=8 + local.set 6 end - local.get 1 + local.get 2 local.get 0 i32.store offset=8 - local.get 7 + local.get 6 local.get 0 i32.store offset=12 local.get 0 - local.get 1 + local.get 2 i32.store offset=12 local.get 0 - local.get 7 + local.get 6 i32.store offset=8 br 3 (;@7;) end - local.get 6 + local.get 7 local.get 5 local.get 2 i32.add @@ -743,7 +742,7 @@ i32.const 3 i32.or i32.store offset=4 - local.get 6 + local.get 7 local.get 0 i32.add local.tee 0 @@ -754,12 +753,12 @@ i32.store offset=4 br 3 (;@6;) end - local.get 5 + local.get 2 i32.const -8 i32.and i32.const 1048720 i32.add - local.set 1 + local.set 5 i32.const 0 i32.load offset=1049000 local.set 0 @@ -767,59 +766,58 @@ block ;; label = @10 i32.const 0 i32.load offset=1048984 - local.tee 6 + local.tee 7 i32.const 1 - local.get 5 + local.get 2 i32.const 3 i32.shr_u i32.shl - local.tee 5 + local.tee 2 i32.and - i32.eqz br_if 0 (;@10;) - local.get 1 - i32.load offset=8 - local.set 5 + i32.const 0 + local.get 7 + local.get 2 + i32.or + i32.store offset=1048984 + local.get 5 + local.set 2 br 1 (;@9;) end - i32.const 0 - local.get 6 local.get 5 - i32.or - i32.store offset=1048984 - local.get 1 - local.set 5 + i32.load offset=8 + local.set 2 end - local.get 1 + local.get 5 local.get 0 i32.store offset=8 - local.get 5 + local.get 2 local.get 0 i32.store offset=12 local.get 0 - local.get 1 + local.get 5 i32.store offset=12 local.get 0 - local.get 5 + local.get 2 i32.store offset=8 end i32.const 0 - local.get 7 + local.get 6 i32.store offset=1049000 i32.const 0 - local.get 2 + local.get 1 i32.store offset=1048992 local.get 8 return end i32.const 0 - local.get 2 + local.get 1 i32.store offset=1049000 i32.const 0 local.get 5 i32.store offset=1048992 end - local.get 6 + local.get 7 i32.const 8 i32.add return @@ -858,21 +856,30 @@ br_if 1 (;@3;) end loop ;; label = @4 + local.get 0 + local.get 6 local.get 0 i32.load offset=4 i32.const -8 i32.and local.tee 5 local.get 2 - i32.ge_u - local.get 5 - local.get 2 i32.sub local.tee 8 local.get 1 i32.lt_u - i32.and + local.tee 4 + select + local.set 3 + local.get 5 + local.get 2 + i32.lt_u local.set 7 + local.get 8 + local.get 1 + local.get 4 + select + local.set 8 block ;; label = @5 local.get 0 i32.load offset=16 @@ -884,13 +891,13 @@ i32.load local.set 5 end - local.get 0 local.get 6 + local.get 3 local.get 7 select local.set 6 - local.get 8 local.get 1 + local.get 8 local.get 7 select local.set 1 @@ -958,12 +965,12 @@ i32.and i32.const 1048720 i32.add - local.set 2 + local.set 5 block ;; label = @5 block ;; label = @6 i32.const 0 i32.load offset=1048984 - local.tee 5 + local.tee 2 i32.const 1 local.get 1 i32.const 3 @@ -971,29 +978,28 @@ i32.shl local.tee 1 i32.and - i32.eqz br_if 0 (;@6;) + i32.const 0 local.get 2 - i32.load offset=8 + local.get 1 + i32.or + i32.store offset=1048984 + local.get 5 local.set 1 br 1 (;@5;) end - i32.const 0 local.get 5 - local.get 1 - i32.or - i32.store offset=1048984 - local.get 2 + i32.load offset=8 local.set 1 end - local.get 2 + local.get 5 local.get 0 i32.store offset=8 local.get 1 local.get 0 i32.store offset=12 local.get 0 - local.get 2 + local.get 5 i32.store offset=12 local.get 0 local.get 1 @@ -1031,887 +1037,884 @@ block ;; label = @7 block ;; label = @8 block ;; label = @9 + i32.const 0 + i32.load offset=1048992 + local.tee 0 + local.get 2 + i32.ge_u + br_if 0 (;@9;) block ;; label = @10 + i32.const 0 + i32.load offset=1048996 + local.tee 0 + local.get 2 + i32.gt_u + br_if 0 (;@10;) + i32.const 0 + local.set 1 + local.get 2 + i32.const 65583 + i32.add + local.tee 5 + i32.const 16 + i32.shr_u + memory.grow + local.tee 0 + i32.const -1 + i32.eq + local.tee 7 + br_if 9 (;@1;) + local.get 0 + i32.const 16 + i32.shl + local.tee 6 + i32.eqz + br_if 9 (;@1;) + i32.const 0 + i32.const 0 + i32.load offset=1049008 + i32.const 0 + local.get 5 + i32.const -65536 + i32.and + local.get 7 + select + local.tee 8 + i32.add + local.tee 0 + i32.store offset=1049008 + i32.const 0 + i32.const 0 + i32.load offset=1049012 + local.tee 1 + local.get 0 + local.get 1 + local.get 0 + i32.gt_u + select + i32.store offset=1049012 block ;; label = @11 - i32.const 0 - i32.load offset=1048992 - local.tee 0 - local.get 2 - i32.ge_u - br_if 0 (;@11;) block ;; label = @12 + block ;; label = @13 + i32.const 0 + i32.load offset=1049004 + local.tee 1 + i32.eqz + br_if 0 (;@13;) + i32.const 1048704 + local.set 0 + loop ;; label = @14 + local.get 0 + i32.load + local.tee 5 + local.get 0 + i32.load offset=4 + local.tee 7 + i32.add + local.get 6 + i32.eq + br_if 2 (;@12;) + local.get 0 + i32.load offset=8 + local.tee 0 + br_if 0 (;@14;) + br 3 (;@11;) + end + end + block ;; label = @13 + block ;; label = @14 + i32.const 0 + i32.load offset=1049020 + local.tee 0 + i32.eqz + br_if 0 (;@14;) + local.get 0 + local.get 6 + i32.le_u + br_if 1 (;@13;) + end + i32.const 0 + local.get 6 + i32.store offset=1049020 + end i32.const 0 - i32.load offset=1048996 - local.tee 0 - local.get 2 - i32.gt_u - br_if 0 (;@12;) + i32.const 4095 + i32.store offset=1049024 i32.const 0 - local.set 1 - local.get 2 - i32.const 65583 - i32.add - local.tee 5 - i32.const 16 - i32.shr_u - memory.grow - local.tee 0 - i32.const -1 - i32.eq - local.tee 6 - br_if 11 (;@1;) - local.get 0 - i32.const 16 - i32.shl - local.tee 7 - i32.eqz - br_if 11 (;@1;) + local.get 8 + i32.store offset=1048708 i32.const 0 + local.get 6 + i32.store offset=1048704 i32.const 0 - i32.load offset=1049008 + i32.const 1048720 + i32.store offset=1048732 i32.const 0 - local.get 5 - i32.const -65536 - i32.and - local.get 6 - select - local.tee 8 - i32.add - local.tee 0 - i32.store offset=1049008 + i32.const 1048728 + i32.store offset=1048740 i32.const 0 + i32.const 1048720 + i32.store offset=1048728 i32.const 0 - i32.load offset=1049012 - local.tee 1 - local.get 0 - local.get 1 - local.get 0 - i32.gt_u - select - i32.store offset=1049012 - block ;; label = @13 - block ;; label = @14 - block ;; label = @15 - i32.const 0 - i32.load offset=1049004 - local.tee 1 - i32.eqz - br_if 0 (;@15;) - i32.const 1048704 - local.set 0 - loop ;; label = @16 - local.get 0 - i32.load - local.tee 5 - local.get 0 - i32.load offset=4 - local.tee 6 - i32.add - local.get 7 - i32.eq - br_if 2 (;@14;) - local.get 0 - i32.load offset=8 - local.tee 0 - br_if 0 (;@16;) - br 3 (;@13;) - end - end - i32.const 0 - i32.load offset=1049020 - local.tee 0 - i32.eqz - br_if 4 (;@10;) - local.get 0 - local.get 7 - i32.gt_u - br_if 4 (;@10;) - br 11 (;@3;) - end - local.get 0 - i32.load offset=12 - br_if 0 (;@13;) - local.get 5 - local.get 1 - i32.gt_u - br_if 0 (;@13;) - local.get 1 - local.get 7 - i32.lt_u - br_if 4 (;@9;) - end + i32.const 1048736 + i32.store offset=1048748 + i32.const 0 + i32.const 1048728 + i32.store offset=1048736 + i32.const 0 + i32.const 1048744 + i32.store offset=1048756 + i32.const 0 + i32.const 1048736 + i32.store offset=1048744 + i32.const 0 + i32.const 1048752 + i32.store offset=1048764 + i32.const 0 + i32.const 1048744 + i32.store offset=1048752 + i32.const 0 + i32.const 1048760 + i32.store offset=1048772 + i32.const 0 + i32.const 1048752 + i32.store offset=1048760 + i32.const 0 + i32.const 1048768 + i32.store offset=1048780 + i32.const 0 + i32.const 1048760 + i32.store offset=1048768 + i32.const 0 + i32.const 1048776 + i32.store offset=1048788 + i32.const 0 + i32.const 1048768 + i32.store offset=1048776 + i32.const 0 + i32.const 0 + i32.store offset=1048716 + i32.const 0 + i32.const 1048784 + i32.store offset=1048796 + i32.const 0 + i32.const 1048776 + i32.store offset=1048784 + i32.const 0 + i32.const 1048784 + i32.store offset=1048792 + i32.const 0 + i32.const 1048792 + i32.store offset=1048804 + i32.const 0 + i32.const 1048792 + i32.store offset=1048800 + i32.const 0 + i32.const 1048800 + i32.store offset=1048812 + i32.const 0 + i32.const 1048800 + i32.store offset=1048808 + i32.const 0 + i32.const 1048808 + i32.store offset=1048820 + i32.const 0 + i32.const 1048808 + i32.store offset=1048816 + i32.const 0 + i32.const 1048816 + i32.store offset=1048828 + i32.const 0 + i32.const 1048816 + i32.store offset=1048824 + i32.const 0 + i32.const 1048824 + i32.store offset=1048836 + i32.const 0 + i32.const 1048824 + i32.store offset=1048832 + i32.const 0 + i32.const 1048832 + i32.store offset=1048844 + i32.const 0 + i32.const 1048832 + i32.store offset=1048840 + i32.const 0 + i32.const 1048840 + i32.store offset=1048852 + i32.const 0 + i32.const 1048840 + i32.store offset=1048848 + i32.const 0 + i32.const 1048848 + i32.store offset=1048860 + i32.const 0 + i32.const 1048856 + i32.store offset=1048868 + i32.const 0 + i32.const 1048848 + i32.store offset=1048856 + i32.const 0 + i32.const 1048864 + i32.store offset=1048876 + i32.const 0 + i32.const 1048856 + i32.store offset=1048864 + i32.const 0 + i32.const 1048872 + i32.store offset=1048884 + i32.const 0 + i32.const 1048864 + i32.store offset=1048872 + i32.const 0 + i32.const 1048880 + i32.store offset=1048892 + i32.const 0 + i32.const 1048872 + i32.store offset=1048880 + i32.const 0 + i32.const 1048888 + i32.store offset=1048900 + i32.const 0 + i32.const 1048880 + i32.store offset=1048888 + i32.const 0 + i32.const 1048896 + i32.store offset=1048908 i32.const 0 + i32.const 1048888 + i32.store offset=1048896 i32.const 0 - i32.load offset=1049020 + i32.const 1048904 + i32.store offset=1048916 + i32.const 0 + i32.const 1048896 + i32.store offset=1048904 + i32.const 0 + i32.const 1048912 + i32.store offset=1048924 + i32.const 0 + i32.const 1048904 + i32.store offset=1048912 + i32.const 0 + i32.const 1048920 + i32.store offset=1048932 + i32.const 0 + i32.const 1048912 + i32.store offset=1048920 + i32.const 0 + i32.const 1048928 + i32.store offset=1048940 + i32.const 0 + i32.const 1048920 + i32.store offset=1048928 + i32.const 0 + i32.const 1048936 + i32.store offset=1048948 + i32.const 0 + i32.const 1048928 + i32.store offset=1048936 + i32.const 0 + i32.const 1048944 + i32.store offset=1048956 + i32.const 0 + i32.const 1048936 + i32.store offset=1048944 + i32.const 0 + i32.const 1048952 + i32.store offset=1048964 + i32.const 0 + i32.const 1048944 + i32.store offset=1048952 + i32.const 0 + i32.const 1048960 + i32.store offset=1048972 + i32.const 0 + i32.const 1048952 + i32.store offset=1048960 + i32.const 0 + i32.const 1048968 + i32.store offset=1048980 + i32.const 0 + i32.const 1048960 + i32.store offset=1048968 + i32.const 0 + local.get 6 + i32.store offset=1049004 + i32.const 0 + i32.const 1048968 + i32.store offset=1048976 + i32.const 0 + local.get 8 + i32.const -40 + i32.add local.tee 0 - local.get 7 + i32.store offset=1048996 + local.get 6 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 6 local.get 0 - local.get 7 - i32.lt_u - select - i32.store offset=1049020 - local.get 7 - local.get 8 i32.add - local.set 5 - i32.const 1048704 - local.set 0 + i32.const 40 + i32.store offset=4 + i32.const 0 + i32.const 2097152 + i32.store offset=1049016 + br 10 (;@2;) + end + local.get 0 + i32.load offset=12 + br_if 0 (;@11;) + local.get 5 + local.get 1 + i32.gt_u + br_if 0 (;@11;) + local.get 1 + local.get 6 + i32.lt_u + br_if 3 (;@8;) + end + i32.const 0 + i32.const 0 + i32.load offset=1049020 + local.tee 0 + local.get 6 + local.get 0 + local.get 6 + i32.lt_u + select + i32.store offset=1049020 + local.get 6 + local.get 8 + i32.add + local.set 5 + i32.const 1048704 + local.set 0 + block ;; label = @11 + block ;; label = @12 block ;; label = @13 - block ;; label = @14 - block ;; label = @15 - loop ;; label = @16 - local.get 0 - i32.load - local.get 5 - i32.eq - br_if 1 (;@15;) - local.get 0 - i32.load offset=8 - local.tee 0 - br_if 0 (;@16;) - br 2 (;@14;) - end - end - local.get 0 - i32.load offset=12 - i32.eqz - br_if 1 (;@13;) - end - i32.const 1048704 - local.set 0 - block ;; label = @14 - loop ;; label = @15 - block ;; label = @16 - local.get 0 - i32.load - local.tee 5 - local.get 1 - i32.gt_u - br_if 0 (;@16;) - local.get 5 - local.get 0 - i32.load offset=4 - i32.add - local.tee 5 - local.get 1 - i32.gt_u - br_if 2 (;@14;) - end - local.get 0 - i32.load offset=8 - local.set 0 - br 0 (;@15;) - end - end - i32.const 0 - local.get 7 - i32.store offset=1049004 - i32.const 0 - local.get 8 - i32.const -40 - i32.add - local.tee 0 - i32.store offset=1048996 - local.get 7 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 7 - local.get 0 - i32.add - i32.const 40 - i32.store offset=4 - i32.const 0 - i32.const 2097152 - i32.store offset=1049016 - local.get 1 - local.get 5 - i32.const -32 - i32.add - i32.const -8 - i32.and - i32.const -8 - i32.add - local.tee 0 - local.get 0 - local.get 1 - i32.const 16 - i32.add - i32.lt_u - select - local.tee 6 - i32.const 27 - i32.store offset=4 - i32.const 0 - i64.load offset=1048704 align=4 - local.set 9 - local.get 6 - i32.const 16 - i32.add - i32.const 0 - i64.load offset=1048712 align=4 - i64.store align=4 - local.get 6 - local.get 9 - i64.store offset=8 align=4 - i32.const 0 - local.get 8 - i32.store offset=1048708 - i32.const 0 - local.get 7 - i32.store offset=1048704 - i32.const 0 - local.get 6 - i32.const 8 - i32.add - i32.store offset=1048712 - i32.const 0 - i32.const 0 - i32.store offset=1048716 - local.get 6 - i32.const 28 - i32.add - local.set 0 loop ;; label = @14 local.get 0 - i32.const 7 - i32.store + i32.load + local.get 5 + i32.eq + br_if 1 (;@13;) local.get 0 - i32.const 4 - i32.add + i32.load offset=8 local.tee 0 - local.get 5 - i32.lt_u br_if 0 (;@14;) + br 2 (;@12;) end - local.get 6 - local.get 1 - i32.eq - br_if 11 (;@2;) - local.get 6 - local.get 6 - i32.load offset=4 - i32.const -2 - i32.and - i32.store offset=4 - local.get 1 - local.get 6 - local.get 1 - i32.sub - local.tee 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 6 - local.get 0 - i32.store + end + local.get 0 + i32.load offset=12 + i32.eqz + br_if 1 (;@11;) + end + i32.const 1048704 + local.set 0 + block ;; label = @12 + loop ;; label = @13 block ;; label = @14 local.get 0 - i32.const 256 - i32.lt_u - br_if 0 (;@14;) + i32.load + local.tee 5 local.get 1 - local.get 0 - call 8 - br 12 (;@2;) - end - local.get 0 - i32.const -8 - i32.and - i32.const 1048720 - i32.add - local.set 5 - block ;; label = @14 - block ;; label = @15 - i32.const 0 - i32.load offset=1048984 - local.tee 7 - i32.const 1 - local.get 0 - i32.const 3 - i32.shr_u - i32.shl - local.tee 0 - i32.and - i32.eqz - br_if 0 (;@15;) - local.get 5 - i32.load offset=8 - local.set 0 - br 1 (;@14;) - end - i32.const 0 - local.get 7 - local.get 0 - i32.or - i32.store offset=1048984 + i32.gt_u + br_if 0 (;@14;) local.get 5 - local.set 0 + local.get 0 + i32.load offset=4 + i32.add + local.tee 5 + local.get 1 + i32.gt_u + br_if 2 (;@12;) end - local.get 5 - local.get 1 - i32.store offset=8 - local.get 0 - local.get 1 - i32.store offset=12 - local.get 1 - local.get 5 - i32.store offset=12 - local.get 1 local.get 0 - i32.store offset=8 - br 11 (;@2;) + i32.load offset=8 + local.set 0 + br 0 (;@13;) end + end + i32.const 0 + local.get 6 + i32.store offset=1049004 + i32.const 0 + local.get 8 + i32.const -40 + i32.add + local.tee 0 + i32.store offset=1048996 + local.get 6 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 6 + local.get 0 + i32.add + i32.const 40 + i32.store offset=4 + i32.const 0 + i32.const 2097152 + i32.store offset=1049016 + local.get 1 + local.get 5 + i32.const -32 + i32.add + i32.const -8 + i32.and + i32.const -8 + i32.add + local.tee 0 + local.get 0 + local.get 1 + i32.const 16 + i32.add + i32.lt_u + select + local.tee 7 + i32.const 27 + i32.store offset=4 + i32.const 0 + i64.load offset=1048704 align=4 + local.set 9 + local.get 7 + i32.const 16 + i32.add + i32.const 0 + i64.load offset=1048712 align=4 + i64.store align=4 + local.get 7 + local.get 9 + i64.store offset=8 align=4 + i32.const 0 + local.get 8 + i32.store offset=1048708 + i32.const 0 + local.get 6 + i32.store offset=1048704 + i32.const 0 + local.get 7 + i32.const 8 + i32.add + i32.store offset=1048712 + i32.const 0 + i32.const 0 + i32.store offset=1048716 + local.get 7 + i32.const 28 + i32.add + local.set 0 + loop ;; label = @12 local.get 0 - local.get 7 + i32.const 7 i32.store local.get 0 + i32.const 4 + i32.add + local.tee 0 + local.get 5 + i32.lt_u + br_if 0 (;@12;) + end + local.get 7 + local.get 1 + i32.eq + br_if 9 (;@2;) + local.get 7 + local.get 7 + i32.load offset=4 + i32.const -2 + i32.and + i32.store offset=4 + local.get 1 + local.get 7 + local.get 1 + i32.sub + local.tee 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 7 + local.get 0 + i32.store + block ;; label = @12 + local.get 0 + i32.const 256 + i32.lt_u + br_if 0 (;@12;) + local.get 1 local.get 0 - i32.load offset=4 - local.get 8 - i32.add - i32.store offset=4 - local.get 7 - local.get 2 - i32.const 3 - i32.or - i32.store offset=4 - local.get 5 - local.get 7 - local.get 2 - i32.add - local.tee 0 - i32.sub - local.set 2 + call 8 + br 10 (;@2;) + end + local.get 0 + i32.const -8 + i32.and + i32.const 1048720 + i32.add + local.set 5 + block ;; label = @12 block ;; label = @13 - local.get 5 - i32.const 0 - i32.load offset=1049004 - i32.eq - br_if 0 (;@13;) - local.get 5 i32.const 0 - i32.load offset=1049000 - i32.eq - br_if 5 (;@8;) - local.get 5 - i32.load offset=4 - local.tee 1 + i32.load offset=1048984 + local.tee 6 + i32.const 1 + local.get 0 i32.const 3 + i32.shr_u + i32.shl + local.tee 0 i32.and - i32.const 1 - i32.ne - br_if 8 (;@5;) - block ;; label = @14 - block ;; label = @15 - local.get 1 - i32.const -8 - i32.and - local.tee 6 - i32.const 256 - i32.lt_u - br_if 0 (;@15;) - local.get 5 - call 7 - br 1 (;@14;) - end - block ;; label = @15 - local.get 5 - i32.const 12 - i32.add - i32.load - local.tee 8 - local.get 5 - i32.const 8 - i32.add - i32.load - local.tee 4 - i32.eq - br_if 0 (;@15;) - local.get 4 - local.get 8 - i32.store offset=12 - local.get 8 - local.get 4 - i32.store offset=8 - br 1 (;@14;) - end - i32.const 0 - i32.const 0 - i32.load offset=1048984 - i32.const -2 - local.get 1 - i32.const 3 - i32.shr_u - i32.rotl - i32.and - i32.store offset=1048984 - end + br_if 0 (;@13;) + i32.const 0 local.get 6 - local.get 2 - i32.add - local.set 2 + local.get 0 + i32.or + i32.store offset=1048984 local.get 5 - local.get 6 - i32.add - local.tee 5 - i32.load offset=4 - local.set 1 - br 8 (;@5;) + local.set 0 + br 1 (;@12;) end - i32.const 0 - local.get 0 - i32.store offset=1049004 - i32.const 0 - i32.const 0 - i32.load offset=1048996 - local.get 2 - i32.add - local.tee 2 - i32.store offset=1048996 - local.get 0 - local.get 2 - i32.const 1 - i32.or - i32.store offset=4 - br 8 (;@4;) + local.get 5 + i32.load offset=8 + local.set 0 end - i32.const 0 - local.get 0 - local.get 2 - i32.sub - local.tee 1 - i32.store offset=1048996 - i32.const 0 - i32.const 0 - i32.load offset=1049004 - local.tee 0 - local.get 2 - i32.add - local.tee 5 - i32.store offset=1049004 local.get 5 local.get 1 - i32.const 1 - i32.or - i32.store offset=4 + i32.store offset=8 local.get 0 - local.get 2 - i32.const 3 - i32.or - i32.store offset=4 + local.get 1 + i32.store offset=12 + local.get 1 + local.get 5 + i32.store offset=12 + local.get 1 local.get 0 - i32.const 8 - i32.add - local.set 1 - br 10 (;@1;) + i32.store offset=8 + br 9 (;@2;) end - i32.const 0 - i32.load offset=1049000 - local.set 1 local.get 0 + local.get 6 + i32.store + local.get 0 + local.get 0 + i32.load offset=4 + local.get 8 + i32.add + i32.store offset=4 + local.get 6 local.get 2 + i32.const 3 + i32.or + i32.store offset=4 + local.get 5 + local.get 6 + local.get 2 + i32.add + local.tee 0 i32.sub - local.tee 5 - i32.const 16 - i32.lt_u - br_if 3 (;@7;) + local.set 1 + local.get 5 i32.const 0 + i32.load offset=1049004 + i32.eq + br_if 3 (;@7;) local.get 5 - i32.store offset=1048992 i32.const 0 - local.get 1 - local.get 2 - i32.add - local.tee 7 - i32.store offset=1049000 - local.get 7 + i32.load offset=1049000 + i32.eq + br_if 4 (;@6;) + block ;; label = @11 + local.get 5 + i32.load offset=4 + local.tee 2 + i32.const 3 + i32.and + i32.const 1 + i32.ne + br_if 0 (;@11;) + block ;; label = @12 + block ;; label = @13 + local.get 2 + i32.const -8 + i32.and + local.tee 7 + i32.const 256 + i32.lt_u + br_if 0 (;@13;) + local.get 5 + call 7 + br 1 (;@12;) + end + block ;; label = @13 + local.get 5 + i32.const 12 + i32.add + i32.load + local.tee 8 + local.get 5 + i32.const 8 + i32.add + i32.load + local.tee 4 + i32.eq + br_if 0 (;@13;) + local.get 4 + local.get 8 + i32.store offset=12 + local.get 8 + local.get 4 + i32.store offset=8 + br 1 (;@12;) + end + i32.const 0 + i32.const 0 + i32.load offset=1048984 + i32.const -2 + local.get 2 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=1048984 + end + local.get 7 + local.get 1 + i32.add + local.set 1 + local.get 5 + local.get 7 + i32.add + local.tee 5 + i32.load offset=4 + local.set 2 + end local.get 5 + local.get 2 + i32.const -2 + i32.and + i32.store offset=4 + local.get 0 + local.get 1 i32.const 1 i32.or i32.store offset=4 + local.get 0 + local.get 1 + i32.add + local.get 1 + i32.store + block ;; label = @11 + local.get 1 + i32.const 256 + i32.lt_u + br_if 0 (;@11;) + local.get 0 + local.get 1 + call 8 + br 8 (;@3;) + end + local.get 1 + i32.const -8 + i32.and + i32.const 1048720 + i32.add + local.set 5 + block ;; label = @11 + block ;; label = @12 + i32.const 0 + i32.load offset=1048984 + local.tee 2 + i32.const 1 + local.get 1 + i32.const 3 + i32.shr_u + i32.shl + local.tee 1 + i32.and + br_if 0 (;@12;) + i32.const 0 + local.get 2 + local.get 1 + i32.or + i32.store offset=1048984 + local.get 5 + local.set 1 + br 1 (;@11;) + end + local.get 5 + i32.load offset=8 + local.set 1 + end + local.get 5 + local.get 0 + i32.store offset=8 local.get 1 local.get 0 - i32.add + i32.store offset=12 + local.get 0 local.get 5 - i32.store + i32.store offset=12 + local.get 0 local.get 1 - local.get 2 - i32.const 3 - i32.or - i32.store offset=4 - br 4 (;@6;) + i32.store offset=8 + br 7 (;@3;) end i32.const 0 - local.get 7 - i32.store offset=1049020 - br 6 (;@3;) + local.get 0 + local.get 2 + i32.sub + local.tee 1 + i32.store offset=1048996 + i32.const 0 + i32.const 0 + i32.load offset=1049004 + local.tee 0 + local.get 2 + i32.add + local.tee 5 + i32.store offset=1049004 + local.get 5 + local.get 1 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 2 + i32.const 3 + i32.or + i32.store offset=4 + local.get 0 + i32.const 8 + i32.add + local.set 1 + br 8 (;@1;) end + i32.const 0 + i32.load offset=1049000 + local.set 1 local.get 0 - local.get 6 - local.get 8 - i32.add - i32.store offset=4 + local.get 2 + i32.sub + local.tee 5 + i32.const 16 + i32.lt_u + br_if 3 (;@5;) i32.const 0 - i32.load offset=1049004 + local.get 5 + i32.store offset=1048992 i32.const 0 - i32.load offset=1048996 - local.get 8 + local.get 1 + local.get 2 i32.add - call 9 - br 6 (;@2;) + local.tee 6 + i32.store offset=1049000 + local.get 6 + local.get 5 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.get 5 + i32.store + local.get 1 + local.get 2 + i32.const 3 + i32.or + i32.store offset=4 + br 4 (;@4;) end - i32.const 0 local.get 0 - i32.store offset=1049000 - i32.const 0 - i32.const 0 - i32.load offset=1048992 - local.get 2 + local.get 7 + local.get 8 i32.add - local.tee 2 - i32.store offset=1048992 - local.get 0 - local.get 2 - i32.const 1 - i32.or i32.store offset=4 - local.get 0 - local.get 2 + i32.const 0 + i32.load offset=1049004 + i32.const 0 + i32.load offset=1048996 + local.get 8 i32.add - local.get 2 - i32.store - br 3 (;@4;) + call 9 + br 5 (;@2;) end i32.const 0 - i32.const 0 - i32.store offset=1049000 + local.get 0 + i32.store offset=1049004 i32.const 0 i32.const 0 - i32.store offset=1048992 - local.get 1 - local.get 0 - i32.const 3 - i32.or - i32.store offset=4 + i32.load offset=1048996 local.get 1 - local.get 0 i32.add - local.tee 0 + local.tee 1 + i32.store offset=1048996 local.get 0 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - end - local.get 1 - i32.const 8 - i32.add - return - end - local.get 5 - local.get 1 - i32.const -2 - i32.and - i32.store offset=4 - local.get 0 - local.get 2 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - local.get 2 - i32.add - local.get 2 - i32.store - block ;; label = @5 - local.get 2 - i32.const 256 - i32.lt_u - br_if 0 (;@5;) - local.get 0 - local.get 2 - call 8 - br 1 (;@4;) - end - local.get 2 - i32.const -8 - i32.and - i32.const 1048720 - i32.add - local.set 1 - block ;; label = @5 - block ;; label = @6 - i32.const 0 - i32.load offset=1048984 - local.tee 5 - i32.const 1 - local.get 2 - i32.const 3 - i32.shr_u - i32.shl - local.tee 2 - i32.and - i32.eqz - br_if 0 (;@6;) - local.get 1 - i32.load offset=8 - local.set 2 - br 1 (;@5;) - end - i32.const 0 - local.get 5 - local.get 2 - i32.or - i32.store offset=1048984 - local.get 1 - local.set 2 - end - local.get 1 - local.get 0 - i32.store offset=8 - local.get 2 - local.get 0 - i32.store offset=12 - local.get 0 - local.get 1 - i32.store offset=12 - local.get 0 - local.get 2 - i32.store offset=8 - end - local.get 7 - i32.const 8 - i32.add - return - end - i32.const 0 - i32.const 4095 - i32.store offset=1049024 - i32.const 0 - local.get 8 - i32.store offset=1048708 - i32.const 0 - local.get 7 - i32.store offset=1048704 - i32.const 0 - i32.const 1048720 - i32.store offset=1048732 - i32.const 0 - i32.const 1048728 - i32.store offset=1048740 - i32.const 0 - i32.const 1048720 - i32.store offset=1048728 - i32.const 0 - i32.const 1048736 - i32.store offset=1048748 - i32.const 0 - i32.const 1048728 - i32.store offset=1048736 - i32.const 0 - i32.const 1048744 - i32.store offset=1048756 - i32.const 0 - i32.const 1048736 - i32.store offset=1048744 - i32.const 0 - i32.const 1048752 - i32.store offset=1048764 - i32.const 0 - i32.const 1048744 - i32.store offset=1048752 - i32.const 0 - i32.const 1048760 - i32.store offset=1048772 - i32.const 0 - i32.const 1048752 - i32.store offset=1048760 - i32.const 0 - i32.const 1048768 - i32.store offset=1048780 - i32.const 0 - i32.const 1048760 - i32.store offset=1048768 - i32.const 0 - i32.const 1048776 - i32.store offset=1048788 - i32.const 0 - i32.const 1048768 - i32.store offset=1048776 - i32.const 0 - i32.const 0 - i32.store offset=1048716 - i32.const 0 - i32.const 1048784 - i32.store offset=1048796 - i32.const 0 - i32.const 1048776 - i32.store offset=1048784 - i32.const 0 - i32.const 1048784 - i32.store offset=1048792 - i32.const 0 - i32.const 1048792 - i32.store offset=1048804 - i32.const 0 - i32.const 1048792 - i32.store offset=1048800 - i32.const 0 - i32.const 1048800 - i32.store offset=1048812 - i32.const 0 - i32.const 1048800 - i32.store offset=1048808 - i32.const 0 - i32.const 1048808 - i32.store offset=1048820 - i32.const 0 - i32.const 1048808 - i32.store offset=1048816 - i32.const 0 - i32.const 1048816 - i32.store offset=1048828 - i32.const 0 - i32.const 1048816 - i32.store offset=1048824 - i32.const 0 - i32.const 1048824 - i32.store offset=1048836 - i32.const 0 - i32.const 1048824 - i32.store offset=1048832 - i32.const 0 - i32.const 1048832 - i32.store offset=1048844 - i32.const 0 - i32.const 1048832 - i32.store offset=1048840 - i32.const 0 - i32.const 1048840 - i32.store offset=1048852 - i32.const 0 - i32.const 1048840 - i32.store offset=1048848 - i32.const 0 - i32.const 1048848 - i32.store offset=1048860 - i32.const 0 - i32.const 1048856 - i32.store offset=1048868 - i32.const 0 - i32.const 1048848 - i32.store offset=1048856 - i32.const 0 - i32.const 1048864 - i32.store offset=1048876 - i32.const 0 - i32.const 1048856 - i32.store offset=1048864 - i32.const 0 - i32.const 1048872 - i32.store offset=1048884 - i32.const 0 - i32.const 1048864 - i32.store offset=1048872 - i32.const 0 - i32.const 1048880 - i32.store offset=1048892 - i32.const 0 - i32.const 1048872 - i32.store offset=1048880 - i32.const 0 - i32.const 1048888 - i32.store offset=1048900 - i32.const 0 - i32.const 1048880 - i32.store offset=1048888 - i32.const 0 - i32.const 1048896 - i32.store offset=1048908 - i32.const 0 - i32.const 1048888 - i32.store offset=1048896 - i32.const 0 - i32.const 1048904 - i32.store offset=1048916 - i32.const 0 - i32.const 1048896 - i32.store offset=1048904 - i32.const 0 - i32.const 1048912 - i32.store offset=1048924 - i32.const 0 - i32.const 1048904 - i32.store offset=1048912 - i32.const 0 - i32.const 1048920 - i32.store offset=1048932 - i32.const 0 - i32.const 1048912 - i32.store offset=1048920 - i32.const 0 - i32.const 1048928 - i32.store offset=1048940 - i32.const 0 - i32.const 1048920 - i32.store offset=1048928 - i32.const 0 - i32.const 1048936 - i32.store offset=1048948 - i32.const 0 - i32.const 1048928 - i32.store offset=1048936 - i32.const 0 - i32.const 1048944 - i32.store offset=1048956 - i32.const 0 - i32.const 1048936 - i32.store offset=1048944 - i32.const 0 - i32.const 1048952 - i32.store offset=1048964 - i32.const 0 - i32.const 1048944 - i32.store offset=1048952 - i32.const 0 - i32.const 1048960 - i32.store offset=1048972 - i32.const 0 - i32.const 1048952 - i32.store offset=1048960 - i32.const 0 - i32.const 1048968 - i32.store offset=1048980 - i32.const 0 - i32.const 1048960 - i32.store offset=1048968 - i32.const 0 - local.get 7 - i32.store offset=1049004 - i32.const 0 - i32.const 1048968 - i32.store offset=1048976 - i32.const 0 - local.get 8 - i32.const -40 - i32.add - local.tee 0 - i32.store offset=1048996 - local.get 7 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 7 - local.get 0 + local.get 1 + i32.const 1 + i32.or + i32.store offset=4 + br 3 (;@3;) + end + i32.const 0 + local.get 0 + i32.store offset=1049000 + i32.const 0 + i32.const 0 + i32.load offset=1048992 + local.get 1 + i32.add + local.tee 1 + i32.store offset=1048992 + local.get 0 + local.get 1 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 1 + i32.add + local.get 1 + i32.store + br 2 (;@3;) + end + i32.const 0 + i32.const 0 + i32.store offset=1049000 + i32.const 0 + i32.const 0 + i32.store offset=1048992 + local.get 1 + local.get 0 + i32.const 3 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.tee 0 + local.get 0 + i32.load offset=4 + i32.const 1 + i32.or + i32.store offset=4 + end + local.get 1 + i32.const 8 + i32.add + return + end + local.get 6 + i32.const 8 i32.add - i32.const 40 - i32.store offset=4 - i32.const 0 - i32.const 2097152 - i32.store offset=1049016 + return end i32.const 0 local.set 1 @@ -2054,92 +2057,99 @@ i32.and i32.store offset=1048984 end - block ;; label = @3 - local.get 2 - i32.load offset=4 - local.tee 3 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@3;) - local.get 2 - local.get 3 - i32.const -2 - i32.and - i32.store offset=4 - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - local.get 1 - i32.add - local.get 1 - i32.store - br 2 (;@1;) - end block ;; label = @3 block ;; label = @4 - local.get 2 - i32.const 0 - i32.load offset=1049004 - i32.eq - br_if 0 (;@4;) - local.get 2 - i32.const 0 - i32.load offset=1049000 - i32.eq - br_if 1 (;@3;) - local.get 3 - i32.const -8 - i32.and - local.tee 4 - local.get 1 - i32.add - local.set 1 block ;; label = @5 - block ;; label = @6 - local.get 4 - i32.const 256 - i32.lt_u - br_if 0 (;@6;) - local.get 2 - call 7 - br 1 (;@5;) - end - block ;; label = @6 - local.get 2 - i32.const 12 - i32.add - i32.load - local.tee 4 - local.get 2 - i32.const 8 - i32.add - i32.load - local.tee 2 - i32.eq - br_if 0 (;@6;) - local.get 2 - local.get 4 - i32.store offset=12 - local.get 4 - local.get 2 - i32.store offset=8 - br 1 (;@5;) - end + local.get 2 + i32.load offset=4 + local.tee 3 + i32.const 2 + i32.and + br_if 0 (;@5;) + local.get 2 i32.const 0 + i32.load offset=1049004 + i32.eq + br_if 2 (;@3;) + local.get 2 i32.const 0 - i32.load offset=1048984 - i32.const -2 + i32.load offset=1049000 + i32.eq + br_if 4 (;@1;) local.get 3 - i32.const 3 - i32.shr_u - i32.rotl + i32.const -8 i32.and - i32.store offset=1048984 + local.tee 4 + local.get 1 + i32.add + local.set 1 + block ;; label = @6 + block ;; label = @7 + local.get 4 + i32.const 256 + i32.lt_u + br_if 0 (;@7;) + local.get 2 + call 7 + br 1 (;@6;) + end + block ;; label = @7 + local.get 2 + i32.const 12 + i32.add + i32.load + local.tee 4 + local.get 2 + i32.const 8 + i32.add + i32.load + local.tee 2 + i32.eq + br_if 0 (;@7;) + local.get 2 + local.get 4 + i32.store offset=12 + local.get 4 + local.get 2 + i32.store offset=8 + br 1 (;@6;) + end + i32.const 0 + i32.const 0 + i32.load offset=1048984 + i32.const -2 + local.get 3 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=1048984 + end + local.get 0 + local.get 1 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 1 + i32.add + local.get 1 + i32.store + local.get 0 + i32.const 0 + i32.load offset=1049000 + i32.ne + br_if 1 (;@4;) + i32.const 0 + local.get 1 + i32.store offset=1048992 + return end + local.get 2 + local.get 3 + i32.const -2 + i32.and + i32.store offset=4 local.get 0 local.get 1 i32.const 1 @@ -2150,123 +2160,112 @@ i32.add local.get 1 i32.store + end + block ;; label = @4 + local.get 1 + i32.const 256 + i32.lt_u + br_if 0 (;@4;) local.get 0 - i32.const 0 - i32.load offset=1049000 - i32.ne - br_if 3 (;@1;) - i32.const 0 local.get 1 - i32.store offset=1048992 - br 2 (;@2;) + call 8 + return end - i32.const 0 - local.get 0 - i32.store offset=1049004 - i32.const 0 - i32.const 0 - i32.load offset=1048996 local.get 1 + i32.const -8 + i32.and + i32.const 1048720 i32.add - local.tee 1 - i32.store offset=1048996 + local.set 2 + block ;; label = @4 + block ;; label = @5 + i32.const 0 + i32.load offset=1048984 + local.tee 3 + i32.const 1 + local.get 1 + i32.const 3 + i32.shr_u + i32.shl + local.tee 1 + i32.and + br_if 0 (;@5;) + i32.const 0 + local.get 3 + local.get 1 + i32.or + i32.store offset=1048984 + local.get 2 + local.set 1 + br 1 (;@4;) + end + local.get 2 + i32.load offset=8 + local.set 1 + end + local.get 2 local.get 0 + i32.store offset=8 local.get 1 - i32.const 1 - i32.or - i32.store offset=4 local.get 0 - i32.const 0 - i32.load offset=1049000 - i32.ne - br_if 1 (;@2;) - i32.const 0 - i32.const 0 - i32.store offset=1048992 - i32.const 0 - i32.const 0 - i32.store offset=1049000 + i32.store offset=12 + local.get 0 + local.get 2 + i32.store offset=12 + local.get 0 + local.get 1 + i32.store offset=8 return end i32.const 0 local.get 0 - i32.store offset=1049000 + i32.store offset=1049004 i32.const 0 i32.const 0 - i32.load offset=1048992 + i32.load offset=1048996 local.get 1 i32.add local.tee 1 - i32.store offset=1048992 + i32.store offset=1048996 local.get 0 local.get 1 i32.const 1 i32.or i32.store offset=4 local.get 0 - local.get 1 - i32.add - local.get 1 - i32.store - return - end - return - end - block ;; label = @1 - local.get 1 - i32.const 256 - i32.lt_u - br_if 0 (;@1;) - local.get 0 - local.get 1 - call 8 - return - end - local.get 1 - i32.const -8 - i32.and - i32.const 1048720 - i32.add - local.set 2 - block ;; label = @1 - block ;; label = @2 i32.const 0 - i32.load offset=1048984 - local.tee 3 - i32.const 1 - local.get 1 - i32.const 3 - i32.shr_u - i32.shl - local.tee 1 - i32.and - i32.eqz + i32.load offset=1049000 + i32.ne br_if 0 (;@2;) - local.get 2 - i32.load offset=8 - local.set 1 - br 1 (;@1;) + i32.const 0 + i32.const 0 + i32.store offset=1048992 + i32.const 0 + i32.const 0 + i32.store offset=1049000 end - i32.const 0 - local.get 3 - local.get 1 - i32.or - i32.store offset=1048984 - local.get 2 - local.set 1 + return end - local.get 2 + i32.const 0 local.get 0 - i32.store offset=8 + i32.store offset=1049000 + i32.const 0 + i32.const 0 + i32.load offset=1048992 local.get 1 + i32.add + local.tee 1 + i32.store offset=1048992 local.get 0 - i32.store offset=12 - local.get 0 - local.get 2 - i32.store offset=12 + local.get 1 + i32.const 1 + i32.or + i32.store offset=4 local.get 0 local.get 1 - i32.store offset=8 + i32.add + local.get 1 + i32.store ) (func (;7;) (type 4) (param i32) (local i32 i32 i32 i32 i32) @@ -2470,45 +2469,44 @@ i32.add local.set 3 block ;; label = @1 + block ;; label = @2 + i32.const 0 + i32.load offset=1048988 + local.tee 4 + i32.const 1 + local.get 2 + i32.shl + local.tee 5 + i32.and + br_if 0 (;@2;) + i32.const 0 + local.get 4 + local.get 5 + i32.or + i32.store offset=1048988 + local.get 3 + local.get 0 + i32.store + local.get 0 + local.get 3 + i32.store offset=24 + br 1 (;@1;) + end block ;; label = @2 block ;; label = @3 block ;; label = @4 - block ;; label = @5 - i32.const 0 - i32.load offset=1048988 - local.tee 4 - i32.const 1 - local.get 2 - i32.shl - local.tee 5 - i32.and - i32.eqz - br_if 0 (;@5;) - local.get 3 - i32.load - local.tee 4 - i32.load offset=4 - i32.const -8 - i32.and - local.get 1 - i32.ne - br_if 1 (;@4;) - local.get 4 - local.set 2 - br 2 (;@3;) - end - i32.const 0 - local.get 4 - local.get 5 - i32.or - i32.store offset=1048988 local.get 3 - local.get 0 - i32.store - local.get 0 - local.get 3 - i32.store offset=24 - br 3 (;@1;) + i32.load + local.tee 4 + i32.load offset=4 + i32.const -8 + i32.and + local.get 1 + i32.ne + br_if 0 (;@4;) + local.get 4 + local.set 2 + br 1 (;@3;) end local.get 1 i32.const 0 @@ -2643,137 +2641,118 @@ local.set 3 block ;; label = @1 block ;; label = @2 - local.get 2 - i32.const 1 - i32.and - br_if 0 (;@2;) - local.get 2 - i32.const 3 - i32.and - i32.eqz - br_if 1 (;@1;) - local.get 1 - i32.load - local.tee 2 - local.get 0 - i32.add - local.set 0 - block ;; label = @3 - local.get 1 - local.get 2 - i32.sub - local.tee 1 - i32.const 0 - i32.load offset=1049000 - i32.ne - br_if 0 (;@3;) - local.get 3 - i32.load offset=4 - i32.const 3 - i32.and - i32.const 3 - i32.ne - br_if 1 (;@2;) - i32.const 0 - local.get 0 - i32.store offset=1048992 - local.get 3 - local.get 3 - i32.load offset=4 - i32.const -2 - i32.and - i32.store offset=4 - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 3 - local.get 0 - i32.store - return - end - block ;; label = @3 - local.get 2 - i32.const 256 - i32.lt_u - br_if 0 (;@3;) - local.get 1 - call 7 - br 1 (;@2;) - end - block ;; label = @3 - local.get 1 - i32.const 12 - i32.add - i32.load - local.tee 4 - local.get 1 - i32.const 8 - i32.add - i32.load - local.tee 5 - i32.eq - br_if 0 (;@3;) - local.get 5 - local.get 4 - i32.store offset=12 - local.get 4 - local.get 5 - i32.store offset=8 - br 1 (;@2;) - end - i32.const 0 - i32.const 0 - i32.load offset=1048984 - i32.const -2 - local.get 2 - i32.const 3 - i32.shr_u - i32.rotl - i32.and - i32.store offset=1048984 - end - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=4 - local.tee 2 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@3;) - local.get 3 - local.get 2 - i32.const -2 - i32.and - i32.store offset=4 - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store - br 1 (;@2;) - end block ;; label = @3 + block ;; label = @4 + local.get 2 + i32.const 1 + i32.and + br_if 0 (;@4;) + local.get 2 + i32.const 3 + i32.and + i32.eqz + br_if 1 (;@3;) + local.get 1 + i32.load + local.tee 2 + local.get 0 + i32.add + local.set 0 + block ;; label = @5 + local.get 1 + local.get 2 + i32.sub + local.tee 1 + i32.const 0 + i32.load offset=1049000 + i32.ne + br_if 0 (;@5;) + local.get 3 + i32.load offset=4 + i32.const 3 + i32.and + i32.const 3 + i32.ne + br_if 1 (;@4;) + i32.const 0 + local.get 0 + i32.store offset=1048992 + local.get 3 + local.get 3 + i32.load offset=4 + i32.const -2 + i32.and + i32.store offset=4 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 3 + local.get 0 + i32.store + return + end + block ;; label = @5 + local.get 2 + i32.const 256 + i32.lt_u + br_if 0 (;@5;) + local.get 1 + call 7 + br 1 (;@4;) + end + block ;; label = @5 + local.get 1 + i32.const 12 + i32.add + i32.load + local.tee 4 + local.get 1 + i32.const 8 + i32.add + i32.load + local.tee 5 + i32.eq + br_if 0 (;@5;) + local.get 5 + local.get 4 + i32.store offset=12 + local.get 4 + local.get 5 + i32.store offset=8 + br 1 (;@4;) + end + i32.const 0 + i32.const 0 + i32.load offset=1048984 + i32.const -2 + local.get 2 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=1048984 + end block ;; label = @4 block ;; label = @5 block ;; label = @6 + local.get 3 + i32.load offset=4 + local.tee 2 + i32.const 2 + i32.and + br_if 0 (;@6;) local.get 3 i32.const 0 i32.load offset=1049004 i32.eq - br_if 0 (;@6;) + br_if 2 (;@4;) local.get 3 i32.const 0 i32.load offset=1049000 i32.eq - br_if 1 (;@5;) + br_if 5 (;@1;) local.get 2 i32.const -8 i32.and @@ -2837,133 +2816,126 @@ i32.const 0 i32.load offset=1049000 i32.ne - br_if 4 (;@2;) + br_if 1 (;@5;) i32.const 0 local.get 0 i32.store offset=1048992 return end - i32.const 0 - local.get 1 - i32.store offset=1049004 - i32.const 0 - i32.const 0 - i32.load offset=1048996 - local.get 0 - i32.add - local.tee 0 - i32.store offset=1048996 + local.get 3 + local.get 2 + i32.const -2 + i32.and + i32.store offset=4 local.get 1 local.get 0 i32.const 1 i32.or i32.store offset=4 local.get 1 - i32.const 0 - i32.load offset=1049000 - i32.eq - br_if 1 (;@4;) - br 2 (;@3;) + local.get 0 + i32.add + local.get 0 + i32.store end - i32.const 0 + local.get 0 + i32.const 256 + i32.lt_u + br_if 2 (;@2;) local.get 1 - i32.store offset=1049000 + local.get 0 + call 8 i32.const 0 i32.const 0 - i32.load offset=1048992 - local.get 0 + i32.load offset=1049024 + i32.const -1 i32.add - local.tee 0 - i32.store offset=1048992 - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 + local.tee 1 + i32.store offset=1049024 local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store + br_if 1 (;@3;) + call 11 return end i32.const 0 + local.get 1 + i32.store offset=1049004 i32.const 0 - i32.store offset=1048992 i32.const 0 + i32.load offset=1048996 + local.get 0 + i32.add + local.tee 0 + i32.store offset=1048996 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + block ;; label = @4 + local.get 1 + i32.const 0 + i32.load offset=1049000 + i32.ne + br_if 0 (;@4;) + i32.const 0 + i32.const 0 + i32.store offset=1048992 + i32.const 0 + i32.const 0 + i32.store offset=1049000 + end + local.get 0 i32.const 0 - i32.store offset=1049000 - end - local.get 0 - i32.const 0 - i32.load offset=1049016 - i32.le_u - br_if 1 (;@1;) - i32.const 0 - i32.load offset=1049004 - local.tee 0 - i32.eqz - br_if 1 (;@1;) - block ;; label = @3 + i32.load offset=1049016 + i32.le_u + br_if 0 (;@3;) i32.const 0 - i32.load offset=1048996 - i32.const 41 - i32.lt_u + i32.load offset=1049004 + local.tee 0 + i32.eqz br_if 0 (;@3;) - i32.const 1048704 - local.set 1 - loop ;; label = @4 - block ;; label = @5 + block ;; label = @4 + i32.const 0 + i32.load offset=1048996 + i32.const 41 + i32.lt_u + br_if 0 (;@4;) + i32.const 1048704 + local.set 1 + loop ;; label = @5 + block ;; label = @6 + local.get 1 + i32.load + local.tee 3 + local.get 0 + i32.gt_u + br_if 0 (;@6;) + local.get 3 + local.get 1 + i32.load offset=4 + i32.add + local.get 0 + i32.gt_u + br_if 2 (;@4;) + end local.get 1 - i32.load - local.tee 3 - local.get 0 - i32.gt_u + i32.load offset=8 + local.tee 1 br_if 0 (;@5;) - local.get 3 - local.get 1 - i32.load offset=4 - i32.add - local.get 0 - i32.gt_u - br_if 2 (;@3;) end - local.get 1 - i32.load offset=8 - local.tee 1 - br_if 0 (;@4;) end + call 11 + i32.const 0 + i32.load offset=1048996 + i32.const 0 + i32.load offset=1049016 + i32.le_u + br_if 0 (;@3;) + i32.const 0 + i32.const -1 + i32.store offset=1049016 end - call 11 - i32.const 0 - i32.load offset=1048996 - i32.const 0 - i32.load offset=1049016 - i32.le_u - br_if 1 (;@1;) - i32.const 0 - i32.const -1 - i32.store offset=1049016 - return - end - block ;; label = @2 - local.get 0 - i32.const 256 - i32.lt_u - br_if 0 (;@2;) - local.get 1 - local.get 0 - call 8 - i32.const 0 - i32.const 0 - i32.load offset=1049024 - i32.const -1 - i32.add - local.tee 1 - i32.store offset=1049024 - local.get 1 - br_if 1 (;@1;) - call 11 return end local.get 0 @@ -2984,19 +2956,18 @@ i32.shl local.tee 0 i32.and - i32.eqz br_if 0 (;@3;) + i32.const 0 + local.get 2 + local.get 0 + i32.or + i32.store offset=1048984 local.get 3 - i32.load offset=8 local.set 0 br 1 (;@2;) end - i32.const 0 - local.get 2 - local.get 0 - i32.or - i32.store offset=1048984 local.get 3 + i32.load offset=8 local.set 0 end local.get 3 @@ -3011,7 +2982,28 @@ local.get 1 local.get 0 i32.store offset=8 + return end + i32.const 0 + local.get 1 + i32.store offset=1049000 + i32.const 0 + i32.const 0 + i32.load offset=1048992 + local.get 0 + i32.add + local.tee 0 + i32.store offset=1048992 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.get 0 + i32.store ) (func (;11;) (type 0) (local i32 i32) @@ -3497,8 +3489,8 @@ block ;; label = @1 block ;; label = @2 local.get 2 - i32.const 15 - i32.gt_u + i32.const 16 + i32.ge_u br_if 0 (;@2;) local.get 0 local.set 3 diff --git a/homestar-wasm/fixtures/example_add_component.wasm b/homestar-wasm/fixtures/example_add_component.wasm index b6094828d732dc005cefb4f67c96e2e098a6c7d6..4d0f6c696a3b832f2b2c6727e2439bd468638b79 100644 GIT binary patch delta 2066 zcmb_dONbm*6n*d3=T&!gO*i9=^Qm#>RT;F6llTE^CPUCEowN=~3_@1wMi7-A19mcK zK*@AaQPhR`nE?u|~&wa0YK7u7o{8wo~BaD(0t+Dydg_(J*V=_>5F~QZIj4F z8Fh2Tt3Xrvsy)xClL(D{K@CkhC@5;kxD+}pPigs7ub?x};{8|)ZR6qRctJjIySdP! zO@1luiE74(ii3Cfg~N$?+`lzNHorXC)3lZav`oty2d!Knr84LJIUv34r$@sy3lLFh zER>cRf+r%5wQ9Z4@Am<(Y^^4Q=Tv3Ks+&ukyVO{98M@d^pNHn2pe>c@1rxa%a#fD2 zVyfH zdLH%?z|)Zqp_YB9DGQ;srZz=c7;iv+0r_>ve+}qiYl2E}O^^|YnYP%}K#9X0t0wpJ4? z@s5205@xSjIm8^DGuVo2d8FlP?x&f@6j$K^9BAw#TIP$bJb+_FImR~50*J3YtbNXz5y&)R44=+8s!0 zr8UyG8KIa#5ae(pb@NnXKs~~%B-X2M1 zIJhwKwTDhPJ$ii%T`K2=rdPjup;1TYwW(oyR35}1{Ir^Q(3ja((OFI$r&U-TnqO+i z5b@snF3bPbXcbM3AvknDweG{U9Fggk- zTmivwTLHzefac9uKV0MK1b{3tMDaD_^C8M`Tm-yEv#e!2GjQcYu1Y6?-8OrqllZjE zruQ*3yI(Ox`PbCV=&pOyqVpIAk(M#$FncFv76ko3R-gn>8oK@0hVO&fC^( z6VLhBTKaewTlmuO>6|>*Tk)-9;{~W#{c+XLl95!2CVs z*bVW4-TA3m_E^~`e1^=~bdPy`dcK92P@x+9NNnPCR};J{d)cNpx2F%VCgny!pE8ly z{Fv_EZ)L*3*tE2lSxbXVl;L9w1^G*0C2pX9JTAMmgQ{u1pI#W;HFL$I){#M5FOs*o H_m+PEU)hL) delta 2260 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 F{|D@VwJHDr diff --git a/homestar-wasm/fixtures/example_test_component.wasm b/homestar-wasm/fixtures/example_test_component.wasm index 41b38a4f694e0b6e383b389f9ee14a7b1e9cc10c..971680099477ca27557f4f8762d350dc7eb2da07 100644 GIT binary patch delta 29352 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 delta 27845 zcmb7t31Cgv^Z%Q3UpCnu5=mrxFP4Oeo!aZFom#3WZEdC6)J{v+#Gcx@7)tF+N<@fw zQClb#`%>Ca)lk(~YAM=K<^MVNzW3yb{=WUAcbPM1X3m^BbLN~g_x0-BQn#m+3N`f| zIoRJsCOR^$O$)5{BWgxS-}>}@jZe}qXPKO%q7@bdC95d{RA$-~oPw)l98 z08g(X|L|JmpV>1}@ec6x4k%fwl-cC%p_t48inpnB>7XD*{xO-o&5BS0lwdrYAVHa+ z5bW@g#3=Qj}m1NbCCJ<>^CSW>c6}Lj)^6N?EOqs7e9a z7!hmpG5MHHCLf;wlP7c#5FlG`4v&b4Fe{OfCbL(RmLq~n1fWG;0Rbhv0<;UFeboSS zfXSqI`J2sNW-kG`z)AiGRe0$C0-}ZfCmMIF_L@>T?urubIe5U}p(8#NCa=Ez`zH(? zGQgBh{(XlH8!)7Q!pKo0-W@W~ltCOg$s8JO+C+YdL*E_ZfR8j~lFz6SeTR%3Hgx0w z(-!TbQYL9DnfncTe}u_SzVe>XukXkKb!(fl$^6!cp~Fnu$XDK-;IGMN;E28-j(n@{ zpaG`sWO2PX5q6OGh@qqUjv7$Ex@ji`I-i}zyU6dYzWv_qJz_xLL4$_AWm-#S9yZAw z5)4rT^b&|Rs<$rDCgP;Y<}y{?i*%_rQSejbqVl26o)mMCf;t@exj8%x52c=>I>nL2 zyFWLV6{bzvJ02C2j?)?Xp5D8nTvL8i4pE`_fezDs`jh^qRmu(JSLKHIk@A%9m2ra2 zKPtZ~1^7D=6t$DzhJ;z zreEm_J)yHSLCh0##bPmEuvj3z5J~&!TUsQ(q1hr$`BKag3&j?(TA6Fk5)YMw%338= zxvBg_kLixsu3Q!u#UEn6!pc2ySNtw+iQ8g{$PnknCGm^+Q5;cz7MsLP@vA5hSH%@^ zLHs0s6W7Hx@wakIxvkt$#*52JzH(eSuN+fymFvo~>gGR`CFZ5(i^?hGJLRNuLb;@z zRW2yMC_gLblrPQm&5O-nm=~EBnirT$thf7b$=u=T>q%LexqgQgy2I~xRbI$EY*|B; zu8k;lEa1f0{PbC07D{t)%@my*y{c2`>*HtJ$)8ezb_w zI?f0W%uYtRM6e30hh-bj%oSy{nKL}*L{=aJt3%{OO4Ck9z6vobMUBMu%c%BrM7tLC zDrIOdMt|%O+(Qjb1nG1wH@Zpr^a&taR$ctlH_0Y7-YOu9I%{%{l+Ti3upFfA@eIDK;v}3HMRUs5xtOxC7GzkPJ7-f5GuWtyy|4r}q4< zUJl4M2W3CtBpWz<@&xtJK8Xvox%aSvS<+;MQl$P9(U`QjHT134rBaii9kSThYKjvk zt4E?NhiX?>`t%tyk;J344wc(J1J~A6?oQja$CWG64y{6bJKCuYkB=<%KvK0Se!|zP zfUoSXW$_K^8|{32l-QNbIX_Zxc(h40h4uKfkw#=SS$0dq41_GuXO${_%j6i6t0o(4 zNt&ytYO?InR#vHijyPDQBdyn7tQzU-ZS_%4q)2t|)zpMq1~fvbJCnKbeu0swjjp}| z9aOf)N}8zcs$ruCT7Hcnnxx&Yv8d#JT|NPRR$TlnJGA9Bo6*6{%QZU*I;7R9GmH*v zYwElTiT|$i9ft7nx*hNw^+GtF8@zz$%+Fu=NC3&)po7O=NlokULWRsv8nh)0xC0H} zh&igW@KR5EC0acaOtoYDEk!`+hND`GMu!9KPtkN(bmP!KV*u$@by&;BO=yocv2hLm zV-j06saMXJEE}|M8h51QnWdXd5p+UZ(zG3}*Ys;$RX&eX+u(XcT}dZ1hc!zjI;Dj* z|Ba4o)Z#PxPD^evicV|yThyg9T9uZs<2j{eO+0UJ*%GCqRXJQMw)!;sw9fO%6J@y7 zXOxvj*w8;-YQ8ikxQH{_-d2784F^p(aGJHQ2x{-PZiZ`G>yN-IwoP|jliEarx9R)L1GoZIwSxrh?$DHenA5QeAtlM?IVhF6Sw_}G)$>5lE`d(GBqF(~fp6agRqW2ct{ zoztSbyhGn*rgT{$P!iv5K6vfz)(-3gy0;LtT6?ueW{}f8k$O;37oUd5#5fD@*_2Ld zZ}dC{TJf*HA|TmkZ&pW*jc-c%4!^k{lFjZFOh0OCdwoXdwTitj!i9SGiNF}F(5G3* zPYx51DurU9G_%#yazXp7k2T1!d56A@HvFt@?(;RO4(?kW*Cl;ZVQ{Vb^$c`b7aX{> zu8e+ikbmE=9LgW|Yy2EG{?=fy>F`$EbJ=80d26OL=CT7O(@|~3fOyK+jt!`W=f?vI zlP)@hE`XP}BwC!WH#}U=abgwJ9w*W~Etd>rCnVL=@{1Faq2pwGS}waows=~8bwYON zIQu*;S6mPu9hBo~`Hjhr33AQAij6!2K4-g z*|agkn$qvuwqez2qjqiBAY8i-e}QglGlxghEp6lQ5gxZ?OSFR{Bej=CG(oMZBjdDn zBVy=|c63A^x{ddCFuIRMw${taX;(&8lk@ecs-W9qR7+QlGTNb0YaKZA-p{~O%MV(? z{%3p;OMhrvKgfpcLq|8IpR|n8A!tGF=nhY9#nl4K&sya%FG94$F_CmZZ^Bn&O4EhR zony8WYJK=oZD=C>qq1}t+Ktq%d^8$u>h*CwuY0l`qN%p&!$|Gxk4w|N%wr!X6WnUY zPupYcEdDf{)@a$EwvRMMwU>Ir?uq|qk zx?`+?eOJeBnp76puP4=waMG^sBwa=gRUbK!Hzd^s>B~vE(8Px15fIHf_LWd)6-;4d z^-#^~y$O=`O>N59i15R*QPT3g<;BnHgIRa(L|ML9+cUNd{hpaOwjM#hCCBGDBDaU* zo6~OX#R)aRWyFM@cs@GeFfG#-PHchM>*t9dIPgB3^m6G9dccOj>4*sfUEL>Vf(w%d zhB=*+0K4QcI1x4Vkn~VnGJ8#aO*%FE=U>%#B9%~=2{l_UcY^%Yl4`CS#9PfVs|5x~ zTR7#CD?F3IhJrBEY&j>}iYZyabX|B#a7X}>a;zxC49EgEhzB64ZV*W)yC_5@ zk%Slr!IgK6_Hb%7w4?I02Jo>1rd5{V=(Hhl=TD}k1D-y;vN$!4Z+I%P8qKIcXaCR8 zIdkTWE^=D=W#%WYb|JJ-Qw|rE*-Ogo1__Wd7lULMgX9*2O9qyBT@`!jEY&*0m;@%%&w2o)O>a&Off@dk1XfxKM?HbPtX-)mhqqzy+EgurJ3g> zps6+Iw88b0ISB)c)KT*c97nfFu@<;>q@sNoHf&x`NOE}I35?3c^QR)3jN^J($@S$I0a?y3k*591mFR#LxL^b2 zYC9Ly_0pGiqA7Q3q{L_u3%{nc%pVpGBl=5gy(kIS+(q>|?3Rpt7ZFjC)#R8uO;2Z0 zlRAN%vncF-2gQ&Tv0X8O2R6lK0KW~7I8C2rs z8Gzqpzx#GD$uVX*p{-xslpbW>SbUBg!{^qLbegU$SsIP&uBG*9hE}-rDxNPbYlzt* zYWe%YPFvEK!_@2tp_agGR%1o9c3^oVaDH9h4%ZqhUiG*kd1>j(!?cwvLSPZwSG61cRSJXk&Mp^27jaxnOt{S+=)UMZtpUTLLkn#ZcfLC?e0x~*#R{DRa~ z@w8t%y(%vBfaHN@64a=QR{F9V0Ya`8w7LpBiMqN0J=8u~{RyrQR`(Z=leOM!8ezsd zv8J51d(8)!IqR-HPJe2isa8D4rgp;fnAB)IFG%f%=Ue)7;8(Af_*1qM{x`>@C8ve} zVd7UcfMEa11_Ln3?$|qe;SxwVhpmb7t6*!B`^pDWO zsv9eNr5VF~&WwuU?{Q4gB34V^SV4>(Z#+NR7>2p0OvYwxVI0iZLwho(Zt@_|pS?L6 z9kyw60;v7G*@i1+p3|CbX#bv&3g`+B2`SplYh$;_hOb1*Bhy#kn3-`-be6tC^v zE@>U#E@}Dgkk`K3;gQnU_yr&*Sj z@KrtVcb}tnr#<1|JZMk2a|;K4)TG|io0+BU*%KDv>H7?*A}SQ@`4|?`e=p{&%o%$t zORL!Sjg+&lM4&S&?9M?Q5px`ZC?5|-RA@e=fFoBab}jGOlV2*~trx8b?f z0T@Z<$O9P^y;>ioSpLV!J%B3kI@y$nj-K#^?6bXEvx7tEA8pgY^8Zo5hX>shn+WB3 z{flB{*)w8yI3&eR)UT5co%A;LHGJD(>#dCT$&n8$Crf{bUcn+!QF9UcEt@Un%+y<< z$gLU2e&kp!`e?h-_Z@JFnW1BD(mp-<0fzFuqg82vR_<7RJimFYErxRHv2`ME99x?! zA#4d#Drs|%htNVT?RaHpurJiq5bV~dg@K8dJnh=?3aEwIA|YF!GO>|)o#b}r4P4_y zK3Tpn{5f>n!XJU6tZm8GPMom9ZQVIhhQ83ePd1<}+Dj*G^tCqNwGIDP5PEJSoy*KU_l<%vI_*N)5M4b|Ik{M) z>iq#C^TIr$OkN*PHEELlLOc}`cK(ts1yhQ4@mhJ#iJ($6K|69Oil%ATFGbQ` zhQD3p!?I&yye+e}J=eQ(vY9H;crE%~Lwim&T1A@g6&nJngezsSIoJA19G-{k&(p5N zMQD1TDhVbZTc{N`ZOV(g(sMTUwtwsM9 z;xkbnMbyBgowym}HC|#^W^#^=rnpHN!b59ON3XoUB%`_7I|X$qQ(Ib4({GY73Y(c< z5UaG#h4Go*SNBrLddJwDr6gLztU>Amy`EG`1DdQwT#u%iTBGZqxUu+%CS^w7Q%O5< zv8lG~el6|x?`z!%d04~LLcJj=TKdiAMTJe$HeC00qZYzXyy<1__APIkt39|S#jSq3 zn7IFF7p^wbW<6-C^||J$-Mupi-Ph-jBHia3sAixI7HbG}hOL|_+Lk|BBIf)1k1)Eg z1>YU##$JPik+q{&jzhWU|8htRo7(L| zkEBhx3&m9x97AUQ5u$B)SQWA7`G;ZjIS=)tN$%q4rlm#QYn1uoqi-pEj$u31=#;Qw zy;_YT6W z)tqebio-dT&0geeXUrWDm+EN}B7zcMa0COgzP zL0BG*;~%}Kj@Idk2M2po{r{k}t~8gs`q2NdE58yhJJ;CHk6MSj)KjSU;Uipi^@!)H z#|}^CBlwL#T{TI8x@r~%7FEVIGAyfroR-BuxglI4LtFXe(w5DlE4Q76!h%x_6VhFn zhh>UqB1H88~oS`D;p9@ zVI>SjU;}`(O^>Z@T-7&|F&s0n*I|%%(^#r(h7{nL(Z{;OriSpo?$bb$_Iz*Uf5OnB zr0(Y>M`88+ujbw!~ZY^hlP7=zJlnP#*!PNeGAmgclJvsySoZvOo1 z?8(0bQzK*Y9xU zI%=j6&!|MQs=rOITG>XmA|^ToO(w&+O(3Upi(^}53Z^f3rj6X(*>mU9JC8`zVYH}x|#ji>?RI)NvpfCA_%2c=L$a`K>{cjiX+8)}F zg8ykQJhTFNG>}8Ch`AtocbJQKYA&s%>uwLY^xTNa|8HZl|5Ql(T&aOh&^WNB{#Lmr#l^dbR5Bv2xPObdJ|C}He=Fm&%rVyf zOUTvLYp%34KQlBI%D>d2Hz}Fb+EfPCf^(j-Jfb%Bq49jPAvK^098iZsyo`{}%pDt3 zV}7>|_2yHxsV4irKmjzFYt)B%rf_s!`kHg=&^xTYKxr_mn=jCd@Mu-*!OUlHJ8bLG zOuku%Q3Qk8t^FJ7$&&HlyQ8klinkjyHR{;gCNGLt4AjA z?e;XxMQ=lQXY)(5wS(R*RXR|7F``Agxj0dD^PrA2$U$}8GyN=;>P`@LaZBKq%L}!+q{Y{FHQ|gOnH@(oaFwO4xdUVby&7Iy$QNenX2*Q z&eVlJ=?>qZ8+ciMjyik2MxEe6Hoo@MgXF(P70oL!Dmn5Em`J%ER1TxCRS&9LdZ`?% zF+NC!Nq(#hSG_;5tmIieCoTroFI+pUz!+QA>J0rTe`ohTrN#y}Z8CTOPp2`(Soh z$J`srp2`Ki>0?a7p}oOrGWX~W2m2N8?L)D&p0D+x?(oLV`qFBLm($gx-f(!ihkYqA zWRKI6`N3~u_;_3Olq&wXAJ#jcb9z4-;h3LwUw~X7=~^%hYQIH^;JoxLbeWMkROTB= z%jAoHrR7GY{?{^v|9*>J3NjWO&(L_KKOGI4s@s?cSlc|UxcN!PRcinZMDwfz$jaLX z(A%NLj+{)GL-!S6pKaQ_9OyrjhyN;~`L(wx0md@vZFKqs9y%CPhcQ$6HZbwb!Emb; z2T~|ce47IK$$Qk*U#B4r#_D64%=z!=^AO6Z$$P!i1spu45vU zyGqV+vFe^N5pEoc8D|6E8wz2UNMYZlrG~Gruwl&D+T1Mp3J<-MY1!<&o7=;}Js>sYYM(rBT!* zV52c~WyU30_HotssUrw{_C8gleZ2a8da-ye8sC2(nqI@@Kfp}+EwBE7qG<>3|A0~{ zjfaeeJO}v5XzD>(TyYF$knNm32ED$6&x}DxpJfmEkn#oP*b6_#&Ijf2;m-hGVfRm> z$_lOEdgG9xn`19Eo_Z3km`YUw#_4)QT9+Y2?o?{n!4N`1iU{E;h0w7KA@pJiRF4!9!i9xA1vn5S)Q;oM z)4=X)erFoOxs9AUoyrF+H+m07t2ccxVElMGg;<^4>c-mP3S|4a`g978GaCF9Y><1} zTmiy*b3xd}Gwok^zWKj+>Kc@i>QvP#zAzub+Xnt;K8D18djiuqPdZ@F_!3aMZ9l#Q zS=}Md6C1EadO!&^ZH07{clh!$n&opxw%6o)mH5t95ygv^!=D}G)63~K+!9vMjM4?V zMm&%dr#~US^dj)tLQK zGT#uiD^3Ji38Ng*9t6nK;fFk^gm$r1kp1L3`a}4flt7!wPjHiT3g_Q8fY&Lmn~qrP z6t~|YLb-ZrrGou(IyIrv+hlJd)K}CJYQfeVs4LrECxdD#Xw^HJGzV1zPgAhnvW0$9 z%H&JZIMMb{{V)wJ1v>H^^6g4CwI#~u4%TIlF7U3JlXr$z%-Qi$dfy~MG@?87k+l6l9wgFKlIih&6F&6 z?XA9{hJx<#`2G0UAd6r57N(iSB@WP!STbBWK&^1AnoIlXj{ROP=0K$D_di5Ktwtw$ zsL1NVDUef}@C=LBfj#o)?;av&F0vEL{^?=*jGkN7k;)zqVrd+H6w%CCe(5MRH@X_C zmvamRKFfR*9{RTZ{!w@nNa}Z-PPls04|Bt1oqsmpI*yJ{<31;FOP9&v@3=CfWkg{~ zx3@Y;2ZYhlD;@pKxu>ZGOd#M4jR0uu85)k8%Xql@_tYO6-}wV&lq`i#Rts-ol7%ry zWpmZVG+yG=gEesPCZYRX%i1R4_iv-bO8OGS@$qDtC@HD;DGV& zc?x!%rx~f-jtz_l9PtyyiZx@o`vFm&-~EX`EaOOZGpV_=uy024i-JZNkA*)WMA*Rn zF3>i5#1TJZs+w+Z^E1__O3pLzS(jw9tE)NkZgKjC01S}Z`4W=F>+@*~R{ni1qAO?E zQ!XO5#OwA;(BB5T^%rU_U*Ek_=-WNAw!@qVmY;7*w9Mf2%Tz1OSgas_ zY96HZjuWlrrM=~GGC#abrx1u8{gu8%m%n#~MnmvluAm2|v-LNsR5MRfv<5&;s8U^C zYO6UHrKEfH5^t;zkW=7e^;b{m;N$%9Z}g*>p@+XojQY{%T6d=(oRu6#CpD5B>7 zthd)8v919(h{Q=YbmG7%3CJurh{Rdu1_=Qq!wn*Fatug$2O(kgQ*ZxNl)X?R)#M90 zg!f;k`4}H>+@R11heAbD?2wdbhtX6*QZwYV&$>Z25%tKS2Lw{kj#XaDLmL@7wds8P zCN%~%|67PpACZ6UVs@_1w+O!_$QD8v?520tmp zj#eJmdO+nuHcf!-l~ql0CtXeUl1ZgGW=J#m0TlqX+d~ZNboO2wQp%1?dsebLb64 zyyvA_)hKn@IVnpX_ZA|G_S!!aqBg;eWh$Z$+}Kq`v`4?!HH#tW@Zp|H5Z|n=1ahuf zMACJAF6D=2Q41=m>LHrZO$i!A*Z6{mcpJACo?s+E3uRqID-$3UaZCT2@yu@_)9Vq6JWuxTu(X`|$iDO{ITT5WCgTE{(UP0+?d0Wjdl@fWlyCu3nXZXA-mF)zO!h2gN8XGY=ss|@}+O0>|E zNC&=R$R^LXplGv0rPvad8g{)pB=?YCOPN&CHB9cnWEvs$P6)xaMT>sjoUZruN+?3D zrKYTs3URvLWjfTR?vr;HG;f8(t)tE=3dM>EU6w72FrbE8MO`0<%;>2Ms|Z3*?YD|g z5HB^eq2E^X7@PP48(O7f#5<*(;e{EKv|4CVy|IfhBSth1-6;2we9=x!-U__@aD48G zui7^9FEJQD8Qi3t_!77C9sKT{z|icf^Wb7!KhGVsL982S1>Ho61+>#2BFUt|Y2} z__RvW!0l@)iBJm2l0MJ)?vFt5NM$H^GmCi91g%ozMT3e?1)F?rM$WV&?<`JSCM)bg z&pF%1%i~2fZRI`jBBu0CeQXP43|VpUfz9mZKjTGrv0aatLU~{nakPw1$Ff7mm3eAG ziE0%Xa@z#)VaF{xlw@ApU0l_BlnBGL=pPX^Fcli;fMWswECGF=?8oBYQiGm47=744H0qAt|10uqJL0B?4w=0tELD? zjdL|cM>@fkYhijxv(K(25VK@)Zf((uwy>oR)Q=T=9f1@}`+z#4mKhFYVSOUhT>HKuQwDk0le8vSb0$+VaIa1OJk9Ul3k5;bd<#Lh$i9; zwxU|UD8{4Y$cxT8as1KCqN06WQ&EDjE0v{!bQWiHlr`e(G)ct0ypXe*+#b#{jeW!u9Zk|RFvTa z)}*RH#q7^z#eHmTExMv&(MDBl1H(JWOWQy*2l-$dSN)O>#)_;zMb_WXfo+R69gO3+ zYCFCD+ID*Vlg91ucA}a?j38YMe~fI%KUI?XmS1WwK7$y~=;=%F5A6U4e~_CSw_(O@ zv2n}o;F2v^m(5T0l(J2iL=SL4M+6j3~9iuf+nIa_(R@P8fg@dRWJdE#r|L)@W@2%*FLb{A2_xwwE+ zF{vrzWNoJ#mXHN*5Ud2%R5ysk$twmaaD&KNg~cE#<8@w1#^OR1_`ARWIUtgBZZSxn z8$^;WCPDqlA zeYO+ig56uLpYR4+9}_d-RD zI=cvjFLx7V8W-pgM(I(}&;p^&kgXrol21%JwwAp$m7K5a^L3H&R5T`Sgpjy1F|IFIa&;Au5y^hWfO#hnI-*EqS4@Nn*Gs5sG3Z#>i& z4vu*!=ZsuogsDe#Xd0*Y5n++WN5K#;M{nppxg+YSu5w6uxeu0q5M*FK(atGl1~x7X zDgWt*$nOZh{FbQZCS{0H(sGQw`|EO6?~izSH4p7C@(nrPe8Jg=(mJNOi6~9+#)G0l zOT&9)K!$yrV16 z=UuVU5UM*{5{0MR7d2^al1Vvs7!Z5IL>X2d!6~(~?^@&S?0Z=2AK{9Fp6Rm?#SbF< zJP7+O$Jui*0_oMhj-lwry|RxH*$b=x%h-}2D?UrbXWrPa^s{`&e+&la(=|GZrvi$DsO7 z`%4`E(8yz4{kFzk^mMsTZ-gyTnyc-}{leKY5LBszPamMcS(?`!jCP!K14 zEE;1+;=PYWG8Pv%J{C5!_-8x^CW`~u$^0Q%oD+}7bH-c|#8yps@R6}1M?4wNGsa<& znapd)iO%BDc>BX~0-q{uwFghY??%ZQ_a=!K<=c!XM*e39gQz6D!el%57 zik&Fsgn&plfjB119^Hqcw+7j4(!k!a##ke?dy)+pp8{XAWKgu}I-GsJvk&fJ-SE}6vMGe!G1CrM5TCX>x7Q87R7yC?mH4z^n5 zHjD%->glK8r`T}gPr*;IJ=A*!m^aPTIpxm8^5r5YtP$}XIZM1IRrRz7XU)PUfE(mJ z4ZVA9mI&eLERX}g7X2JCTm0>gg?d5r=Lm1^Fh^L>?%w#pv7kr#ri#(Ea)U0>QiO7K zcLHGZ4C4p}?W$G7sze z6#jai*j|)sm@azSd=Xd_>cO(@deLO<*&;~yjJy!cK>m!MMNTb-eDhg|z4ihucU`1g=Ld_h_cD>^FA@dV4ehyBbFv0h*=W$beCEgV|w72IA|3H`~n`iO1u>Mk2E8JbE*VWvcMj3jQo(? zhO_QttHcY)eDYik*P6_2S7Re#0%xtpp4#X9+iGlu>So@-Y3*y*2wU;~<-gVxvF$K! zycU~ryZGg`qKeCYUh_I62fe=sC$lKWy;Fr3ho*{J62Z&(pQ_IbkWL|Voyy>k=W%BJPzrW@(hcJ))&asC?Lm$Upw4-GA zVU+yECk|t$@asb&)cFGo-1IQMR(RYGU!Mw#@i9r!MAHczcUZhyx_Aca1fF>qzaBFC zkf_9qkH8XFORjPHv&`_0JA`}-Ss=!ZH(qWY5gmeO=*>X*X8fKVww=wE``r1cXz86H z{n}h#RZE%}hB2`1C=PT|`NmPv!MIi8{>OCddU#A!^0_IE2RTY+9@0Yz_nP}e_Vi8e za9kf0V~pDhSx0(I-82z_rCmk2h z;K(|ipPa#R!<}&N?{&gcz88I<)9c@33R}eGeh|&Dw0`>s91wiX+kX%rTmO_@YVyUr zM;5z$wkmS@V^+^24h4w2<>5GDCr{5qXuplq@&YnNw zsOSVY`cce*@g4sWbM75(a9#|oM=x*(cE zz8)8_4*Q<}yZ}x9V6Xo(OcovRVLna})A`SQXnP@dzbND(&+dy*^BE4hBnz9qf;l~dU;PD(k2L$5U&Lh5 z%y85)9nF!I=LF!S_V!{azZ2jCj4z)8u$i_*f8(K7#9ACKg#3o+tiu<&M{%;cGtufS~mZ>c)j#$BfOC7Q~fgtvt=s}xsH{~HNJWs zcA@891ab2l*j2h9J($d|fT{iMe8$QIuaCxZ$_;EK=|2p{pZ+c?y7~~>R8LHnQ*st> z{ax&KrmU*@@|aVtuFm>g7N+sEo1$aM&AK1KA+>5%VM3P8eB&m<-%R$qg>W;IyWJA4 zoxjtDp8-N5*TcTX}7M+NfOh1kjedMLTI$?(7vX}jDqw805-Pj{3ADua>qdYJtuukAJ=+2IDmLxW?!C(DsV{q?}`_F?FO9u6c@b0Jq(E~?s-oPfHTOshZ*4n``p)WuNb#Q z^7aE?zAu_ax_2H~oSK5JaG0+C2@jUXef|`)(B3vM!csq-s}zb^ z6`c-7QJ3KyYM$PBX{B&jf%qZ4R5sLcsSr^Sdgg)HivHaH0DD$@`R)TuzFFMtAttDe zJmR5x){GfBYyx4#A+us;|*zo)tgK-0I{2MXo299|ob|KyG;v-Q` zxQy5ndrK3h7>q-nhyiV!Li(a$i&5Z^5g(bT``jS1@;x_*#JTJsxBrQlf}E^QN~mk6 zN>(^stipt;MO7hj<{FTBOO<7{oo*F6!@pJ2isE|6Aq(&zcqRO-n^5=Z#annJDQ}>y zCrBBD6K{KCMfpsgeDA>?kfob>5FN<7*5x{o3=@4zFD8EB0{gAaD5m5HW0{w`kbuk-}PNB&CraNSR%Tl|bb=#U-;$XCl@ZWy58=Zm;| zfU?MAujFDZumS>==0OLf6ZREGR-567Z`t1tls$6Me%7KOvFIe%D5Z>qV_aPd;HUY_ zE&e~V<-X@trIoo*dHo=zz4;ax8*(fQf|@S!fM6vSx9P!(+;p-Vw~M%WIkM;MCCex; zQ-qO!Zh{s(EO({!(4SDt%Z~xh@kb$w4GZVRAxgCnaP9r7PIJ9ffAzz4yM(O=5ehDK4 zu$0sBYnM|RKa1gQV5|a$D{$~KV80r*e9G(O+Zwm2#_dRX7b&oguArmzsepm0zeVzi z3PlJyB9W36busExRHAMA@GR0^J(MW&ex2X1sI-Tj@2#k0f#{f6Wgcz;aWMY<_R4We zOF~nIS5kr;Vw?ObaOcX1cUD$%aW?stH(-~G!pa%23YB$f+EmtMpJLn&8#ljry?M48 ziVU@SKYepCi{w64l!}}muLMF4iRdbd(2Fc-1@O44@!1iafPn;RuT?2ZdwmtX*BqRz z39dI6xllvg%?Y}=-#^6wt#(z5(puv7cIWAvixxgfuclPA+_?UH(YRVsa@XA68don` zHtXq|i+F%RJooqK5O*Q@8%Xz_hlB=wUPJGVL&nXwrt%hmLu)G4Eynm)tb~D&9J*_~ zv8IlC+ql)Jr8IRh@i0_2vsMvHFdMKFMPZ5oD_Oe;o-kmq6oujU2c%_bfI%EPZXSB_ zF!$X?<$NBUMS~IY8sGGAbREU7f-}%|eF0?Ip-**1^NnwEr#i|`Y~TdcRhrepBgBU*tq<=0o?X9F4s_b zDPXFcuSQtCNBEn3k(V?IFjUWOsA#}w-$P2M>gzeQ76Dv}$nko$p zyz2IFRl)w_d9K(D5**^hX3AQu&Pu(cyon{~u$PoRL3?GWh4>iZ9I``xkgd<+%P%Rz z!;KPe8I@V_o6|V+!>{|!+6RP3Jgm7QPk;_JN6eGQu`QG@@D0LOt(7TQ;J(~anUC4= za!bYLNXaZA&lBaM2p1&#=rs1SwG|qfWna}wF_T;EcAV5k$qaXfg+3~ReFy*C*#`fY z+v@z+w$=HY+v#t;+9^9|Hix%YrbN$@Jpjq^gR;0TLy8!9$``RmepsPtQ zE4^KTsTon!f;o~w78ky({N&6bL)d0fvyB2rU|Q0OU!it`l(Lpm^V}f5R$nz$m)F@6 zq&uq%+#nLCP}fIMoB{(<*1^n2z3oy!q0ZtF59z49h7LH;5nc}+&e6Eq&wMTtW5r*;8QOfP~?9#8TXFLam|d0yjt`4hT~X z2uE~<#QOio2EUBWKHVVP436!lJc4p-Rrs%gx5X5LyAA)C)jg`D(_KJUBCj0z#HQE z^&;;tJyYKPnfK0kFG{!DGv&RXdGGIzU&@sD48q9C`-Us_eZ$QSLxrg-cb;et

Wg*QjA7G=>fzY!+XR}_=IGk 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